react-scroll-percentage
Advanced tools
Comparing version 3.1.0 to 4.0.0
108
package.json
{ | ||
"name": "react-scroll-percentage", | ||
"version": "3.1.0", | ||
"version": "4.0.0", | ||
"description": "Monitor the scroll percentage of a component inside the viewport, using the IntersectionObserver API.", | ||
@@ -9,6 +9,2 @@ "main": "dist/react-scroll-percentage.cjs.js", | ||
"unpkg": "dist/react-scroll-percentage.umd.min.js", | ||
"files": [ | ||
"dist/*", | ||
"src/*" | ||
], | ||
"author": "Daniel Schmidt", | ||
@@ -28,103 +24,9 @@ "repository": { | ||
], | ||
"scripts": { | ||
"build": "rm -rf dist && npm run build:lib && npm run build:flow", | ||
"build:lib": "run-p rollup:*", | ||
"build:storybook": "build-storybook --output-dir example", | ||
"build:flow": "node scripts/create-flow", | ||
"dev": "concurrently -k -r 'jest --watch' 'npm run storybook'", | ||
"lint": "eslint {src,stories,tests}/.", | ||
"rollup:es": "rollup -c --environment FORMAT:es", | ||
"rollup:cjs": "rollup -c --environment FORMAT:cjs", | ||
"rollup:umd": "rollup -c --environment FORMAT:umd", | ||
"rollup:umd.min": "rollup -c --environment MINIFY,FORMAT:umd", | ||
"prepare": "npm run build", | ||
"pretty": "prettier '**/*.{js,json,md,ts}' --write", | ||
"storybook": "start-storybook -p 9000", | ||
"test": "jest" | ||
}, | ||
"husky": { | ||
"hooks": { | ||
"pre-commit": "flow status && lint-staged", | ||
"post-commit": "git update-index --again" | ||
} | ||
}, | ||
"lint-staged": { | ||
"*.{js,json,css,md,ts}": [ | ||
"prettier --write", | ||
"git add" | ||
], | ||
"src/**/*.js": [ | ||
"eslint", | ||
"jest --findRelatedTests", | ||
"flow force-recheck --focus" | ||
] | ||
}, | ||
"eslintConfig": { | ||
"extends": [ | ||
"insilico" | ||
] | ||
}, | ||
"jest": { | ||
"testEnvironment": "jsdom", | ||
"snapshotSerializers": [ | ||
"enzyme-to-json/serializer" | ||
], | ||
"setupFiles": [ | ||
"<rootDir>/scripts/jest-setup.js" | ||
] | ||
}, | ||
"dependencies": { | ||
"invariant": "^2.2.4", | ||
"react-intersection-observer": "^6.4.0" | ||
"@babel/runtime": "^7.4.5", | ||
"react-intersection-observer": "^8.23.0" | ||
}, | ||
"peerDependencies": { | ||
"react": "^15.0.0 || ^16.0.0" | ||
}, | ||
"devDependencies": { | ||
"@babel/cli": "^7.2.3", | ||
"@babel/core": "^7.2.2", | ||
"@babel/plugin-external-helpers": "^7.0.0", | ||
"@babel/plugin-proposal-class-properties": "^7.2.3", | ||
"@babel/plugin-proposal-decorators": "^7.2.3", | ||
"@babel/plugin-proposal-export-namespace-from": "^7.0.0", | ||
"@babel/plugin-proposal-function-sent": "^7.1.0", | ||
"@babel/plugin-proposal-json-strings": "^7.0.0", | ||
"@babel/plugin-proposal-numeric-separator": "^7.0.0", | ||
"@babel/plugin-proposal-throw-expressions": "^7.0.0", | ||
"@babel/plugin-syntax-dynamic-import": "^7.0.0", | ||
"@babel/plugin-syntax-import-meta": "^7.0.0", | ||
"@babel/preset-env": "^7.2.3", | ||
"@babel/preset-flow": "^7.0.0", | ||
"@babel/preset-react": "^7.0.0", | ||
"@babel/runtime": "^7.0.0", | ||
"@storybook/addon-actions": "^4.1.4", | ||
"@storybook/addon-options": "^4.1.4", | ||
"@storybook/react": "^4.1.4", | ||
"babel-core": "^7.0.0-bridge.0", | ||
"babel-jest": "^23.6.0", | ||
"babel-loader": "^8.0.5", | ||
"concurrently": "^4.0.1", | ||
"enzyme": "^3.8.0", | ||
"enzyme-adapter-react-16": "^1.7.1", | ||
"enzyme-to-json": "^3.3.4", | ||
"eslint": "^5.12.0", | ||
"eslint-config-insilico": "^6.0.0", | ||
"flow-bin": "^0.89.0", | ||
"flow-copy-source": "^2.0.2", | ||
"husky": "^1.3.1", | ||
"intersection-observer": "^0.5.0", | ||
"jest": "^23.6.0", | ||
"lint-staged": "^8.1.0", | ||
"npm-run-all": "^4.1.3", | ||
"prettier": "^1.14.3", | ||
"react": "^16.7.0", | ||
"react-dom": "^16.7.0", | ||
"react-test-renderer": "^16.7.0", | ||
"rollup": "^1.0.2", | ||
"rollup-plugin-babel": "^4.2.0", | ||
"rollup-plugin-commonjs": "^9.1.8", | ||
"rollup-plugin-node-resolve": "^4.0.0", | ||
"rollup-plugin-replace": "^2.0.0", | ||
"rollup-plugin-uglify": "^6.0.0" | ||
"react": "^15.0.0 || ^16.0.0 || ^17.0.0" | ||
} | ||
} | ||
} |
249
README.md
# react-scroll-percentage | ||
[![Greenkeeper badge](https://badges.greenkeeper.io/thebuilder/react-scroll-percentage.svg)](https://greenkeeper.io/) | ||
[![Travis](https://travis-ci.org/thebuilder/react-scroll-percentage.svg?branch=master)](https://travis-ci.org/thebuilder/react-scroll-percentage) | ||
[![styled with prettier](https://img.shields.io/badge/styled_with-prettier-ff69b4.svg)](https://github.com/prettier/prettier) | ||
[![npm](https://img.shields.io/npm/v/react-scroll-percentage.svg)](https://www.npmjs.com/package/react-scroll-percentage) | ||
[![Version Badge][npm-version-svg]][package-url] | ||
[![GZipped size][npm-minzip-svg]][bundlephobia-url] | ||
[![Build Status][travis-svg]][travis-url] | ||
[![Coverage Statu][coveralls-svg]][coveralls-url] | ||
[![dependency status][deps-svg]][deps-url] | ||
[![dev dependency status][dev-deps-svg]][dev-deps-url] | ||
[![License][license-image]][license-url] | ||
[![Downloads][downloads-image]][downloads-url] | ||
[![Greenkeeper badge][greenkeeper-svg]][greenkeeper-url] | ||
[![styled with prettier][prettier-svg]][prettier-url] | ||
React component that reports the current scroll percentage of a element inside | ||
the viewport. It uses [React Intersection | ||
Observer](https://github.com/thebuilder/react-intersection-observer) to only | ||
report the percentage when the element is inside the viewport. | ||
the viewport. Contains both a [Hooks](#hooks), [render props](#render-props) and | ||
[plain children](#plain-children) implementation. | ||
```js | ||
import ScrollPercentage from 'react-scroll-percentage' | ||
;<ScrollPercentage> | ||
{percentage => ( | ||
<h2>{`Percentage scrolled: ${percentage.toPrecision(2)}%.`}</h2> | ||
)} | ||
</ScrollPercentage> | ||
``` | ||
## Features | ||
## Demo | ||
- 🎣 **Hooks or Component API** - With `useCrollPercentage` it's easier than | ||
ever to monitor elements | ||
- ⚡️ **Optimized performance** - Uses | ||
[React Intersection Observer](https://github.com/thebuilder/react-intersection-observer) | ||
to only update when elements are inside the viewport | ||
- 🌳 **Tree-shakeable** - Only include the parts you use | ||
See https://thebuilder.github.io/react-scroll-percentage/ for a demo. | ||
## Installation | ||
@@ -40,58 +41,133 @@ | ||
## Props | ||
> ⚠️ You also want to add the | ||
> [intersection-observer](https://www.npmjs.com/package/react-scroll-percentage) | ||
> polyfill for full browser support. Check out adding the [polyfill](#polyfill) | ||
> for details about how you can include it. | ||
The **`<ScrollPercentage />`** accepts the following props: | ||
## Usage | ||
| Name | Type | Default | Required | Description | | ||
| --------- | ----------------------------------------------- | ------- | -------- | ----------------------------------------------------------------------------------------- | | ||
| tag | Node | 'div' | true | Element tag to use for the wrapping component | | ||
| children | ((percentage: number, inView: boolean) => Node) | | true | Children should be either a function or a node | | ||
| threshold | Number | 0 | false | Number between 0 and 1 indicating the percentage that should be visible before triggering | | ||
| onChange | (percentage: number, inView: boolean) => void | | false | Call this function whenever the in view state changes | | ||
| innerRef | (element: ?HTMLElement) => void | | false | Get a reference to the inner DOM node | | ||
### Hooks 🎣 | ||
## Example code | ||
#### `useScrollPercentage` | ||
### Render prop | ||
```js | ||
const [ref, percentage] = useScrollPercentage(options) | ||
``` | ||
The basic usage pass a function as the child. It will be called whenever the | ||
state changes, with the current value of `percentage` and `inView`. | ||
Call the `useScrollPercentage` hook, with the (optional) [options](#options) you | ||
need. It will return an array containing a `ref`, the current scroll | ||
`percentage` and the current | ||
[`IntersectionObserverEntry`](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry). | ||
Assign the `ref` to the DOM element you want to monitor, and the hook will | ||
report the status. | ||
> Note that <ScrollPercentage> will still render a wrapping element (default is a `<div>`). | ||
> You can change to element by setting `tag`, and any excess props like `className` will be passed to the element | ||
```jsx | ||
import React from 'react' | ||
import { useScrollPercentage } from 'react-scroll-percentage' | ||
```js | ||
import ScrollPercentage from 'react-scroll-percentage' | ||
;<ScrollPercentage> | ||
{(percentage, inView) => ( | ||
<h2>{`Percentage scrolled: ${percentage.toPrecision(2)}%.`}</h2> | ||
)} | ||
</ScrollPercentage> | ||
const Component = () => { | ||
const [ref, percentage] = useScrollPercentage({ | ||
/* Optional options */ | ||
threshold: 0, | ||
}) | ||
return ( | ||
<div ref={ref}> | ||
<h2>{`Percentage scrolled: ${percentage.toPrecision(2)}%.`}</h2> | ||
</div> | ||
) | ||
} | ||
``` | ||
### OnChange callback | ||
### Render props | ||
You can monitor the onChange method, and control the state in your own | ||
component. The child node will always be rendered. | ||
To use the `<ScrollPercentage>` component, you pass it a function. It will be | ||
called whenever the user scrolls the viewport, with the new value of | ||
`percentage`. In addition to the `percentage`, children also receives a `ref` | ||
that should be set on the containing DOM element. | ||
```js | ||
import ScrollPercentage from 'react-scroll-percentage' | ||
;<ScrollPercentage | ||
onChange={(percentage, inView) => console.log(percentage, inView)} | ||
> | ||
<h2>Plain children are always rendered. Use onChange to monitor state.</h2> | ||
</ScrollPercentage> | ||
If you need it, you can also access the | ||
[`IntersectionObserverEntry`](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry) | ||
on `entry`, giving you access to all the details about the current intersection | ||
state. | ||
```jsx | ||
import { ScrollPercentage } from 'react-scroll-percentage' | ||
const Component = () => ( | ||
<ScrollPercentage> | ||
{({ percentage, ref, entry }) => ( | ||
<div ref={ref}> | ||
<h2>{`Percentage scrolled: ${percentage.toPrecision(2)}%.`}</h2> | ||
</div> | ||
)} | ||
</ScrollPercentage> | ||
) | ||
export default Component | ||
``` | ||
## Polyfills | ||
### Plain children | ||
### Intersection Observer | ||
You can pass any element to the `<ScrollPercentage />`, and it will handle | ||
creating the wrapping DOM element. Add a handler to the `onChange` method, and | ||
control the state in your own component. Any extra props you add the | ||
`<ScrollPercentage />` will be passed to the HTML element, allowing you set the | ||
`className`, `style`, etc. | ||
```jsx | ||
import { ScrollPercentage } from 'react-scroll-percentage' | ||
const Component = () => ( | ||
<ScrollPercentage | ||
as="div" | ||
onChange={(percentage, entry) => console.log('Percentage:', percentage)} | ||
> | ||
<h2>Plain children are always rendered. Use onChange to monitor state.</h2> | ||
</ScrollPercentage> | ||
) | ||
export default Component | ||
``` | ||
> ⚠️ When rendering a plain child, make sure you keep your HTML output semantic. | ||
> Change the `as` to match the context, and add a `className` to style the | ||
> `<ScrollPercentage />`. The component does not support Ref Forwarding, so if | ||
> you need a `ref` to the HTML element, use the Render Props version instead. | ||
### Options | ||
Provide these as props on the **`<ScrollPercentage />`** component and as the | ||
options argument for the hooks. | ||
| Name | Type | Default | Required | Description | | ||
| --------------- | ------- | ------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | | ||
| **root** | Element | window | false | The Element that is used as the viewport for checking visibility of the target. Defaults to the browser viewport (`window`) if not specified or if null. | | ||
| **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). | | ||
| **threshold** | number | 0 | false | Number between 0 and 1 indicating the percentage that should be visible before triggering. | | ||
| **triggerOnce** | boolean | false | false | Only trigger this method once | | ||
### ScrollPercentage Props | ||
The **`<ScrollPercentage />`** component also accepts the following props: | ||
| Name | Type | Default | Required | Description | | ||
| ------------ | ------------------------------------------------------------ | ------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ||
| **as** | `string` | 'div' | false | Render the wrapping element as this element. Defaults to `div`. | | ||
| **children** | `({ref, percentage, entry}) => React.ReactNode`, `ReactNode` | | true | Children expects a function that receives an object containing the `percentage` 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. | | ||
| **onChange** | `(percentage, entry) => void` | | false | Call this function whenever the in view state changes. It will receive the `percentage` value, alongside the current `IntersectionObserverEntry`. | | ||
## Intersection Observer | ||
[Intersection Observer](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API) | ||
is the API is used to determine if an element is inside the viewport or not. Browser support is pretty good, but Safari is still missing support. | ||
is the API is used to determine if an element is inside the viewport or not. | ||
[Browser support is pretty good](http://caniuse.com/#feat=intersectionobserver) - | ||
With | ||
[Safari adding support in 12.1](https://webkit.org/blog/8718/new-webkit-features-in-safari-12-1/), | ||
all major browsers now support Intersection Observers natively. | ||
> [Can i use intersectionobserver?](https://caniuse.com/#feat=intersectionobserver) | ||
### Polyfill | ||
You can import the | ||
[polyfill](https://www.npmjs.com/package/react-intersection-observer) directly or use | ||
[polyfill](https://www.npmjs.com/package/intersection-observer) directly or use | ||
a service like [polyfill.io](https://polyfill.io/v2/docs/) to add it when | ||
@@ -110,37 +186,42 @@ needed. | ||
If you are using Webpack (or similar) you could use [dynamic | ||
imports](https://webpack.js.org/api/module-methods/#import-), to load the | ||
Polyfill only if needed. A basic implementation could look something like this: | ||
If you are using Webpack (or similar) you could use | ||
[dynamic imports](https://webpack.js.org/api/module-methods/#import-), to load | ||
the Polyfill only if needed. A basic implementation could look something like | ||
this: | ||
```js | ||
loadPolyfills() | ||
.then(() => /* Render React application now that your Polyfills are ready */) | ||
/** | ||
* Do feature detection, to figure out which polyfills needs to be imported. | ||
**/ | ||
function loadPolyfills() { | ||
const polyfills = [] | ||
if (!supportsIntersectionObserver()) { | ||
polyfills.push(import('intersection-observer')) | ||
* Do feature detection, to figure out which polyfills needs to be imported. | ||
**/ | ||
async function loadPolyfills() { | ||
if (typeof window.IntersectionObserver === 'undefined') { | ||
await import('intersection-observer') | ||
} | ||
return Promise.all(polyfills) | ||
} | ||
function supportsIntersectionObserver() { | ||
return ( | ||
'IntersectionObserver' in global && | ||
'IntersectionObserverEntry' in global && | ||
'intersectionRatio' in IntersectionObserverEntry.prototype | ||
) | ||
} | ||
``` | ||
### requestAnimationFrame | ||
To optimize scroll updates, | ||
[requestAnimationFrame](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame) | ||
is used. Make sure your target browsers support it, or include the required | ||
polyfill. | ||
[package-url]: https://npmjs.org/package/react-scroll-percentage | ||
[npm-version-svg]: https://img.shields.io/npm/v/react-scroll-percentage.svg | ||
[npm-minzip-svg]: https://img.shields.io/bundlephobia/minzip/react.svg | ||
[bundlephobia-url]: https://bundlephobia.com/result?p=react-scroll-percentage | ||
[travis-svg]: https://travis-ci.org/thebuilder/react-scroll-percentage.svg | ||
[travis-url]: https://travis-ci.org/thebuilder/react-scroll-percentage | ||
[coveralls-svg]: | ||
https://coveralls.io/repos/github/thebuilder/react-scroll-percentage/badge.svg?branch=master | ||
[coveralls-url]: | ||
https://coveralls.io/github/thebuilder/react-scroll-percentage?branch=master | ||
[deps-svg]: https://david-dm.org/thebuilder/react-scroll-percentage.svg | ||
[deps-url]: https://david-dm.org/thebuilder/react-scroll-percentage | ||
[dev-deps-svg]: | ||
https://david-dm.org/thebuilder/react-scroll-percentage/dev-status.svg | ||
[dev-deps-url]: | ||
https://david-dm.org/thebuilder/react-scroll-percentage#info=devDependencies | ||
[license-image]: http://img.shields.io/npm/l/react-scroll-percentage.svg | ||
[license-url]: LICENSE | ||
[downloads-image]: http://img.shields.io/npm/dm/react-scroll-percentage.svg | ||
[downloads-url]: http://npm-stat.com/charts.html?package=react-scroll-percentage | ||
[greenkeeper-svg]: | ||
https://badges.greenkeeper.io/thebuilder/react-scroll-percentage.svg | ||
[greenkeeper-url]: https://greenkeeper.io/ | ||
[prettier-svg]: https://img.shields.io/badge/styled_with-prettier-ff69b4.svg | ||
[prettier-url]: https://github.com/prettier/prettier |
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
67751
0
226
0
483
1
+ Added@babel/runtime@^7.4.5
+ Addedreact@17.0.2(transitive)
+ Addedreact-intersection-observer@8.34.0(transitive)
- Removedinvariant@^2.2.4
- Removedinvariant@2.2.4(transitive)
- Removedprop-types@15.8.1(transitive)
- Removedreact@16.14.0(transitive)
- Removedreact-intersection-observer@6.4.2(transitive)
- Removedreact-is@16.13.1(transitive)