├── .editorconfig
├── .gitignore
├── .npmignore
├── .parcelrc
├── .prettierrc
├── .travis.yml
├── LICENSE
├── README.md
├── bundler
└── parcel-transformer-replace
│ ├── package.json
│ └── transformer.js
├── docs
├── api
│ ├── ObserveViewport_connectViewport_useViewport.md
│ ├── ViewportProvider.md
│ ├── types.md
│ └── useRect.md
└── concepts
│ ├── defer_events.md
│ ├── recalculateLayoutBeforeUpdate.md
│ └── scheduler.md
├── examples
├── index.html
├── index.tsx
└── styles.css
├── jest.config.js
├── lib
├── ConnectViewport.tsx
├── ObserveViewport.tsx
├── ViewportCollector.tsx
├── ViewportProvider.tsx
├── __tests__
│ ├── ObserveViewport.client.test.tsx
│ ├── hooks.client.test.tsx
│ ├── server.test.tsx
│ └── utils.test.ts
├── hooks.ts
├── index.ts
├── modules.d.ts
├── types.ts
└── utils.ts
├── package.json
├── tsconfig.json
├── tsconfig.test.json
├── tslint.json
└── yarn.lock
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | end_of_line = lf
5 | insert_final_newline = true
6 | charset = utf-8
7 | indent_style = space
8 | indent_size = 2
9 | trim_trailing_whitespace = true
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | examples-dist/
3 | dist/
4 | .parcel-cache/
5 | .rpt2_cache/
6 | .vscode/
7 | coverage/
8 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | lib/
2 | examples/
3 | node_modules/
4 | examples-dist/
5 | .vscode/
6 | .cache/
7 | .parcel-cache/
8 | coverage/
9 | .rpt2_cache/
10 | .editorconfig
11 | .prettierrc
12 | rollup.config.js
13 | tsconfig.json
14 | tsconfig.test.json
15 | tslint.json
16 | .travis.yml
17 | jest.config.js
18 | dev-tools
19 |
--------------------------------------------------------------------------------
/.parcelrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@parcel/config-default",
3 | "transformers": {
4 | "*.{js,mjs,jsm,jsx,es6,cjs,ts,tsx}": ["parcel-transformer-replace", "..."]
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "typescript",
3 | "trailingComma": "all",
4 | "singleQuote": true
5 | }
6 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | matrix:
3 | include:
4 | - node_js: "13"
5 | - node_js: "12"
6 | - node_js: "10"
7 | - node_js: "8"
8 |
9 | script: "yarn test -- --coverage"
10 | after_success: cat ./coverage/lcov.info | npx coveralls
11 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Jannick Garthen
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React Viewport Utils
2 |
3 | A set of low level utility components for react to make working with the viewport (e.g scroll position or size of the page) easy to use and performant by default.
4 |
5 | 
6 | [](https://www.npmjs.com/package/react-viewport-utils)
7 | 
8 | [](https://bundlephobia.com/result?p=react-viewport-utils)
9 | [](https://travis-ci.org/garthenweb/react-viewport-utils)
10 | [](https://coveralls.io/github/garthenweb/react-viewport-utils?branch=master)
11 |
12 | See the example folder for more information about what you can build with it.
13 |
14 | ## Why?
15 |
16 | On a website with more sophisticated user interactions a lot of components need access to viewport information to e.g. know whether they are in the viewport, should resize or trigger an animation.
17 |
18 | Most of the libraries reimplement the required functionality for that kind of features on its own over and over again. Those functionalities are not just hard to implement but can also, if not done well, cause the UX to suffer by introducing [layout thrashing](https://developers.google.com/web/fundamentals/performance/rendering/avoid-large-complex-layouts-and-layout-thrashing) and therefore [jank](http://jankfree.org/) and will also cause the bundle size to grow which reduce the [time to interaction](https://philipwalton.com/articles/why-web-developers-need-to-care-about-interactivity/). Further its hard to prioritize between highly and less important events if the implementation is not bundled in one central position.
19 |
20 | This library solves all those issues by
21 |
22 | * using one central event handler per event to collect data
23 | * triggers updates to components using request animation frame
24 | * allows to prioritize the importance of updates at runtime which allows to drop frames for less important updates in case the main thread is busy
25 | * implements patterns like `onUpdate` callbacks, [render props](https://reactjs.org/docs/render-props.html), [higher order components](https://reactjs.org/docs/higher-order-components.html) and [hooks](https://reactjs.org/docs/hooks-intro.html) which make the developer experience as simple as possible and allows the developer to concentrate on the application and not on global event handling.
26 |
27 | ## Installation/ requirements
28 |
29 | Please note that `react` version 16.3 or higher is required for this library to work because it is using the [context](https://reactjs.org/docs/context.html) as well as [references](https://reactjs.org/docs/refs-and-the-dom.html) api.
30 |
31 | ```
32 | npm install --save react-viewport-utils
33 | ```
34 |
35 | By default the library ships with Typescript definitions, so there is no need to install a separate dependency. Typescript is no a requirement, all type definition are served within separate files.
36 |
37 | For detection of some resize events the `ResizeObserver` API is used internally which is not supported in some browsers. Please make sure to implement a polyfill on your own in case its required for your application.
38 |
39 | ## Supported Environments
40 |
41 | ### Browsers
42 |
43 | The goal is to support the most recent versions of all major browsers (Edge, Safari, Chrome and Firefox).
44 |
45 | We try to be downward compatible with older browsers when possible to at least not throw errors, but older versions will not be test at all.
46 |
47 | In case you have specific requirements, please fill an issue or create a PR so we can discuss about them.
48 |
49 | ### NodeJS
50 |
51 | The project aims to support recent releases of v8 and v10 and higher of NodeJS.
52 |
53 | ## Documentation
54 |
55 | ### API
56 |
57 | * [`ViewportProvider`](docs/api/ViewportProvider.md)
58 | * [`ObserveViewport`](docs/api/ObserveViewport_connectViewport_useViewport.md#render-props-event-handler-observeviewport)
59 | * [`connectViewport`](docs/api/ObserveViewport_connectViewport_useViewport.md#hoc-connectviewport)
60 | * [`useViewport`](docs/api/ObserveViewport_connectViewport_useViewport.md#hooks-useviewport-usescroll-usedimensions-useLayoutSnapshot)
61 | * [`useMutableViewport`](https://github.com/garthenweb/react-viewport-utils/blob/master/docs/api/ObserveViewport_connectViewport_useViewport.md#hooks-usemutableviewport)
62 | * [`useScroll`](docs/api/ObserveViewport_connectViewport_useViewport.md#hooks-useviewport-usescroll-usedimensions-useLayoutSnapshot)
63 | * [`useDimensions`](docs/api/ObserveViewport_connectViewport_useViewport.md#hooks-useviewport-usescroll-usedimensions-useLayoutSnapshot)
64 | * [`useLayoutSnapshot`](docs/api/ObserveViewport_connectViewport_useViewport.md#hooks-useviewport-usescroll-usedimensions-useLayoutSnapshot)
65 | * [`useViewportEffect`](docs/api/ObserveViewport_connectViewport_useViewport.md#hook-effects-useViewportEffect-useScrollEffect-useDimensionsEffect)
66 | * [`useScrollEffect`](docs/api/ObserveViewport_connectViewport_useViewport.md#hook-effects-useViewportEffect-useScrollEffect-useDimensionsEffect)
67 | * [`useDimensionsEffect`](docs/api/ObserveViewport_connectViewport_useViewport.md#hook-effects-useViewportEffect-useScrollEffect-useDimensionsEffect)
68 | * [`useRect`](docs/api/useRect.md#useRect)
69 | * [`useRectEffect`](docs/api/useRect.md#useRectEffect)
70 | * [Types](docs/api/types.md)
71 |
72 | ### Concepts
73 |
74 | * [Experimental Scheduler](docs/concepts/scheduler.md)
75 | * [recalculateLayoutBeforeUpdate](docs/concepts/recalculateLayoutBeforeUpdate.md)
76 | * [Defer Events](docs/concepts/defer_events.md)
77 |
78 | ## License
79 |
80 | Licensed under the [MIT License](https://opensource.org/licenses/mit-license.php).
81 |
--------------------------------------------------------------------------------
/bundler/parcel-transformer-replace/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "parcel-transformer-replace",
3 | "version": "1.0.0",
4 | "main": "./transformer.js",
5 | "dependencies": {
6 | "@parcel/plugin": "^2.0.0"
7 | },
8 | "engines": {
9 | "parcel": "^2.0.0"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/bundler/parcel-transformer-replace/transformer.js:
--------------------------------------------------------------------------------
1 | const { Transformer } = require('@parcel/plugin');
2 | const pkg = require('../../package.json');
3 |
4 | module.exports.default = new Transformer({
5 | async transform({ asset }) {
6 | const code = await asset.getCode();
7 | // TODO consider adding a source map. Currently, we put the variable length to the exact same size to not make the code jump, but this is a bit odd and error prone...
8 | const result = code.replace(/_VERS_/gm, pkg.version);
9 |
10 | asset.setCode(result);
11 | return [asset];
12 | },
13 | });
14 |
--------------------------------------------------------------------------------
/docs/api/ObserveViewport_connectViewport_useViewport.md:
--------------------------------------------------------------------------------
1 | # Observe the Viewport
2 |
3 | Dependent on the use case we support different ways to connect to the viewport properties. All options described in this document will expose the current `scroll` and `dimensions` information collected by a `ViewportProvider`.
4 |
5 | ## Render Props/ Event Handler: `ObserveViewport`
6 |
7 | Render props are easy to implement in the most situations but the event handler allows more control about performance and to trigger side effects.
8 |
9 | ### API
10 |
11 | | Property | Type | Required? | Description |
12 | |:---|:---|:---:|:---|
13 | | onUpdate | function | | Triggers as soon as a viewport update was detected. Contains the `Viewport` as the first argument and the last return of `recalculateLayoutBeforeUpdate` as the second argument |
14 | | recalculateLayoutBeforeUpdate | function | | Enables a way to calculate layout information for all components as a badge before the onUpdate call. Contains the `Viewport` as the first argument. See [recalculateLayoutBeforeUpdate](../concepts/recalculateLayoutBeforeUpdate.md) |
15 | | children | function | | Like `onUpdate` but expects to return that will be rendered on the page. Contains the `Viewport` as the first argument. |
16 | | deferUpdateUntilIdle | boolean | | Defers to trigger updates until the collector is idle. See [Defer Events](../concepts/defer_events.md) |
17 | | priority | `'low'`, `'normal'`, `'high'`, `'highest'` | | Allows to set a priority of the update. See [Defer Events](../concepts/scheduler.md) |
18 | | disableScrollUpdates | boolean | | Disables updates to scroll events |
19 | | disableDimensionsUpdates | boolean | | Disables updates to dimensions events |
20 |
21 | ### Example
22 |
23 | ``` javascript
24 | import * as React from 'react';
25 | import {
26 | ViewportProvider,
27 | ObserveViewport,
28 | } from 'react-viewport-uitls';
29 |
30 | const handleUpdate = ({ scroll, dimensions }) {
31 | console.log(scroll, dimensions);
32 | }
33 |
34 | render(
35 |
36 |
37 |
38 |
39 |
40 | {({ scroll }) =>
{scroll.x}
}
41 |
42 | ,
43 | document.querySelector('main')
44 | );
45 | ```
46 |
47 | ## HOC: `connectViewport`
48 |
49 | This is just a wrapper for the `ObserveViewport` to implement the HOC pattern.
50 |
51 | ### API
52 |
53 | | Property | Type | Required? | Description |
54 | |:---|:---|:---:|:---|
55 | | omit | `['scroll', 'dimensions']` | | Allows to disable scroll or dimensions events for the higher order component |
56 | | deferUpdateUntilIdle | boolean | | Defers to trigger updates until the collector is idle. See [Defer Events](../concepts/defer_events.md). |
57 | | options.priority | `'low'`, `'normal'`, `'high'`, `'highest'` | | Allows to set a priority of the update. See [Defer Events](../concepts/scheduler.md) |
58 |
59 | ## Example
60 |
61 | ``` javascript
62 | import * as React from 'react';
63 | import {
64 | ViewportProvider,
65 | connectViewport,
66 | } from 'react-viewport-utils';
67 |
68 | const Component = ({ scroll, dimensions }) => (
69 | <>
70 |
83 | ,
84 | document.querySelector('main')
85 | );
86 | ```
87 |
88 | ## Hooks: `useViewport`, `useScroll`, `useDimensions`, `useLayoutSnapshot`
89 |
90 | **!!! Hooks require a `ViewportProvider` as a parent and only work with react v16.7.0 !!!**
91 |
92 | ### API
93 |
94 | | Argument | Type | Required? | Description |
95 | |:---|:---|:---:|:---|
96 | | options.disableScrollUpdates | boolean | | Disables updates to scroll events (only for `useViewport` and `useLayoutSnapshot`) |
97 | | options.disableDimensionsUpdates | boolean | | Disables updates to dimensions events (only for `useViewport` and `useLayoutSnapshot`) |
98 | | options.deferUpdateUntilIdle | boolean | | Defers to trigger updates until the collector is idle. See [Defer Events](../concepts/defer_events.md) |
99 | | options.priority | `'low'`, `'normal'`, `'high'`, `'highest'` | | Allows to set a priority of the update. See [Defer Events](../concepts/scheduler.md) |
100 |
101 | ## Hooks: `useMutableViewport`
102 |
103 | **!!! Hooks require a `ViewportProvider` as a parent and only work with react v16.7.0 !!!**
104 |
105 | Exposes the current viewport state as a mutable and readonly object. It will not trigger updates when the value on the viewport change but allows to access the current and most up to date information at any time without any negative performance impact.
106 |
107 | ### Example
108 |
109 | ``` javascript
110 | import * as React from 'react';
111 | import { useScroll, useDimensions } from 'react-viewport-utils';
112 |
113 | function Component() {
114 | const ref = React.useRef()
115 | const mutableViewport = useMutableViewport();
116 | useRectEffect((rect) => {
117 | console.log(
118 | 'Is element above the current scroll position?',
119 | mutableViewport.scroll.y > rect.bottom
120 | )
121 | }, ref)
122 |
123 | return (
124 |
125 | );
126 | }
127 | ```
128 |
129 | ## Hook Effects: `useViewportEffect`, `useScrollEffect`, `useDimensionsEffect`
130 |
131 | Hook effects allow to trigger side effects on change without updating the component.
132 |
133 | **!!! Hooks require a `ViewportProvider` as a parent and only work with react v16.7.0 !!!**
134 |
135 | ### API
136 |
137 | | Argument | Type | Required? | Description |
138 | |:---|:---|:---:|:---|
139 | | effect | (Viewport \| Scroll \| Dimensions) => void | x | Disables updates to scroll events (only for `useViewport`) |
140 | | options.disableScrollUpdates | boolean | | Disables updates to scroll events (only for `useViewport`) |
141 | | options.disableDimensionsUpdates | boolean | | Disables updates to dimensions events (only for `useViewport`) |
142 | | options.deferUpdateUntilIdle | boolean | | Defers to trigger updates until the collector is idle. See [Defer Events](../concepts/defer_events.md) |
143 | | options.priority | `'low'`, `'normal'`, `'high'`, `'highest'` | | Allows to set a priority of the update. See [Defer Events](../concepts/scheduler.md) |
144 | | options.recalculateLayoutBeforeUpdate | function | | Enables a way to calculate layout information for all components as a badge before the effect call. Contains `Viewport`, `Scroll` or `Dimensions` as the first argument, dependent of the used hook. See [recalculateLayoutBeforeUpdate](../concepts/recalculateLayoutBeforeUpdate.md) |
145 | | deps | array | | Array with dependencies. In case a value inside the array changes, this will force an update to the effect function |
146 |
147 | ### Example
148 |
149 | ``` javascript
150 | import * as React from 'react';
151 | import { useScrollEffect, useViewportEffect } from 'react-viewport-utils';
152 |
153 | function Component() {
154 | const ref = React.useRef()
155 | useScrollEffect((scroll) => {
156 | console.log(scroll);
157 | });
158 | useViewportEffect((viewport, elementWidth) => {
159 | console.log(viewport, top);
160 | }, {
161 | recalculateLayoutBeforeUpdate: () => ref.current ? ref.current.getBoundingClientRect().width : null
162 | });
163 |
164 | return ;
165 | }
166 | ```
167 |
168 | ## Related docs
169 |
170 | * [ViewportProvider](./ViewportProvider.md)
171 | * [Types](./types.md)
172 |
--------------------------------------------------------------------------------
/docs/api/ViewportProvider.md:
--------------------------------------------------------------------------------
1 | # ViewportProvider
2 |
3 | The ViewportProvider is the heart because it collects and delegates global viewport information to connected components.
4 |
5 | All other components needs to be a child of the `ViewportProvider` to receive events.
6 |
7 | In case you are building libraries, don't worry about having more than one `ViewportProvider` within the tree. The library will detect other `ViewportProvider` and make sure that only on provider will collect and send out events.
8 |
9 | ## API
10 |
11 | | Property | Type | Required? | Description |
12 | |:---|:---|:---:|:---|
13 | | experimentalSchedulerEnabled | boolean | | If set enables the experimental scheduler which allows to make use of the `priority` props on connected components to drop frames if necessary for a smooth user experience. |
14 | | children | ReactNode | ✓ | Any react node that should be rendered. Nested in the tree can be components that connect to viewport updates |
15 |
16 | ## Example
17 |
18 | ``` javascript
19 | import * as React from 'react';
20 | import {
21 | ViewportProvider,
22 | connectViewport,
23 | } from 'react-viewport-utils';
24 |
25 | const Component = ({ scroll, dimensions }) => (
26 | <>
27 |
40 | ,
41 | document.querySelector('main')
42 | );
43 | ```
44 |
45 | ## Related docs
46 |
47 | * [Observe the Viewport](./ObserveViewport_connectViewport_useViewport.md)
48 | * [Scheduler](../concepts/scheduler.md)
49 |
--------------------------------------------------------------------------------
/docs/api/types.md:
--------------------------------------------------------------------------------
1 | # Types
2 |
3 | ## Scroll
4 |
5 | For scroll events the `scroll` object is exposed which contains the following properties.
6 |
7 | | Property | Type | Description |
8 | |:---|:---|:---|
9 | | x | number | Horizontal scroll position |
10 | | y | number | Vertical scroll position |
11 | | xTurn | number | Horizontal scroll position where the scroll dRection turned in the opposite dRection |
12 | | yTurn | number | Vertical scroll position where the scroll dRection turned in the opposite dRection |
13 | | xDTurn | number | Difference of the horizontal scroll position where the scroll dRection turned in the opposite dRection |
14 | | yDTurn | number | Difference of the vertical scroll position where the scroll dRection turned in the opposite dRection |
15 | | isScrollingUp | boolean | Whether the page is scrolling up |
16 | | isScrollingDown | boolean | Whether the page is scrolling down |
17 | | isScrollingLeft | boolean | Whether the page is scrolling left |
18 | | isScrollingRight | boolean | Whether the page is scrolling right |
19 |
20 | ## Dimensions
21 |
22 | | Property | Type | Description |
23 | |:---|:---|:---|
24 | | width | number | Inner width of the `window` |
25 | | height | number | Inner height of the `window` |
26 | | outerWidth | Outer width of the `window` |
27 | | outerHeight | Outer height of the `window` |
28 | | clientWidth | number | Width of the document element |
29 | | clientHeight | number | Height of the document element |
30 | | documentWidth | number | Complete width of the document |
31 | | documentHeight | number | Complete height of the document |
32 |
33 | ## Viewport
34 |
35 | | Property | Type | Description |
36 | |:---|:---|:---|
37 | | scroll | Scroll | See Scroll type above |
38 | | dimensions | Dimensions | See Dimensions type above |
39 |
40 |
41 | ## Rect
42 |
43 | | Property | Type | Description |
44 | |:---|:---|:---|
45 | | top | number | Top position of the element, relative to the viewport |
46 | | right | number | Right position of the element, relative to the viewport |
47 | | bottom | number | Bottom position of the element, relative to the viewport |
48 | | left | number | Left position of the element, relative to the viewport |
49 | | width | number | Width of the element |
50 | | height | number | Height of the element |
51 |
--------------------------------------------------------------------------------
/docs/api/useRect.md:
--------------------------------------------------------------------------------
1 | # Observe an element
2 |
3 | ## `useRect`
4 |
5 | Returns the rect of the elements, including it's position within the viewport as well as it's size.
6 |
7 | Please note that at the moment the rect will only update after global scroll or resize events. Changes to the element without those interactions will not be observed (e.g. if an animation is performed).
8 | In case you need full control over the element, I recommend using the [ResizeObserver DOM API](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver).
9 |
10 | **!!! Hooks require a `ViewportProvider` as a parent and only work with react v16.7.0 !!!**
11 |
12 | ### API
13 |
14 | | Argument | Type | Required? | Description |
15 | |:---|:---|:---:|:---|
16 | | ref | React.RefObject\ | x | The reference to an element that should be observed |
17 | | options.disableScrollUpdates | boolean | | Disables updates to scroll events (only for `useViewport`) |
18 | | options.disableDimensionsUpdates | boolean | | Disables updates to dimensions events (only for `useViewport`) |
19 | | options.deferUpdateUntilIdle | boolean | | Defers to trigger updates until the collector is idle. See [Defer Events](../concepts/defer_events.md) |
20 | | options.priority | `'low'`, `'normal'`, `'high'`, `'highest'` | | Allows to set a priority of the update. See [Defer Events](../concepts/scheduler.md) |
21 | | deps | array | | Array with dependencies. In case a value inside the array changes, this will force an update on the rect |
22 |
23 | ### Example
24 |
25 | ``` javascript
26 | import * as React from 'react';
27 | import { useRect } from 'react-viewport-utils';
28 |
29 | function Component() {
30 | const ref = React.useRef()
31 | const rect = useRect(ref) || {
32 | width: NaN,
33 | height: NaN,
34 | };
35 |
36 | return (
37 |
38 | Current Size is {rect.width}x{rect.width}
39 |
40 | );
41 | }
42 | ```
43 |
44 | ## `useRectEffect`
45 |
46 | Same as the `useRect` hook but as an effect, therefore it does not return anything and will not re-render the component. This should be used if a side effect should be performed.
47 |
48 | **!!! Hooks require a `ViewportProvider` as a parent and only work with react v16.7.0 !!!**
49 |
50 | ### API
51 |
52 | | Argument | Type | Required? | Description |
53 | |:---|:---|:---:|:---|
54 | | effect | (rect: Rect \| null) => void | x | The side effect that should be performed |
55 | | ref | React.RefObject\ | x | The reference to an element that should be observed |
56 | | options.disableScrollUpdates | boolean | | Disables updates to scroll events (only for `useViewport`) |
57 | | options.disableDimensionsUpdates | boolean | | Disables updates to dimensions events (only for `useViewport`) |
58 | | options.deferUpdateUntilIdle | boolean | | Defers to trigger updates until the collector is idle. See [Defer Events](../concepts/defer_events.md) |
59 | | options.priority | `'low'`, `'normal'`, `'high'`, `'highest'` | | Allows to set a priority of the update. See [Defer Events](../concepts/scheduler.md) |
60 | | deps | array | | Array with dependencies. In case a value inside the array changes, this will force an update to the effect function |
61 |
62 | ### Example
63 |
64 | ``` javascript
65 | import * as React from 'react';
66 | import { useRectEffect } from 'react-viewport-utils';
67 |
68 | function Component() {
69 | const ref = React.useRef()
70 | useRectEffect((rect) => console.log(rect), ref)
71 |
72 | return (
73 |
74 | );
75 | }
76 | ```
77 |
78 | ## Related docs
79 |
80 | * [ViewportProvider](./ViewportProvider.md)
81 | * [Types](./types.md)
82 |
--------------------------------------------------------------------------------
/docs/concepts/defer_events.md:
--------------------------------------------------------------------------------
1 | # Defer events
2 |
3 | Some updates are heavy and might reduce the user experience when scheduled simultaneously to others. Therefore its possible to defer events until idle by enabling `deferUpdateUntilIdle` (default is `false`). If enabled, the `onUpdate` callback/ the rerender of the component will be deferred until no events (independent whether `omit`, `disableDimensionsUpdates` or `disableScrollUpdates` is used) are scheduled anymore.
4 |
5 | This option is available for
6 |
7 | * ObserveViewport
8 | * connectViewport
9 | * useViewport, useScroll, useDimensions, useLayoutSnapshot
10 |
11 | ## Example
12 |
13 | ``` javascript
14 |
18 |
19 | const ConnectedComponent = connectViewport({ deferUpdateUntilIdle: true })(Component);
20 | ```
21 |
--------------------------------------------------------------------------------
/docs/concepts/recalculateLayoutBeforeUpdate.md:
--------------------------------------------------------------------------------
1 | # `recalculateLayoutBeforeUpdate`
2 |
3 | When an update is triggered, sometimes further calculations on the DOM which might trigger [layouts/ reflows](https://gist.github.com/paulirish/5d52fb081b3570c81e3a) are required to execute a task.
4 | In general the best performance is archive by first reading all the values in one badge and later update the DOM again. With multiple components in one page this can become difficult.
5 |
6 | The optional `recalculateLayoutBeforeUpdate` property, which accepts a function, will allow to exactly handle those reads in one badge for all components to later perform the update:
7 |
8 | * first all `recalculateLayoutBeforeUpdate` functions for all components are executed.
9 | * second all `onUpdate` function are called which receive the value returned from `recalculateLayoutBeforeUpdate` as the second argument.
10 |
11 | This option is available for
12 |
13 | * ObserveViewport
14 | * useLayoutSnapshot
15 |
16 | ## Example
17 |
18 | ``` javascript
19 | el.getBoundingClientRect()}
21 | onUpdate={({ scroll }, rect) => console.log('Top offset: ', scroll.y + rect.top))}
22 | />
23 |
24 | const Component = () => {
25 | const offsetTop = useLayoutSnapshot(
26 | ({ scroll }) => scroll.y + el.getBoundingClientRect().top
27 | );
28 | }
29 | ```
30 |
--------------------------------------------------------------------------------
/docs/concepts/scheduler.md:
--------------------------------------------------------------------------------
1 | ### Experimental Scheduler
2 |
3 | Some updates on the page have higher priority than others, e.g. an animation of a visible element is more important for a good ux than a tracking event that fires after a certain scroll position. For sure, the tracking event must fire at some point in time but it is not important that it fires immediately.
4 |
5 | The scheduler is able to handle those differences. By default it measures the amount of time an update needs in average and might drop some frames in favor of others. To tell which Observers are more important than others it allows to set 4 different levels: `highest`, `high`, `normal` and `low`.
6 |
7 | The scheduler learns over time based on how fast updates are executed, therefore the amount of events called depend heavily on the platform. On a low end device it will fire way less events than on a high end device.
8 |
9 | When the scheduler is disabled, all observers have priority `highest` as default and will therefore never drop frames. Default when enabled is `normal`.
10 |
11 | Its always guaranteed that the observer fires at some point in time with the recent updates but it might drop some frames in between if `priority` is not set to `highest`.
12 |
13 | The scheduler is for now disabled by default and needs to be activated on the `ViewportProvider`.
14 |
15 | **!!! This is an experimental API and its implementation might change in the future !!!**
16 |
17 | ``` javascript
18 | const handleUpdate = ({ scroll, dimensions }: Viewport) {
19 | console.log(scroll, dimensions);
20 | }
21 |
22 | render(
23 |
24 |
25 |
26 | ,
27 | document.querySelector('main')
28 | );
29 | ```
30 |
31 | The priority of an observer can be updated at runtime which allows to update priority e.g. for elements that are not visible at the moment.
32 |
--------------------------------------------------------------------------------
/examples/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Example
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/examples/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { render } from 'react-dom';
3 |
4 | import {
5 | ViewportProvider,
6 | ObserveViewport,
7 | connectViewport,
8 | useScroll,
9 | useLayoutSnapshot,
10 | useDimensions,
11 | useRect,
12 | useScrollEffect,
13 | useDimensionsEffect,
14 | useViewportEffect,
15 | useRectEffect,
16 | useMutableViewport,
17 | } from '../lib/index';
18 |
19 | import './styles.css';
20 |
21 | const Placeholder = () => ;
22 | const ViewportHeader = connectViewport({ omit: ['scroll'] })<{ a: string }>(
23 | ({ dimensions, a }) => (
24 |
25 | Viewport: {dimensions.width}x{dimensions.height}
26 | {a}
27 |
28 |
29 | ),
30 | );
31 |
32 | const DisplayViewport = React.memo(() => {
33 | const div = React.useRef(null);
34 | const { x, y } = useScroll({
35 | priority: 'low',
36 | });
37 | const { documentHeight, clientWidth } = useDimensions({
38 | priority: 'low',
39 | });
40 | const rect = useRect(div);
41 | return (
42 |