Skip to content

Commit 1537592

Browse files
authored
feat: modern rewrite (#370)
1 parent dbe8f56 commit 1537592

32 files changed

+3351
-2494
lines changed

.editorconfig

Lines changed: 0 additions & 9 deletions
This file was deleted.

.github/dependabot.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55

66
version: 2
77
updates:
8-
- package-ecosystem: "npm"
9-
directory: "/"
8+
- package-ecosystem: 'npm'
9+
directory: '/'
1010
schedule:
11-
interval: "monthly"
12-
time: "07:00"
11+
interval: 'monthly'
12+
time: '07:00'

.github/workflows/release.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ on:
33
push:
44
branches:
55
- master
6+
- beta
67
jobs:
78
release:
89
name: Release

.storybook/main.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
module.exports = {
22
stories: ['../src/stories/**/*.@(story|stories).@(ts|tsx|js|jsx|mdx)'],
33
addons: ['@storybook/addon-actions', '@storybook/addon-viewport'],
4-
}
4+
};

.storybook/preview-head.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<style>
33
body {
44
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
5-
Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif;
5+
Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif;
66
padding: 0;
77
margin: 0;
88
color: #0c0c0c;

.storybook/preview.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import React from 'react'
2-
import { addDecorator, addParameters } from '@storybook/react'
3-
import { create } from '@storybook/theming'
4-
import 'intersection-observer'
5-
import { Global } from '@emotion/react'
1+
import React from 'react';
2+
import { addDecorator, addParameters } from '@storybook/react';
3+
import { create } from '@storybook/theming';
4+
import 'intersection-observer';
5+
import { Global } from '@emotion/react';
66

77
addParameters({
88
options: {
@@ -14,7 +14,7 @@ addParameters({
1414
isFullscreen: false,
1515
panelPosition: 'bottom',
1616
},
17-
})
17+
});
1818

1919
addDecorator((storyFn) => (
2020
<>
@@ -32,4 +32,4 @@ addDecorator((storyFn) => (
3232
/>
3333
{storyFn()}
3434
</>
35-
))
35+
));

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
The MIT License (MIT)
22

3-
Copyright (c) 2017 React Intersection Observer authors
3+
Copyright (c) 2020 React Intersection Observer authors
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

README.md

Lines changed: 82 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
[![Version Badge][npm-version-svg]][package-url]
44
[![GZipped size][npm-minzip-svg]][bundlephobia-url]
55
![Test](https://github.com/thebuilder/react-intersection-observer/workflows/Test/badge.svg)
6-
[![Coverage Status][coveralls-svg]][coveralls-url]
76
[![dependency status][deps-svg]][deps-url]
87
[![dev dependency status][dev-deps-svg]][dev-deps-url]
98
[![License][license-image]][license-url]
@@ -27,7 +26,7 @@ to tell you when an element enters or leaves the viewport. Contains both a
2726
where possible
2827
- ⚙️ **Matches native API** - Intuitive to use
2928
- 🌳 **Tree-shakeable** - Only include the parts you use
30-
- 💥 **Tiny bundle** [~1.7 kB gzipped][bundlephobia-url]
29+
- 💥 **Tiny bundle** [~1.5 kB gzipped][bundlephobia-url]
3130

3231
## Installation
3332

@@ -43,44 +42,43 @@ or NPM:
4342
npm install react-intersection-observer --save
4443
```
4544

46-
> ⚠️ You also want to add the
47-
> [intersection-observer](https://www.npmjs.com/package/react-intersection-observer)
48-
> polyfill for full browser support. Check out adding the [polyfill](#polyfill)
49-
> for details about how you can include it.
50-
5145
## Usage
5246

5347
### Hooks 🎣
5448

5549
#### `useInView`
5650

5751
```js
58-
const [ref, inView, entry] = useInView(options)
52+
// Use object destructing, so you don't need to remember the exact order
53+
const { ref, inView, entry } = useInView(options);
54+
55+
// Or array destructing, making it easy to customize the field names
56+
const [ref, inView, entry] = useInView(options);
5957
```
6058

6159
React Hooks make it easy to monitor the `inView` state of your components. Call
6260
the `useInView` hook with the (optional) [options](#options) you need. It will
6361
return an array containing a `ref`, the `inView` status and the current
64-
[`IntersectionObserverEntry`](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry).
62+
[`entry`](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry).
6563
Assign the `ref` to the DOM element you want to monitor, and the hook will
6664
report the status.
6765

6866
```jsx
69-
import React from 'react'
70-
import { useInView } from 'react-intersection-observer'
67+
import React from 'react';
68+
import { useInView } from 'react-intersection-observer';
7169

7270
const Component = () => {
73-
const [ref, inView, entry] = useInView({
71+
const { ref, inView, entry } = useInView({
7472
/* Optional options */
7573
threshold: 0,
76-
})
74+
});
7775

7876
return (
7977
<div ref={ref}>
8078
<h2>{`Header inside viewport ${inView}.`}</h2>
8179
</div>
82-
)
83-
}
80+
);
81+
};
8482
```
8583

8684
[![Edit react-intersection-observer](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/react-intersection-observer-ud2vo?fontsize=14&hidenavigation=1&theme=dark)
@@ -99,7 +97,7 @@ on `entry`, giving you access to all the details about the current intersection
9997
state.
10098

10199
```jsx
102-
import { InView } from 'react-intersection-observer'
100+
import { InView } from 'react-intersection-observer';
103101

104102
const Component = () => (
105103
<InView>
@@ -109,9 +107,9 @@ const Component = () => (
109107
</div>
110108
)}
111109
</InView>
112-
)
110+
);
113111

114-
export default Component
112+
export default Component;
115113
```
116114

117115
### Plain children
@@ -122,15 +120,15 @@ state in your own component. Any extra props you add to `<InView>` will be
122120
passed to the HTML element, allowing you set the `className`, `style`, etc.
123121

124122
```jsx
125-
import { InView } from 'react-intersection-observer'
123+
import { InView } from 'react-intersection-observer';
126124

127125
const Component = () => (
128126
<InView as="div" onChange={(inView, entry) => console.log('Inview:', inView)}>
129127
<h2>Plain children are always rendered. Use onChange to monitor state.</h2>
130128
</InView>
131-
)
129+
);
132130

133-
export default Component
131+
export default Component;
134132
```
135133

136134
> ⚠️ When rendering a plain child, make sure you keep your HTML output semantic.
@@ -142,30 +140,32 @@ export default Component
142140

143141
### Options
144142

145-
Provide these as props on the **`<InView />`** component and as the options
143+
Provide these as props on the **`<InView />`** component or as the options
146144
argument for the hooks.
147145

148-
| Name | Type | Default | Required | Description |
149-
| --------------- | ------------------ | -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
150-
| **root** | Element | document | false | The IntersectionObserver interface's read-only root property identifies the Element or Document whose bounds are treated as the bounding box of the viewport for the element which is the observer's target. If the root is null, then the bounds of the actual document viewport are used. |
151-
| **rootMargin** | string | '0px' | false | Margin around the root. Can have values similar to the CSS margin property, e.g. "10px 20px 30px 40px" (top, right, bottom, left). |
152-
| **threshold** | number \| number[] | 0 | false | Number between 0 and 1 indicating the percentage that should be visible before triggering. Can also be an array of numbers, to create multiple trigger points. |
153-
| **skip** | boolean | false | false | Skip creating the IntersectionObserver. You can use this to enable and disable the observer as needed. If `skip` is set while `inView`, the current state will still be kept. |
154-
| **triggerOnce** | boolean | false | false | Only trigger this method once. |
146+
| Name | Type | Default | Required | Description |
147+
| ---------------------- | ------------------ | --------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
148+
| **root** | Element | document | false | The IntersectionObserver interface's read-only root property identifies the Element or Document whose bounds are treated as the bounding box of the viewport for the element which is the observer's target. If the root is null, then the bounds of the actual document viewport are used. |
149+
| **rootMargin** | string | '0px' | false | Margin around the root. Can have values similar to the CSS margin property, e.g. "10px 20px 30px 40px" (top, right, bottom, left). |
150+
| **threshold** | number \| number[] | 0 | false | Number between 0 and 1 indicating the percentage that should be visible before triggering. Can also be an array of numbers, to create multiple trigger points. |
151+
| **trackVisibility** 🧪 | boolean | false | false | A boolean indicating whether this IntersectionObserver will track changes in a target’s visibility. |
152+
| **delay** 🧪 | number | undefined | false | A number indicating the minimum delay in milliseconds between notifications from this observer for a given target. This must be set to at least `100` if `trackVisibility` is `true`. |
153+
| **skip** | boolean | false | false | Skip creating the IntersectionObserver. You can use this to enable and disable the observer as needed. If `skip` is set while `inView`, the current state will still be kept. |
154+
| **triggerOnce** | boolean | false | false | Only trigger the observer once. |
155155

156156
> ⚠️ When passing an array to `threshold`, store the array in a constant to
157157
> avoid the component re-rendering too often. For example:
158158
159-
```js
160-
const THRESHOLD = [0.25, 0.5, 0.75] // Store multiple thresholds in a constant
159+
```jsx
160+
const THRESHOLD = [0.25, 0.5, 0.75]; // Store multiple thresholds in a constant
161161
const MyComponent = () => {
162-
const [ref, inView, entry] = useInView({ threshold: THRESHOLD })
162+
const [ref, inView, entry] = useInView({ threshold: THRESHOLD });
163163
return (
164164
<div ref={ref}>
165165
Triggered at intersection ratio {entry.intersectionRatio}
166166
</div>
167-
)
168-
}
167+
);
168+
};
169169
```
170170

171171
### InView Props
@@ -178,6 +178,32 @@ The **`<InView />`** component also accepts the following props:
178178
| **children** | `({ref, inView, entry}) => React.ReactNode`, `ReactNode` | | true | Children expects a function that receives an object containing the `inView` boolean and a `ref` that should be assigned to the element root. Alternatively pass a plain child, to have the `<InView />` deal with the wrapping element. You will also get the `IntersectionObserverEntry` as `entry, giving you more details. |
179179
| **onChange** | `(inView, entry) => void` | | false | Call this function whenever the in view state changes. It will receive the `inView` boolean, alongside the current `IntersectionObserverEntry`. |
180180

181+
### IntersectionObserver v2 🧪
182+
183+
The new
184+
[v2 implementation of IntersectionObserver](https://developers.google.com/web/updates/2019/02/intersectionobserver-v2)
185+
extends the original API, so you can track if the element is covered by another
186+
element or has filters applied to it. Useful for blocking clickjacking attempts
187+
or tracking ad exposure.
188+
189+
To use it, you'll need to add the new `trackVisibility` and `delay` options.
190+
When you get the `entry` back, you can then monitor if `isVisible` is `true`.
191+
192+
```jsx
193+
const TrackVisible = () => {
194+
const { ref, entry } = useInView({ trackVisibility: true, delay: 100 });
195+
return <div ref={ref}>{entry?.isVisible}</div>;
196+
};
197+
```
198+
199+
This is still a very new addition, so check
200+
[caniuse](https://caniuse.com/#feat=intersectionobserver-v2) for current browser
201+
support. If `trackVisibility` has been set, and the current browser doesn't
202+
support it, a fallback has been added to always report `isVisible` as `true`.
203+
204+
It's not added to the TypeScript `lib.d.ts` file yet, so you will also have to
205+
extend the `IntersectionObserverEntry` with the `isVisible` boolean.
206+
181207
## Recipes
182208
183209
The `IntersectionObserver` itself is just a simple but powerful tool. Here's a
@@ -194,26 +220,26 @@ few ideas for how you can use it.
194220
195221
You can wrap multiple `ref` assignments in a single `useCallback`:
196222
197-
```js
198-
import React, { useRef } from 'react'
199-
import { useInView } from 'react-intersection-observer'
223+
```jsx
224+
import React, { useRef } from 'react';
225+
import { useInView } from 'react-intersection-observer';
200226

201227
function Component(props) {
202-
const ref = useRef()
203-
const [inViewRef, inView] = useInView()
228+
const ref = useRef();
229+
const [inViewRef, inView] = useInView();
204230

205231
// Use `useCallback` so we don't recreate the function on each render - Could result in infinite loop
206232
const setRefs = useCallback(
207233
(node) => {
208234
// Ref's from useRef needs to have the node assigned to `current`
209-
ref.current = node
235+
ref.current = node;
210236
// Callback refs, like the one from `useInView`, is a function that takes the node as an argument
211-
inViewRef(node)
237+
inViewRef(node);
212238
},
213239
[inViewRef],
214-
)
240+
);
215241

216-
return <div ref={setRefs}>Shared ref is visible: {inView}</div>
242+
return <div ref={setRefs}>Shared ref is visible: {inView}</div>;
217243
}
218244
```
219245
@@ -258,23 +284,23 @@ Call the `intersectionMockInstance` method with an element, to get the (mocked)
258284
### Test Example
259285
260286
```js
261-
import React from 'react'
262-
import { render } from 'react-testing-library'
263-
import { useInView } from 'react-intersection-observer'
264-
import { mockAllIsIntersecting } from 'react-intersection-observer/test-utils'
287+
import React from 'react';
288+
import { render } from 'react-testing-library';
289+
import { useInView } from 'react-intersection-observer';
290+
import { mockAllIsIntersecting } from 'react-intersection-observer/test-utils';
265291

266292
const HookComponent = ({ options }) => {
267-
const [ref, inView] = useInView(options)
268-
return <div ref={ref}>{inView.toString()}</div>
269-
}
293+
const [ref, inView] = useInView(options);
294+
return <div ref={ref}>{inView.toString()}</div>;
295+
};
270296

271297
test('should create a hook inView', () => {
272-
const { getByText } = render(<HookComponent />)
298+
const { getByText } = render(<HookComponent />);
273299

274300
// This causes all (existing) IntersectionObservers to be set as intersecting
275-
mockAllIsIntersecting(true)
276-
getByText('true')
277-
})
301+
mockAllIsIntersecting(true);
302+
getByText('true');
303+
});
278304
```
279305
280306
## Intersection Observer
@@ -301,7 +327,7 @@ yarn add intersection-observer
301327
Then import it in your app:
302328
303329
```js
304-
import 'intersection-observer'
330+
import 'intersection-observer';
305331
```
306332
307333
If you are using Webpack (or similar) you could use
@@ -315,7 +341,7 @@ this:
315341
**/
316342
async function loadPolyfills() {
317343
if (typeof window.IntersectionObserver === 'undefined') {
318-
await import('intersection-observer')
344+
await import('intersection-observer');
319345
}
320346
}
321347
```
@@ -325,10 +351,6 @@ async function loadPolyfills() {
325351
[npm-minzip-svg]: https://img.shields.io/bundlephobia/minzip/react.svg
326352
[bundlephobia-url]:
327353
https://bundlephobia.com/result?p=react-intersection-observer
328-
[coveralls-svg]:
329-
https://coveralls.io/repos/github/thebuilder/react-intersection-observer/badge.svg?branch=master
330-
[coveralls-url]:
331-
https://coveralls.io/github/thebuilder/react-intersection-observer?branch=master
332354
[deps-svg]: https://david-dm.org/thebuilder/react-intersection-observer.svg
333355
[deps-url]: https://david-dm.org/thebuilder/react-intersection-observer
334356
[dev-deps-svg]:

0 commit comments

Comments
 (0)