diff --git a/README.md b/README.md
index 8d3ec6b..dfba7d4 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,4 @@
-React Visibility Sensor
-====
+# React Visibility Sensor
[](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...
@@ -55,25 +52,43 @@ function MyComponent (props) {
}
```
+### Child Function syntax
+
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, visibilityRect, getRef }) => (
+ I am {isVisible ? "visible" : "invisible"}
+ )}
);
}
```
-Props
-----
+The child function must return an element. The 3 arguments that you can access from the child function are:
+
+- `isVisible`: Boolean
+- `getRef`: a Function allowing you to specify which element should be used for calculating visibility. This is useful when you have something absolutely-positioned. Using `getRef` is optional, and if it is not called a wrapper `` will be created with the ref.
+- `visibilityRect`: an Object indicating which sides of the element are visible, in the shape of:
+
+```
+{
+ top: Boolean,
+ bottom: Boolean,
+ left: Boolean,
+ right: Boolean
+}
+```
+
+---
+
+## 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`
- `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.
@@ -87,16 +102,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/example/main.js b/example/main.js
index 7be0055..6405821 100644
--- a/example/main.js
+++ b/example/main.js
@@ -34,7 +34,7 @@ class Example extends React.Component {
partialVisibility={this.props.partialVisibility}
offset={this.props.offset}
>
-
+ {({ getRef }) =>
}
diff --git a/tests/visibility-sensor-spec.jsx b/tests/visibility-sensor-spec.jsx
index 742dad1..6a1d9b0 100644
--- a/tests/visibility-sensor-spec.jsx
+++ b/tests/visibility-sensor-spec.jsx
@@ -42,7 +42,7 @@ describe("VisibilitySensor", function() {
var element = (
-
+ { ({ getRef }) => }
);
@@ -78,9 +78,9 @@ describe("VisibilitySensor", function() {
scrollDelay={10}
onChange={onChange}
intervalCheck={false}
- >
+ >{ () => (
-
+ )}
);
@@ -119,7 +119,7 @@ describe("VisibilitySensor", function() {
function getElement(style) {
return (
-
+ { () => }
);
}
@@ -135,7 +135,7 @@ describe("VisibilitySensor", function() {
var element = (
-
+ { () => }
);
@@ -224,7 +224,7 @@ describe("VisibilitySensor", function() {
offset={{ top: 50 }}
intervalDelay={10}
>
-
+ { ({ getRef }) => }
);
@@ -255,7 +255,7 @@ describe("VisibilitySensor", function() {
offset={{ direction: "top", value: 50 }}
intervalDelay={10}
>
-
+ { () => }
);
@@ -288,7 +288,7 @@ describe("VisibilitySensor", function() {
offset={{ top: 50 }}
intervalDelay={10}
>
-
+ { () => }
);
@@ -321,7 +321,7 @@ describe("VisibilitySensor", function() {
offset={{ top: -50 }}
intervalDelay={10}
>
-
+ { () => }
);
@@ -340,6 +340,10 @@ describe("VisibilitySensor", function() {
"visibilityRect" in props,
"children should be called with visibilityRect prop"
);
+ assert(
+ "getRef" in props,
+ "children should be called with getRef prop"
+ );
return ;
};
@@ -364,7 +368,7 @@ describe("VisibilitySensor", function() {
var element = (
-
+ { () => }
);
@@ -383,7 +387,7 @@ describe("VisibilitySensor", function() {
var element = (
);
@@ -391,3 +395,86 @@ describe("VisibilitySensor", function() {
ReactDOM.render(element, node);
});
});
+
+
+describe("VisibilitySensor with Container", function() {
+ var node;
+ var container;
+
+ beforeEach(function() {
+ node = document.createElement("div");
+ document.body.appendChild(node);
+
+ container = document.createElement('div');
+ document.body.appendChild(container);
+ });
+
+ afterEach(function() {
+ ReactDOM.unmountComponentAtNode(node);
+ document.body.removeChild(node);
+ document.body.removeChild(container);
+ });
+
+
+ it("should detect an absolutely positioned element inside the visible part of a container", function(done) {
+ var firstTime = true;
+ var onChange = function(isVisible) {
+ if (firstTime) {
+ assert.equal(isVisible, true, "Component is visible");
+ done();
+ }
+ };
+
+ container.style.width = 300
+ container.style.height = 300
+ container.style.position = 'relative'
+ container.style.overflow = 'hidden'
+
+ var element = {( { getRef } ) => (
+
+ )}
+
+ ReactDOM.render(element, node);
+ });
+
+ it("should not detect an absolutely positioned element outside the visible part of a container", function(done) {
+ var firstTime = true;
+ var onChange = function(isVisible) {
+ if (firstTime) {
+ assert.equal(isVisible, false, "Component is not visible");
+ done();
+ }
+ };
+
+ container.style.width = 300
+ container.style.height = 300
+ container.style.position = 'relative'
+ container.style.overflow = 'hidden'
+
+ var element = {( { getRef } ) => (
+
+ )}
+
+ ReactDOM.render(element, node);
+ });
+});
diff --git a/visibility-sensor.js b/visibility-sensor.js
index 067b3c4..4a346ce 100644
--- a/visibility-sensor.js
+++ b/visibility-sensor.js
@@ -76,6 +76,8 @@ export default class VisibilitySensor extends React.Component {
constructor(props) {
super(props);
+ this.node = React.createRef();
+
this.state = {
isVisible: null,
visibilityRect: {}
@@ -83,7 +85,6 @@ export default class VisibilitySensor extends React.Component {
}
componentDidMount() {
- this.node = ReactDOM.findDOMNode(this);
if (this.props.active) {
this.startWatching();
}
@@ -94,9 +95,6 @@ export default class VisibilitySensor extends React.Component {
}
componentDidUpdate(prevProps) {
- // re-register node in componentDidUpdate if children diffs [#103]
- this.node = ReactDOM.findDOMNode(this);
-
if (this.props.active && !prevProps.active) {
this.setState({
isVisible: null,
@@ -219,7 +217,7 @@ export default class VisibilitySensor extends React.Component {
* Check if the element is within the visible viewport
*/
check = () => {
- const el = this.node;
+ const el = this.node && this.node.current;
let rect;
let containmentRect;
@@ -325,12 +323,35 @@ export default class VisibilitySensor extends React.Component {
};
render() {
- if (this.props.children instanceof Function) {
- return this.props.children({
+ const { children } = this.props;
+ const isFunction = children instanceof Function;
+
+ if (isFunction) {
+ let didAskForRef = false;
+ const getRef = () => {
+ didAskForRef = true;
+ return this.node;
+ };
+
+ const output = children({
isVisible: this.state.isVisible,
- visibilityRect: this.state.visibilityRect
+ visibilityRect: this.state.visibilityRect,
+ getRef: getRef
});
+
+ // if the consumer doesn't use our getRef function, we'll wrap
+ // it in a node and apply the ref ourselves.
+ return didAskForRef ? output : {output}
;
}
- return React.Children.only(this.props.children);
+
+ if (!React.Children.count(children)) {
+ return ;
+ }
+
+ console.warn(`[notice] passing children directly into the VisibilitySensor has been deprecated, and will be removed in the next major version.
+
+Please upgrade to the Child Function syntax instead: https://github.com/joshwnj/react-visibility-sensor#child-function-syntax`);
+
+ return {children}
;
}
}