react-intersection-observer
Advanced tools
Comparing version 0.3.2 to 1.0.0
@@ -64,6 +64,4 @@ 'use strict'; | ||
if (_this.node) (0, _intersection.unobserve)(_this.node); | ||
if (node) { | ||
(0, _intersection.observe)(node, _this.handleChange, _this.props.threshold); | ||
} | ||
_this.node = node; | ||
_this.observeNode(); | ||
}, _this.handleChange = function (inView) { | ||
@@ -77,5 +75,4 @@ return _this.setState({ inView: inView }); | ||
value: function componentWillUpdate(nextProps, nextState) { | ||
if (!!this.props.onChange && nextState.inView !== this.state.inView) { | ||
if (!!this.props.onChange && nextState !== this.state) { | ||
this.props.onChange(nextState.inView); | ||
if (nextState.inView && nextProps.unobserve) {} | ||
} | ||
@@ -86,2 +83,8 @@ } | ||
value: function componentDidUpdate(prevProps, prevState) { | ||
// If a IntersectionObserver option changed, reinit the observer | ||
if (prevProps.rootMargin !== this.props.rootMargin || prevProps.root !== this.props.root || prevProps.threshold !== this.props.threshold) { | ||
(0, _intersection.unobserve)(this.node); | ||
this.observeNode(); | ||
} | ||
if (prevState.inView !== this.state.inView) { | ||
@@ -103,12 +106,31 @@ if (this.state.inView && this.props.triggerOnce) { | ||
}, { | ||
key: 'render', | ||
value: function render() { | ||
key: 'observeNode', | ||
value: function observeNode() { | ||
if (!this.node) return; | ||
var _props = this.props, | ||
children = _props.children, | ||
render = _props.render, | ||
tag = _props.tag, | ||
triggerOnce = _props.triggerOnce, | ||
threshold = _props.threshold, | ||
props = _objectWithoutProperties(_props, ['children', 'render', 'tag', 'triggerOnce', 'threshold']); | ||
root = _props.root, | ||
rootMargin = _props.rootMargin, | ||
rootId = _props.rootId; | ||
(0, _intersection.observe)(this.node, this.handleChange, { | ||
threshold: threshold, | ||
root: root, | ||
rootMargin: rootMargin | ||
}, rootId); | ||
} | ||
}, { | ||
key: 'render', | ||
value: function render() { | ||
var _props2 = this.props, | ||
children = _props2.children, | ||
render = _props2.render, | ||
tag = _props2.tag, | ||
triggerOnce = _props2.triggerOnce, | ||
threshold = _props2.threshold, | ||
root = _props2.root, | ||
rootId = _props2.rootId, | ||
rootMargin = _props2.rootMargin, | ||
props = _objectWithoutProperties(_props2, ['children', 'render', 'tag', 'triggerOnce', 'threshold', 'root', 'rootId', 'rootMargin']); | ||
var inView = this.state.inView; | ||
@@ -138,4 +160,11 @@ | ||
triggerOnce: _propTypes2.default.bool, | ||
/** Number between 0 and 1 indicating the the percentage that should be visible before triggering */ | ||
threshold: _propTypes2.default.number, | ||
/** Number between 0 and 1 indicating the the percentage that should be visible before triggering. Can also be an array of numbers, to create multiple trigger points. */ | ||
threshold: _propTypes2.default.oneOfType([_propTypes2.default.arrayOf(_propTypes2.default.number), _propTypes2.default.number]), | ||
/** The HTMLElement that is used as the viewport for checking visibility of the target. Defaults to the browser viewport if not specified or if null.*/ | ||
root: _propTypes2.default.object, | ||
/** Margin around the root. Can have values similar to the CSS margin property, e.g. "10px 20px 30px 40px" (top, right, bottom, left). */ | ||
rootMargin: _propTypes2.default.string, | ||
/** Unique identifier for the root element - This is used to identify the IntersectionObserver instance, so it can be reused. | ||
* If you defined a root element, without adding an id, it will create a new instance for all components. */ | ||
rootId: _propTypes2.default.string, | ||
/** Call this function whenever the in view state changes */ | ||
@@ -142,0 +171,0 @@ onChange: _propTypes2.default.func, |
@@ -8,2 +8,3 @@ "use strict"; | ||
exports.unobserve = unobserve; | ||
exports.destroy = destroy; | ||
var INSTANCE_MAP = new Map(); | ||
@@ -15,21 +16,44 @@ var OBSERVER_MAP = new Map(); | ||
* @param element {HTMLElement} | ||
* @param callback {Function} - Called with inView | ||
* @param threshold {Number} Number between 0 and 1, indicating how much of the element should be visible before triggering | ||
* @param callback {Function} Called with inView | ||
* @param options {Object} InterSection observer options | ||
* @param options.threshold {Number} Number between 0 and 1, indicating how much of the element should be visible before triggering | ||
* @param options.root {HTMLElement} It should have a unique id or data-intersection-id in order for the Observer to reused. | ||
* @param options.rootMargin {String} The CSS margin to apply to the root element. | ||
* @param rootId {String} Unique identifier for the root element, to enable reusing the IntersectionObserver | ||
*/ | ||
function observe(element, callback) { | ||
var threshold = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0; | ||
var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : { | ||
threshold: 0 | ||
}; | ||
var rootId = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null; | ||
var threshold = options.threshold, | ||
root = options.root, | ||
rootMargin = options.rootMargin; | ||
if (!element || !callback) return; | ||
var observerInstance = OBSERVER_MAP.get(threshold); | ||
var observerId = rootMargin ? threshold + "_" + rootMargin : "" + threshold; | ||
if (root) { | ||
observerId = rootId ? rootId + "_" + observerId : null; | ||
} | ||
var observerInstance = observerId ? OBSERVER_MAP.get(observerId) : null; | ||
if (!observerInstance) { | ||
observerInstance = new IntersectionObserver(onChange, { threshold: threshold }); | ||
OBSERVER_MAP.set(threshold, observerInstance); | ||
observerInstance = new IntersectionObserver(onChange, options); | ||
if (observerId) OBSERVER_MAP.set(observerId, observerInstance); | ||
} | ||
INSTANCE_MAP.set(element, { | ||
var instance = { | ||
callback: callback, | ||
visible: false, | ||
threshold: threshold | ||
}); | ||
options: options, | ||
observerId: observerId, | ||
observer: !observerId ? observerInstance : undefined | ||
}; | ||
INSTANCE_MAP.set(element, instance); | ||
observerInstance.observe(element); | ||
return instance; | ||
} | ||
@@ -47,5 +71,6 @@ | ||
var _INSTANCE_MAP$get = INSTANCE_MAP.get(element), | ||
threshold = _INSTANCE_MAP$get.threshold; | ||
observerId = _INSTANCE_MAP$get.observerId, | ||
observer = _INSTANCE_MAP$get.observer; | ||
var observerInstance = OBSERVER_MAP.get(threshold); | ||
var observerInstance = observerId ? OBSERVER_MAP.get(observerId) : observer; | ||
@@ -58,7 +83,9 @@ if (observerInstance) { | ||
var itemsLeft = false; | ||
INSTANCE_MAP.forEach(function (item) { | ||
if (item.threshold === threshold) { | ||
itemsLeft = true; | ||
} | ||
}); | ||
if (observerId) { | ||
INSTANCE_MAP.forEach(function (item, key) { | ||
if (item.observerId === observerId && key !== element) { | ||
itemsLeft = true; | ||
} | ||
}); | ||
} | ||
@@ -68,3 +95,3 @@ if (observerInstance && !itemsLeft) { | ||
observerInstance.disconnect(); | ||
OBSERVER_MAP.delete(threshold); | ||
OBSERVER_MAP.delete(observerId); | ||
} | ||
@@ -77,2 +104,14 @@ | ||
/** | ||
* Destroy all IntersectionObservers currently connected | ||
**/ | ||
function destroy() { | ||
OBSERVER_MAP.forEach(function (observer) { | ||
observer.disconnect(); | ||
}); | ||
OBSERVER_MAP.clear(); | ||
INSTANCE_MAP.clear(); | ||
} | ||
function onChange(changes) { | ||
@@ -85,12 +124,17 @@ changes.forEach(function (intersection) { | ||
var _INSTANCE_MAP$get2 = INSTANCE_MAP.get(target), | ||
callback = _INSTANCE_MAP$get2.callback, | ||
visible = _INSTANCE_MAP$get2.visible, | ||
threshold = _INSTANCE_MAP$get2.threshold; | ||
var instance = INSTANCE_MAP.get(target); | ||
var options = instance.options; | ||
// Trigger on 0 ratio only when not visible. This is fallback for browsers without isIntersecting support | ||
var inView = void 0; | ||
if (Array.isArray(options.threshold)) { | ||
// If threshold is an array, check if any of them intersects. This just triggers the onChange event multiple times. | ||
inView = options.threshold.some(function (threshold) { | ||
return instance.visible ? intersectionRatio > threshold : intersectionRatio >= threshold; | ||
}); | ||
} else { | ||
// Trigger on 0 ratio only when not visible. This is fallback for browsers without isIntersecting support | ||
inView = instance.visible ? intersectionRatio > options.threshold : intersectionRatio >= options.threshold; | ||
} | ||
var inView = visible ? intersectionRatio > threshold : intersectionRatio >= threshold; | ||
if (isIntersecting !== undefined) { | ||
@@ -102,10 +146,7 @@ // If isIntersecting is defined, ensure that the element is actually intersecting. | ||
INSTANCE_MAP.set(target, { | ||
callback: callback, | ||
visible: inView, | ||
threshold: threshold | ||
}); | ||
// Update the visible value on the instance | ||
instance.visible = inView; | ||
if (callback) { | ||
callback(inView); | ||
if (instance.callback) { | ||
instance.callback(inView); | ||
} | ||
@@ -118,3 +159,4 @@ } | ||
observe: observe, | ||
unobserve: unobserve | ||
unobserve: unobserve, | ||
destroy: destroy | ||
}; |
@@ -64,6 +64,4 @@ 'use strict'; | ||
if (_this.node) (0, _intersection.unobserve)(_this.node); | ||
if (node) { | ||
(0, _intersection.observe)(node, _this.handleChange, _this.props.threshold); | ||
} | ||
_this.node = node; | ||
_this.observeNode(); | ||
}, _this.handleChange = function (inView) { | ||
@@ -77,5 +75,4 @@ return _this.setState({ inView: inView }); | ||
value: function componentWillUpdate(nextProps, nextState) { | ||
if (!!this.props.onChange && nextState.inView !== this.state.inView) { | ||
if (!!this.props.onChange && nextState !== this.state) { | ||
this.props.onChange(nextState.inView); | ||
if (nextState.inView && nextProps.unobserve) {} | ||
} | ||
@@ -86,2 +83,8 @@ } | ||
value: function componentDidUpdate(prevProps, prevState) { | ||
// If a IntersectionObserver option changed, reinit the observer | ||
if (prevProps.rootMargin !== this.props.rootMargin || prevProps.root !== this.props.root || prevProps.threshold !== this.props.threshold) { | ||
(0, _intersection.unobserve)(this.node); | ||
this.observeNode(); | ||
} | ||
if (prevState.inView !== this.state.inView) { | ||
@@ -103,12 +106,31 @@ if (this.state.inView && this.props.triggerOnce) { | ||
}, { | ||
key: 'render', | ||
value: function render() { | ||
key: 'observeNode', | ||
value: function observeNode() { | ||
if (!this.node) return; | ||
var _props = this.props, | ||
children = _props.children, | ||
render = _props.render, | ||
tag = _props.tag, | ||
triggerOnce = _props.triggerOnce, | ||
threshold = _props.threshold, | ||
props = _objectWithoutProperties(_props, ['children', 'render', 'tag', 'triggerOnce', 'threshold']); | ||
root = _props.root, | ||
rootMargin = _props.rootMargin, | ||
rootId = _props.rootId; | ||
(0, _intersection.observe)(this.node, this.handleChange, { | ||
threshold: threshold, | ||
root: root, | ||
rootMargin: rootMargin | ||
}, rootId); | ||
} | ||
}, { | ||
key: 'render', | ||
value: function render() { | ||
var _props2 = this.props, | ||
children = _props2.children, | ||
render = _props2.render, | ||
tag = _props2.tag, | ||
triggerOnce = _props2.triggerOnce, | ||
threshold = _props2.threshold, | ||
root = _props2.root, | ||
rootId = _props2.rootId, | ||
rootMargin = _props2.rootMargin, | ||
props = _objectWithoutProperties(_props2, ['children', 'render', 'tag', 'triggerOnce', 'threshold', 'root', 'rootId', 'rootMargin']); | ||
var inView = this.state.inView; | ||
@@ -138,4 +160,11 @@ | ||
triggerOnce: _propTypes2.default.bool, | ||
/** Number between 0 and 1 indicating the the percentage that should be visible before triggering */ | ||
threshold: _propTypes2.default.number, | ||
/** Number between 0 and 1 indicating the the percentage that should be visible before triggering. Can also be an array of numbers, to create multiple trigger points. */ | ||
threshold: _propTypes2.default.oneOfType([_propTypes2.default.arrayOf(_propTypes2.default.number), _propTypes2.default.number]), | ||
/** The HTMLElement that is used as the viewport for checking visibility of the target. Defaults to the browser viewport if not specified or if null.*/ | ||
root: _propTypes2.default.object, | ||
/** Margin around the root. Can have values similar to the CSS margin property, e.g. "10px 20px 30px 40px" (top, right, bottom, left). */ | ||
rootMargin: _propTypes2.default.string, | ||
/** Unique identifier for the root element - This is used to identify the IntersectionObserver instance, so it can be reused. | ||
* If you defined a root element, without adding an id, it will create a new instance for all components. */ | ||
rootId: _propTypes2.default.string, | ||
/** Call this function whenever the in view state changes */ | ||
@@ -142,0 +171,0 @@ onChange: _propTypes2.default.func, |
@@ -8,2 +8,3 @@ "use strict"; | ||
exports.unobserve = unobserve; | ||
exports.destroy = destroy; | ||
var INSTANCE_MAP = new Map(); | ||
@@ -15,21 +16,44 @@ var OBSERVER_MAP = new Map(); | ||
* @param element {HTMLElement} | ||
* @param callback {Function} - Called with inView | ||
* @param threshold {Number} Number between 0 and 1, indicating how much of the element should be visible before triggering | ||
* @param callback {Function} Called with inView | ||
* @param options {Object} InterSection observer options | ||
* @param options.threshold {Number} Number between 0 and 1, indicating how much of the element should be visible before triggering | ||
* @param options.root {HTMLElement} It should have a unique id or data-intersection-id in order for the Observer to reused. | ||
* @param options.rootMargin {String} The CSS margin to apply to the root element. | ||
* @param rootId {String} Unique identifier for the root element, to enable reusing the IntersectionObserver | ||
*/ | ||
function observe(element, callback) { | ||
var threshold = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0; | ||
var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : { | ||
threshold: 0 | ||
}; | ||
var rootId = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null; | ||
var threshold = options.threshold, | ||
root = options.root, | ||
rootMargin = options.rootMargin; | ||
if (!element || !callback) return; | ||
var observerInstance = OBSERVER_MAP.get(threshold); | ||
var observerId = rootMargin ? threshold + "_" + rootMargin : "" + threshold; | ||
if (root) { | ||
observerId = rootId ? rootId + "_" + observerId : null; | ||
} | ||
var observerInstance = observerId ? OBSERVER_MAP.get(observerId) : null; | ||
if (!observerInstance) { | ||
observerInstance = new IntersectionObserver(onChange, { threshold: threshold }); | ||
OBSERVER_MAP.set(threshold, observerInstance); | ||
observerInstance = new IntersectionObserver(onChange, options); | ||
if (observerId) OBSERVER_MAP.set(observerId, observerInstance); | ||
} | ||
INSTANCE_MAP.set(element, { | ||
var instance = { | ||
callback: callback, | ||
visible: false, | ||
threshold: threshold | ||
}); | ||
options: options, | ||
observerId: observerId, | ||
observer: !observerId ? observerInstance : undefined | ||
}; | ||
INSTANCE_MAP.set(element, instance); | ||
observerInstance.observe(element); | ||
return instance; | ||
} | ||
@@ -47,5 +71,6 @@ | ||
var _INSTANCE_MAP$get = INSTANCE_MAP.get(element), | ||
threshold = _INSTANCE_MAP$get.threshold; | ||
observerId = _INSTANCE_MAP$get.observerId, | ||
observer = _INSTANCE_MAP$get.observer; | ||
var observerInstance = OBSERVER_MAP.get(threshold); | ||
var observerInstance = observerId ? OBSERVER_MAP.get(observerId) : observer; | ||
@@ -58,7 +83,9 @@ if (observerInstance) { | ||
var itemsLeft = false; | ||
INSTANCE_MAP.forEach(function (item) { | ||
if (item.threshold === threshold) { | ||
itemsLeft = true; | ||
} | ||
}); | ||
if (observerId) { | ||
INSTANCE_MAP.forEach(function (item, key) { | ||
if (item.observerId === observerId && key !== element) { | ||
itemsLeft = true; | ||
} | ||
}); | ||
} | ||
@@ -68,3 +95,3 @@ if (observerInstance && !itemsLeft) { | ||
observerInstance.disconnect(); | ||
OBSERVER_MAP.delete(threshold); | ||
OBSERVER_MAP.delete(observerId); | ||
} | ||
@@ -77,2 +104,14 @@ | ||
/** | ||
* Destroy all IntersectionObservers currently connected | ||
**/ | ||
function destroy() { | ||
OBSERVER_MAP.forEach(function (observer) { | ||
observer.disconnect(); | ||
}); | ||
OBSERVER_MAP.clear(); | ||
INSTANCE_MAP.clear(); | ||
} | ||
function onChange(changes) { | ||
@@ -85,12 +124,17 @@ changes.forEach(function (intersection) { | ||
var _INSTANCE_MAP$get2 = INSTANCE_MAP.get(target), | ||
callback = _INSTANCE_MAP$get2.callback, | ||
visible = _INSTANCE_MAP$get2.visible, | ||
threshold = _INSTANCE_MAP$get2.threshold; | ||
var instance = INSTANCE_MAP.get(target); | ||
var options = instance.options; | ||
// Trigger on 0 ratio only when not visible. This is fallback for browsers without isIntersecting support | ||
var inView = void 0; | ||
if (Array.isArray(options.threshold)) { | ||
// If threshold is an array, check if any of them intersects. This just triggers the onChange event multiple times. | ||
inView = options.threshold.some(function (threshold) { | ||
return instance.visible ? intersectionRatio > threshold : intersectionRatio >= threshold; | ||
}); | ||
} else { | ||
// Trigger on 0 ratio only when not visible. This is fallback for browsers without isIntersecting support | ||
inView = instance.visible ? intersectionRatio > options.threshold : intersectionRatio >= options.threshold; | ||
} | ||
var inView = visible ? intersectionRatio > threshold : intersectionRatio >= threshold; | ||
if (isIntersecting !== undefined) { | ||
@@ -102,10 +146,7 @@ // If isIntersecting is defined, ensure that the element is actually intersecting. | ||
INSTANCE_MAP.set(target, { | ||
callback: callback, | ||
visible: inView, | ||
threshold: threshold | ||
}); | ||
// Update the visible value on the instance | ||
instance.visible = inView; | ||
if (callback) { | ||
callback(inView); | ||
if (instance.callback) { | ||
instance.callback(inView); | ||
} | ||
@@ -118,3 +159,4 @@ } | ||
observe: observe, | ||
unobserve: unobserve | ||
unobserve: unobserve, | ||
destroy: destroy | ||
}; |
{ | ||
"name": "react-intersection-observer", | ||
"version": "0.3.2", | ||
"version": "1.0.0", | ||
"description": "Monitor if a component is inside the viewport, using IntersectionObserver API", | ||
@@ -94,4 +94,4 @@ "main": "lib/index.js", | ||
"enzyme-to-json": "^1.5.1", | ||
"eslint": "^4.2.0", | ||
"eslint-config-insilico": "^4.1.0", | ||
"eslint": "^4.3.0", | ||
"eslint-config-insilico": "^4.1.1", | ||
"eslint-config-prettier": "^2.3.0", | ||
@@ -98,0 +98,0 @@ "husky": "^0.14.3", |
@@ -45,3 +45,3 @@ # react-intersection-observer | ||
Then import it in your app | ||
Then import it in your app: | ||
@@ -55,10 +55,13 @@ ```js | ||
| Name | Type | Default | Required | Description | | ||
| ---------------- | --------- | ----------------- | -------- | ----------------------------------------------------- | | ||
| tag | String | | false | Element tag to use for the wrapping component | | ||
| children | func/node | | false | Children should be either a function or a node | | ||
| triggerOnce | Bool | false | true | Only trigger this method once | | ||
| threshold | Number | 0 | false | Number between 0 and 1 indicating the the percentage that should be visible before triggering | | ||
| onChange | Func | | false | Call this function whenever the in view state changes | | ||
| render | Func | | false | Use render method to only render content when inView | | ||
| Name | Type | Default | Required | Description | | ||
| ---------------- | ----------- | ----------------- | -------- | ----------------------------------------------------- | | ||
| children | func/node | | true | Children should be either a function or a node | | ||
| root | HTMLElement | | false | The HTMLElement that is used as the viewport for checking visibility of the target. Defaults to the browser viewport if not specified or if null. | | ||
| rootId | String | | false | Unique identifier for the root element - This is used to identify the IntersectionObserver instance, so it can be reused. If you defined a root element, without adding an id, it will create a new instance for all components. | | ||
| 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). | | ||
| tag | String | 'div' | false | Element tag to use for the wrapping component | | ||
| threshold | Number | 0 | false | Number between 0 and 1 indicating the the percentage that should be visible before triggering. Can also be an array of numbers, to create multiple trigger points. | | ||
| triggerOnce | Bool | false | false | Only trigger this method once | | ||
| onChange | Func | | false | Call this function whenever the in view state changes | | ||
| render | Func | | false | Use render method to only render content when inView | | ||
@@ -65,0 +68,0 @@ ## Example code |
@@ -24,4 +24,14 @@ import React, { Component, createElement } from 'react' // eslint-disable-line no-unused-vars | ||
triggerOnce: PropTypes.bool, | ||
/** Number between 0 and 1 indicating the the percentage that should be visible before triggering */ | ||
threshold: PropTypes.number, | ||
/** Number between 0 and 1 indicating the the percentage that should be visible before triggering. Can also be an array of numbers, to create multiple trigger points. */ | ||
threshold: PropTypes.oneOfType([ | ||
PropTypes.arrayOf(PropTypes.number), | ||
PropTypes.number, | ||
]), | ||
/** The HTMLElement that is used as the viewport for checking visibility of the target. Defaults to the browser viewport if not specified or if null.*/ | ||
root: PropTypes.object, | ||
/** Margin around the root. Can have values similar to the CSS margin property, e.g. "10px 20px 30px 40px" (top, right, bottom, left). */ | ||
rootMargin: PropTypes.string, | ||
/** Unique identifier for the root element - This is used to identify the IntersectionObserver instance, so it can be reused. | ||
* If you defined a root element, without adding an id, it will create a new instance for all components. */ | ||
rootId: PropTypes.string, | ||
/** Call this function whenever the in view state changes */ | ||
@@ -44,6 +54,4 @@ onChange: PropTypes.func, | ||
componentWillUpdate(nextProps, nextState) { | ||
if (!!this.props.onChange && nextState.inView !== this.state.inView) { | ||
if (!!this.props.onChange && nextState !== this.state) { | ||
this.props.onChange(nextState.inView) | ||
if (nextState.inView && nextProps.unobserve) { | ||
} | ||
} | ||
@@ -53,2 +61,12 @@ } | ||
componentDidUpdate(prevProps, prevState) { | ||
// If a IntersectionObserver option changed, reinit the observer | ||
if ( | ||
prevProps.rootMargin !== this.props.rootMargin || | ||
prevProps.root !== this.props.root || | ||
prevProps.threshold !== this.props.threshold | ||
) { | ||
unobserve(this.node) | ||
this.observeNode() | ||
} | ||
if (prevState.inView !== this.state.inView) { | ||
@@ -71,8 +89,21 @@ if (this.state.inView && this.props.triggerOnce) { | ||
observeNode() { | ||
if (!this.node) return | ||
const { threshold, root, rootMargin, rootId } = this.props | ||
observe( | ||
this.node, | ||
this.handleChange, | ||
{ | ||
threshold, | ||
root, | ||
rootMargin, | ||
}, | ||
rootId, | ||
) | ||
} | ||
handleNode = node => { | ||
if (this.node) unobserve(this.node) | ||
if (node) { | ||
observe(node, this.handleChange, this.props.threshold) | ||
} | ||
this.node = node | ||
this.observeNode() | ||
} | ||
@@ -89,4 +120,8 @@ | ||
threshold, | ||
root, | ||
rootId, | ||
rootMargin, | ||
...props | ||
} = this.props | ||
const { inView } = this.state | ||
@@ -93,0 +128,0 @@ |
@@ -7,19 +7,44 @@ const INSTANCE_MAP = new Map() | ||
* @param element {HTMLElement} | ||
* @param callback {Function} - Called with inView | ||
* @param threshold {Number} Number between 0 and 1, indicating how much of the element should be visible before triggering | ||
* @param callback {Function} Called with inView | ||
* @param options {Object} InterSection observer options | ||
* @param options.threshold {Number} Number between 0 and 1, indicating how much of the element should be visible before triggering | ||
* @param options.root {HTMLElement} It should have a unique id or data-intersection-id in order for the Observer to reused. | ||
* @param options.rootMargin {String} The CSS margin to apply to the root element. | ||
* @param rootId {String} Unique identifier for the root element, to enable reusing the IntersectionObserver | ||
*/ | ||
export function observe(element, callback, threshold = 0) { | ||
export function observe( | ||
element, | ||
callback, | ||
options = { | ||
threshold: 0, | ||
}, | ||
rootId = null, | ||
) { | ||
const { threshold, root, rootMargin } = options | ||
if (!element || !callback) return | ||
let observerInstance = OBSERVER_MAP.get(threshold) | ||
let observerId = rootMargin ? `${threshold}_${rootMargin}` : `${threshold}` | ||
if (root) { | ||
observerId = rootId ? `${rootId}_${observerId}` : null | ||
} | ||
let observerInstance = observerId ? OBSERVER_MAP.get(observerId) : null | ||
if (!observerInstance) { | ||
observerInstance = new IntersectionObserver(onChange, { threshold }) | ||
OBSERVER_MAP.set(threshold, observerInstance) | ||
observerInstance = new IntersectionObserver(onChange, options) | ||
if (observerId) OBSERVER_MAP.set(observerId, observerInstance) | ||
} | ||
INSTANCE_MAP.set(element, { | ||
const instance = { | ||
callback, | ||
visible: false, | ||
threshold, | ||
}) | ||
options, | ||
observerId, | ||
observer: !observerId ? observerInstance : undefined, | ||
} | ||
INSTANCE_MAP.set(element, instance) | ||
observerInstance.observe(element) | ||
return instance | ||
} | ||
@@ -36,4 +61,6 @@ | ||
if (INSTANCE_MAP.has(element)) { | ||
const { threshold } = INSTANCE_MAP.get(element) | ||
const observerInstance = OBSERVER_MAP.get(threshold) | ||
const { observerId, observer } = INSTANCE_MAP.get(element) | ||
const observerInstance = observerId | ||
? OBSERVER_MAP.get(observerId) | ||
: observer | ||
@@ -46,7 +73,9 @@ if (observerInstance) { | ||
let itemsLeft = false | ||
INSTANCE_MAP.forEach(item => { | ||
if (item.threshold === threshold) { | ||
itemsLeft = true | ||
} | ||
}) | ||
if (observerId) { | ||
INSTANCE_MAP.forEach((item, key) => { | ||
if (item.observerId === observerId && key !== element) { | ||
itemsLeft = true | ||
} | ||
}) | ||
} | ||
@@ -56,3 +85,3 @@ if (observerInstance && !itemsLeft) { | ||
observerInstance.disconnect() | ||
OBSERVER_MAP.delete(threshold) | ||
OBSERVER_MAP.delete(observerId) | ||
} | ||
@@ -65,2 +94,14 @@ | ||
/** | ||
* Destroy all IntersectionObservers currently connected | ||
**/ | ||
export function destroy() { | ||
OBSERVER_MAP.forEach(observer => { | ||
observer.disconnect() | ||
}) | ||
OBSERVER_MAP.clear() | ||
INSTANCE_MAP.clear() | ||
} | ||
function onChange(changes) { | ||
@@ -70,9 +111,21 @@ changes.forEach(intersection => { | ||
const { isIntersecting, intersectionRatio, target } = intersection | ||
const { callback, visible, threshold } = INSTANCE_MAP.get(target) | ||
const instance = INSTANCE_MAP.get(target) | ||
const options = instance.options | ||
// Trigger on 0 ratio only when not visible. This is fallback for browsers without isIntersecting support | ||
let inView = visible | ||
? intersectionRatio > threshold | ||
: intersectionRatio >= threshold | ||
let inView | ||
if (Array.isArray(options.threshold)) { | ||
// If threshold is an array, check if any of them intersects. This just triggers the onChange event multiple times. | ||
inView = options.threshold.some(threshold => { | ||
return instance.visible | ||
? intersectionRatio > threshold | ||
: intersectionRatio >= threshold | ||
}) | ||
} else { | ||
// Trigger on 0 ratio only when not visible. This is fallback for browsers without isIntersecting support | ||
inView = instance.visible | ||
? intersectionRatio > options.threshold | ||
: intersectionRatio >= options.threshold | ||
} | ||
if (isIntersecting !== undefined) { | ||
@@ -84,10 +137,7 @@ // If isIntersecting is defined, ensure that the element is actually intersecting. | ||
INSTANCE_MAP.set(target, { | ||
callback, | ||
visible: inView, | ||
threshold, | ||
}) | ||
// Update the visible value on the instance | ||
instance.visible = inView | ||
if (callback) { | ||
callback(inView) | ||
if (instance.callback) { | ||
instance.callback(inView) | ||
} | ||
@@ -101,2 +151,3 @@ } | ||
unobserve, | ||
destroy, | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
44393
790
1
117