diff --git a/README.md b/README.md index 8d3ec6b..f554e91 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ -React Visibility Sensor -==== +# React Visibility Sensor [![Build Status](https://secure.travis-ci.org/joshwnj/react-visibility-sensor.png)](http://travis-ci.org/joshwnj/react-visibility-sensor) @@ -7,8 +6,7 @@ Sensor component for React that notifies you when it goes in or out of the windo Sponsored by [X-Team](https://x-team.com) -Install ----- +## Install `npm install react-visibility-sensor` @@ -23,8 +21,7 @@ In this case, make sure that `React` and `ReactDOM` are already loaded and globa Take a look at the [umd example](./example-umd/) to see this in action -Example ----- +## Example [View an example on codesandbox](https://codesandbox.io/s/p73kyx9zpm) @@ -40,13 +37,13 @@ To run the example locally: General usage goes something like: ```js -const VisibilitySensor = require('react-visibility-sensor'); +const VisibilitySensor = require("react-visibility-sensor"); -function onChange (isVisible) { - console.log('Element is now %s', isVisible ? 'visible' : 'hidden'); +function onChange(isVisible) { + console.log("Element is now %s", isVisible ? "visible" : "hidden"); } -function MyComponent (props) { +function MyComponent(props) { return (
...content goes here...
@@ -58,24 +55,22 @@ function MyComponent (props) { You can also pass a child function, which can be convenient if you don't need to store the visibility anywhere: ```js -function MyComponent (props) { +function MyComponent(props) { return ( - {({isVisible}) => -
I am {isVisible ? 'visible' : 'invisible'}
- } + {({ isVisible }) =>
I am {isVisible ? "visible" : "invisible"}
}
); } ``` -Props ----- +## Props - `onChange`: callback for whenever the element changes from being within the window viewport or not. Function is called with 1 argument `(isVisible: boolean)` -- `active`: (default `true`) boolean flag for enabling / disabling the sensor. When `active !== true` the sensor will not fire the `onChange` callback. +- `active`: (default `true`) boolean flag for enabling / disabling the sensor. When `active !== true` the sensor will not fire the `onChange` callback. - `partialVisibility`: (default `false`) consider element visible if only part of it is visible. Also possible values are - 'top', 'right', 'bottom', 'left' - in case it's needed to detect when one of these become visible explicitly. - `offset`: (default `{}`) with offset you can define amount of px from one side when the visibility should already change. So in example setting `offset={{top:10}}` means that the visibility changes hidden when there is less than 10px to top of the viewport. Offset works along with `partialVisibility` +- `minTopPercent`: (default `0.0`) consider element visible if a minimum percentage is visible, so if at least 0.2 (20%) is in the viewport, we mark the element as visible. - `minTopValue`: (default `0`) consider element visible if only part of it is visible and a minimum amount of pixels could be set, so if at least 100px are in viewport, we mark element as visible. - `intervalCheck`: (default `true`) when this is true, it gives you the possibility to check if the element is in view even if it wasn't because of a user scroll - `intervalDelay`: (default `100`) integer, number of milliseconds between checking the element's position in relation the the window viewport. Making this number too low will have a negative impact on performance. @@ -87,16 +82,14 @@ Props - `resizeThrottle`: (default: `-1`) by specifying a value > -1, you are enabling throttle instead of the delay to trigger checks on resize event. Throttle supercedes delay. - `containment`: (optional) element to use as a viewport when checking visibility. Default behaviour is to use the browser window as viewport. - `delayedCall`: (default `false`) if is set to true, wont execute on page load ( prevents react apps triggering elements as visible before styles are loaded ) -- `children`: can be a React element or a function. If you provide a function, it will be called with 1 argument `{isVisible: ?boolean, visibilityRect: Object}` +- `children`: can be a React element or a function. If you provide a function, it will be called with 1 argument `{isVisible: ?boolean, visibilityRect: Object}` It's possible to use both `intervalCheck` and `scrollCheck` together. This means you can detect most visibility changes quickly with `scrollCheck`, and an `intervalCheck` with a higher `intervalDelay` will act as a fallback for other visibility events, such as resize of a container. -Thanks ----- +## Thanks Special thanks to [contributors](https://github.com/joshwnj/react-visibility-sensor/graphs/contributors) -License ----- +## License MIT diff --git a/package.json b/package.json index c847473..4c05cf6 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ } }, "lint-staged": { - "*.{js,json,css,md}": [ + "*.{js,json,jsx,css,md}": [ "prettier --write", "git add" ] diff --git a/tests/visibility-sensor-spec.jsx b/tests/visibility-sensor-spec.jsx index 742dad1..370a279 100644 --- a/tests/visibility-sensor-spec.jsx +++ b/tests/visibility-sensor-spec.jsx @@ -16,9 +16,7 @@ describe("VisibilitySensor", function() { document.body.removeChild(node); }); - it("should notify of changes to visibility when parent moves", function( - done - ) { + it("should notify of changes to visibility when parent moves", function(done) { var firstTime = true; var onChange = function(isVisible) { // by default we expect the sensor to be visible @@ -49,9 +47,7 @@ describe("VisibilitySensor", function() { ReactDOM.render(element, node); }); - it("should notify of changes to visibility when user scrolls", function( - done - ) { + it("should notify of changes to visibility when user scrolls", function(done) { var firstTime = true; var onChange = function(isVisible) { // by default we expect the sensor to be visible @@ -198,9 +194,7 @@ describe("VisibilitySensor", function() { assert(!component2.debounceCheck, "debounceCheck should not be set"); }); - it("should work when using offset prop and moving to outside of offset area", function( - done - ) { + it("should work when using offset prop and moving to outside of offset area", function(done) { var firstTime = true; node.setAttribute("style", "position:absolute; top:51px"); var onChange = function(isVisible) { @@ -262,9 +256,7 @@ describe("VisibilitySensor", function() { ReactDOM.render(element, node); }); - it("should work when using offset prop and moving to inside of offset area", function( - done - ) { + it("should work when using offset prop and moving to inside of offset area", function(done) { var firstTime = true; node.setAttribute("style", "position:absolute; top:49px"); var onChange = function(isVisible) { @@ -295,9 +287,7 @@ describe("VisibilitySensor", function() { ReactDOM.render(element, node); }); - it("should work when using negative offset prop and moving to outside of viewport", function( - done - ) { + it("should work when using negative offset prop and moving to outside of viewport", function(done) { var firstTime = true; node.setAttribute("style", "position:absolute; top:-49px"); var onChange = function(isVisible) { @@ -390,4 +380,172 @@ describe("VisibilitySensor", function() { ReactDOM.render(element, node); }); + + it("should return visible if a minimum top is visible", function(done) { + var firstTime = true; + var onChange = function(isVisible) { + if (firstTime) { + assert.equal(isVisible, true, "Component is visible"); + done(); + } + }; + + const style = { + position: "absolute", + top: "100%", + marginTop: "-8px", + width: "10px", + height: "10px" + }; + + var element = ( + +
+ + ); + + ReactDOM.render(element, node); + }); + + it("should not return visible if a minimum top is not visible", function(done) { + var firstTime = true; + var onChange = function(isVisible) { + if (firstTime) { + assert.equal(isVisible, false, "Component is not visible"); + done(); + } + }; + + const style = { + position: "absolute", + top: "100%", + marginTop: "-7px", + width: "10px", + height: "10px" + }; + + var element = ( + +
+ + ); + + ReactDOM.render(element, node); + }); + + it("should not return visible if a minimum top value is set and the component is rendered off the top of the page", function(done) { + var firstTime = true; + var onChange = function(isVisible) { + if (firstTime) { + assert.equal(isVisible, false, "Component is not visible"); + done(); + } + }; + + const style = { + position: "absolute", + top: "0", + marginTop: "-11px", + width: "10px", + height: "10px" + }; + + var element = ( + +
+ + ); + + ReactDOM.render(element, node); + }); + + it("should return visible if a minimum percentage is visible", function(done) { + var firstTime = true; + var onChange = function(isVisible) { + if (firstTime) { + assert.equal(isVisible, true, "Component is visible"); + done(); + } + }; + + const style = { + position: "absolute", + top: "100%", + marginTop: "-8px", + width: "10px", + height: "10px" + }; + + var element = ( + +
+ + ); + + ReactDOM.render(element, node); + }); + + it("should not return visible if a minimum percentage is not visible", function(done) { + var firstTime = true; + var onChange = function(isVisible) { + if (firstTime) { + assert.equal(isVisible, false, "Component is not visible"); + done(); + } + }; + + const style = { + position: "absolute", + top: "100%", + marginTop: "-7px", + width: "10px", + height: "10px" + }; + + var element = ( + +
+ + ); + + ReactDOM.render(element, node); + }); + + it("should not return visible if a minimum percentage is set and the component is rendered off the top of the page", function(done) { + var firstTime = true; + var onChange = function(isVisible) { + if (firstTime) { + assert.equal(isVisible, false, "Component is not visible"); + done(); + } + }; + + const style = { + position: "absolute", + top: "0", + marginTop: "-11px", + width: "10px", + height: "10px" + }; + + var element = ( + +
+ + ); + + ReactDOM.render(element, node); + }); }); diff --git a/visibility-sensor.js b/visibility-sensor.js index 067b3c4..71d5924 100644 --- a/visibility-sensor.js +++ b/visibility-sensor.js @@ -21,6 +21,7 @@ export default class VisibilitySensor extends React.Component { static defaultProps = { active: true, partialVisibility: false, + minTopPercent: 0, minTopValue: 0, scrollCheck: false, scrollDelay: 250, @@ -70,6 +71,7 @@ export default class VisibilitySensor extends React.Component { ? PropTypes.instanceOf(window.Element) : PropTypes.any, children: PropTypes.oneOfType([PropTypes.element, PropTypes.func]), + minTopPercent: PropTypes.number, minTopValue: PropTypes.number }; @@ -290,10 +292,18 @@ export default class VisibilitySensor extends React.Component { // if we have minimum top visibility set by props, lets check, if it meets the passed value // so if for instance element is at least 200px in viewport, then show it. - isVisible = this.props.minTopValue - ? partialVisible && - rect.top <= containmentRect.bottom - this.props.minTopValue - : partialVisible; + if (this.props.minTopValue) { + isVisible = + partialVisible && + rect.top <= containmentRect.bottom - this.props.minTopValue; + } else if (this.props.minTopPercent) { + const height = rect.bottom - rect.top; + const minTopValue = height * this.props.minTopPercent; + isVisible = + partialVisible && rect.top <= containmentRect.bottom - minTopValue; + } else { + isVisible = partialVisible; + } } // Deprecated options for calculating offset.