├── .gitignore
├── LICENSE.txt
├── README.md
├── dist
├── scroll.observer.umd.js
└── scroll.observer.umd.min.js
├── package-lock.json
├── package.json
├── rollup.config.js
└── src
└── scroll.observer.js
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /.idea
3 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 | Copyright (c) 2018 Martin Laxenaire
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
5 |
6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7 |
8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Lightweight vanilla javascript library to handle intersection observers
2 |
3 |
Initializing
4 |
5 | ```javascript
6 | import {ScrollObserver} from './src/scroll.observer.js';
7 |
8 | const scrollObserver = new ScrollObserver();
9 | ```
10 |
11 | In a browser, you can use the UMD files located in the `dist` directory:
12 |
13 | ```html
14 |
15 | ```
16 |
17 |
18 | ```javascript
19 | const scrollObserver = new ScrollObserver();
20 | ```
21 |
22 | Parameters
23 |
24 | Basic params
25 |
26 | The basic parameters you can specify are the same you'd use with an intersection observer (see https://developer.mozilla.org/fr/docs/Web/API/Intersection_Observer_API):
27 |
28 | | Parameter | Type | Default | Description |
29 | | --- | --- | --- | --- |
30 | | root | CSS selector | "viewport" | Root intersection observer parameter |
31 | | rootMargin | String | "0px" | Root margins to use |
32 | | threshold | array | 0 | Array of thresholds that will trigger the callback function |
33 |
34 | ```javascript
35 | const scrollObserver = new ScrollObserver({
36 | // root
37 | root: document.getElementById("content"),
38 | // rootMargins
39 | rootMargin: "20px 0",
40 | // threshold array
41 | threshold: [0, 0.25, 0.5, 0.75, 1]
42 | });
43 | ```
44 |
45 | Observe elements
46 |
47 | Once you've created an observer, you need to observe elements.
48 |
49 | You can add one or multiple elements to observe thanks to the `observe()` method:
50 |
51 | ```javascript
52 | scrollObserver.observe({
53 | // elements to observe
54 | elements: document.querySelectorAll(".scroll-observed-el")
55 | });
56 | ```
57 |
58 | You can use a CSS selector instead of a list of elements:
59 |
60 | ```javascript
61 | // exactly the same as above
62 | scrollObserver.observe({
63 | // elements to observe specified with a selector
64 | selector: ".scroll-observed-el"
65 | });
66 | ```
67 |
68 | There are 3 callbacks that you can use when observing an element, `onBeforeElVisible()`, `onElVisible()` and `onElHidden`, trigger by the intersection observer based on the `root`, `rootMargin` and `threshold` parameters:
69 |
70 | ```javascript
71 | scrollObserver.observe({
72 | // elements to observe
73 | elements: document.querySelectorAll(".scroll-observed-el"),
74 | // wait for 200ms (cumulative) each time one of this element is visible
75 | stagger: 200,
76 | // an ".scroll-observed-el" element became visible
77 | onBeforeElVisible: (observedEl) => {
78 | // observedEl is an object containing the original HTML element, its bounding rectangle and other properties
79 | console.log(observedEl);
80 | },
81 | // an ".scroll-observed-el" element became visible and its staggering timeout has run
82 | onElVisible: (observedEl) => {
83 | // observedEl is an object containing the original HTML element, its bounding rectangle and other properties
84 | console.log(observedEl);
85 | },
86 | // an ".scroll-observed-el" element became hidden
87 | onElHidden: (observedEl) => {
88 | // observedEl is an object containing the original HTML element, its bounding rectangle and other properties
89 | console.log(observedEl);
90 | },
91 | });
92 | ```
93 |
94 | Complete parameter list
95 |
96 | Here is a complete list of all `observe()` method parameters:
97 |
98 | | Parameter | Type | Default | Description |
99 | | --- | --- | --- | --- |
100 | | elements | array of HTML elements | [] | elements to observe |
101 | | selector | string | null | elements to observe based on a CSS selector |
102 | | keepObserving | bool | false | Whether we should keep observing the element after it has been in and out of view |
103 | | triggerRatio | float between 0 and 1 | 0 | The ratio at which the visible/hidden callback should be called |
104 | | alwaysTrigger | bool | true | Whether it should trigger the visible callback multiple times or just once |
105 | | stagger | float | 0 | Number of milliseconds to wait before calling the visible callback (used for staggering animations) |
106 | | onBeforeElVisible | function(observedEl) | void | Function to execute right when this element become visible. Use it to change this element staggering property for example |
107 | | onElVisible | function(observedEl) | void | Function to execute when this element become visible, after the staggering timeout |
108 | | onElHidden | function(observedEl) | void | Function to execute when this element become hidden |
109 |
110 |
111 | Unobserve elements
112 |
113 | You can decide to stop observing elements whenever you want.
114 |
115 | Unobserving one element
116 |
117 | You can pass a HTML element to the `unobserveEl()` method:
118 |
119 | ```javascript
120 | scrollObserver.unobserveEl(document.getElementById("observed-el"));
121 | ```
122 |
123 | Unobserving multiple elements
124 |
125 | You can pass an array of elements to the `unobserveEls()` method:
126 |
127 | ```javascript
128 | scrollObserver.unobserveEls(document.querySelectorAll(".scroll-observed-el"));
129 | ```
130 |
131 | Unobserving all elements and disconnect observer
132 |
133 | You can also decide to unobserve all elements and disconnect your observer:
134 |
135 | ```javascript
136 | scrollObserver.unobserveAll();
137 | ```
138 |
139 |
140 | Events
141 |
142 | There are 4 events you can use, `onError()`, `onBeforeElVisible()`, `onElVisible()` and `onElHidden()`:
143 |
144 | ```javascript
145 | scrollObserver.onError(() => {
146 | // intersection observer API is not supported, do something
147 | }).onBeforeElVisible((observedEl) => {
148 | // called each time an observed element become visible
149 | console.log(observedEl);
150 | }).onElVisible((observedEl) => {
151 | // called each time an observed element become visible but after its staggering timeout
152 | console.log(observedEl);
153 | }).onElHidden((observedEl) => {
154 | // called each time an observed element become hidden
155 | console.log(observedEl);
156 | });
157 | ```
--------------------------------------------------------------------------------
/dist/scroll.observer.umd.js:
--------------------------------------------------------------------------------
1 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
2 |
3 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
4 |
5 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
6 |
7 | function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
8 |
9 | (function (global, factory) {
10 | (typeof exports === "undefined" ? "undefined" : _typeof(exports)) === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : (global = global || self, factory(global.window = global.window || {}));
11 | })(this, function (exports) {
12 | 'use strict';
13 | /***
14 | Lightweight vanilla javascript library to handle intersection observers
15 | Inspired from past work & and Baptiste Briel work: http://awams.bbriel.me/
16 | Author: Martin Laxenaire https://www.martin-laxenaire.fr/
17 | Version: 1.2.0
18 | ***/
19 |
20 | var ScrollObserver = /*#__PURE__*/function () {
21 | /***
22 | If the intersection observer API is available, init our observer
23 | call the onError callback else
24 | params (see https://developer.mozilla.org/fr/docs/Web/API/Intersection_Observer_API):
25 | @root (CSS selector): root intersection observer params, viewport if null
26 | @rootMargin (string): root margins to use, default to 0 pixel
27 | @threshold (array): array of thresholds that will trigger the callback function, default to 0
28 | ***/
29 | function ScrollObserver(_ref) {
30 | var _this = this;
31 |
32 | var root = _ref.root,
33 | _ref$rootMargin = _ref.rootMargin,
34 | rootMargin = _ref$rootMargin === void 0 ? "0px" : _ref$rootMargin,
35 | _ref$threshold = _ref.threshold,
36 | threshold = _ref$threshold === void 0 ? 0 : _ref$threshold;
37 |
38 | _classCallCheck(this, ScrollObserver);
39 |
40 | if (!!window.IntersectionObserver) {
41 | this.root = document.querySelector(root);
42 |
43 | if (!this.root) {
44 | this.root = "viewport";
45 | }
46 |
47 | this.rootMargin = rootMargin;
48 | this.threshold = threshold; // cache elements to observe
49 |
50 | this.els = [];
51 | this._staggerEls = [];
52 | this.observer = new IntersectionObserver(this._callback.bind(this), {
53 | root: this.root === "viewport" ? null : this.root,
54 | rootMargin: this.rootMargin,
55 | threshold: this.threshold
56 | });
57 | } else {
58 | // intersection observer API not supported, trigger onError callback
59 | setTimeout(function () {
60 | _this._onErrorCallback && _this._onErrorCallback();
61 | }, 0);
62 | }
63 | }
64 | /***
65 | This is our intersection observer callback
66 | called each time an entry crosses a threshold
67 | Can call callback functions based on element parameters
68 | ***/
69 |
70 |
71 | _createClass(ScrollObserver, [{
72 | key: "_callback",
73 | value: function _callback(entries) {
74 | var _this2 = this;
75 |
76 | entries.forEach(function (entry, index) {
77 | // find our entry in our cache elements array
78 | var cachedEl = _this2.els.find(function (data) {
79 | return data.el.isSameNode(entry.target);
80 | });
81 |
82 | if (cachedEl) {
83 | // if intersection ratio is bigger than the triggerRatio property
84 | if (entry.intersectionRatio > cachedEl.triggerRatio) {
85 | // if we should always trigger it or if visibility hasn't been triggered yet
86 | if (cachedEl.alwaysTrigger || !cachedEl.inView) {
87 | // on before el visible callback
88 | cachedEl.onBeforeElVisible && cachedEl.onBeforeElVisible(cachedEl);
89 | _this2._onBeforeElVisibleCallback && _this2._onBeforeElVisibleCallback(cachedEl); // calculate staggering timeout
90 |
91 | var staggerTimeout = cachedEl.stagger;
92 |
93 | _this2._staggerEls.forEach(function (el) {
94 | if (el.inView) {
95 | staggerTimeout += el.stagger;
96 | }
97 | });
98 |
99 | _this2._staggerEls.push(cachedEl); // apply staggering timeout
100 |
101 |
102 | setTimeout(function () {
103 | cachedEl.onElVisible && cachedEl.onElVisible(cachedEl);
104 | _this2._onElVisibleCallback && _this2._onElVisibleCallback(cachedEl); // remove from the staggering els array
105 |
106 | _this2._staggerEls = _this2._staggerEls.filter(function (el) {
107 | return cachedEl.id !== el.id;
108 | });
109 | }, staggerTimeout);
110 | } // element is now in view
111 |
112 |
113 | cachedEl.inView = true;
114 | } else if (cachedEl.inView && entry.intersectionRatio <= cachedEl.triggerRatio) {
115 | // if intersection ratio is smaller than our trigger ratio and our element is visible
116 | // element is no more visible
117 | cachedEl.inView = false;
118 | cachedEl.onElHidden && cachedEl.onElHidden(cachedEl);
119 | _this2._onElHiddenCallback && _this2._onElHiddenCallback(cachedEl); // if we should observe it just once, unobserve it now
120 |
121 | if (!cachedEl.keepObserving) {
122 | _this2._unobserve(cachedEl);
123 | }
124 | } // update its ratio property
125 |
126 |
127 | cachedEl.ratio = entry.intersectionRatio; // update its boundingClientRect object
128 |
129 | cachedEl.boundingClientRect = entry.boundingClientRect;
130 | }
131 | });
132 | }
133 | /***
134 | Start observing a set of elements
135 | params:
136 | @elements (array of HTML elements): elements to observe
137 | @selector (CSS selector): elements to query based on a CSS selector
138 | @keepObserving (bool): whether we should keep observing the element after it has been in and out of view, default to false
139 | @triggerRatio (float between 0 and 1): the ratio at which the visible/hidden callback should be called, default to 0
140 | @alwaysTrigger (bool): whether it should trigger the visible callback multiple times or just once, default to true
141 | @stagger (int): number of milliseconds to wait before calling the visible callback (used for staggering animations), default to 0
142 | @onBeforeElVisible (function): function to execute right before an element onElVisible callback is called, used it to change its stagger property for example
143 | @onElVisible (function): function to execute when an element become visible
144 | @onElHidden (function): function to execute when an element become hidden
145 | ***/
146 |
147 | }, {
148 | key: "observe",
149 | value: function observe(_ref2) {
150 | var _ref2$elements = _ref2.elements,
151 | elements = _ref2$elements === void 0 ? [] : _ref2$elements,
152 | selector = _ref2.selector,
153 | _ref2$keepObserving = _ref2.keepObserving,
154 | keepObserving = _ref2$keepObserving === void 0 ? false : _ref2$keepObserving,
155 | _ref2$triggerRatio = _ref2.triggerRatio,
156 | triggerRatio = _ref2$triggerRatio === void 0 ? 0 : _ref2$triggerRatio,
157 | _ref2$alwaysTrigger = _ref2.alwaysTrigger,
158 | alwaysTrigger = _ref2$alwaysTrigger === void 0 ? true : _ref2$alwaysTrigger,
159 | _ref2$stagger = _ref2.stagger,
160 | stagger = _ref2$stagger === void 0 ? 0 : _ref2$stagger,
161 | _ref2$onBeforeElVisib = _ref2.onBeforeElVisible,
162 | onBeforeElVisible = _ref2$onBeforeElVisib === void 0 ? function () {} : _ref2$onBeforeElVisib,
163 | _ref2$onElVisible = _ref2.onElVisible,
164 | onElVisible = _ref2$onElVisible === void 0 ? function () {} : _ref2$onElVisible,
165 | _ref2$onElHidden = _ref2.onElHidden,
166 | onElHidden = _ref2$onElHidden === void 0 ? function () {} : _ref2$onElHidden;
167 | var els = elements.length ? elements : document.querySelectorAll(selector); // add elements to our els array and start observing them
168 |
169 | for (var i = 0; i < els.length; i++) {
170 | this.els.push({
171 | el: els[i],
172 | // HTML element
173 | keepObserving: keepObserving,
174 | triggerRatio: Math.max(0, Math.min(1, triggerRatio)) || 0,
175 | alwaysTrigger: alwaysTrigger,
176 | ratio: 0,
177 | stagger: stagger,
178 | onBeforeElVisible: onBeforeElVisible,
179 | onElVisible: onElVisible,
180 | onElHidden: onElHidden,
181 | inView: false,
182 | // not in view on init
183 | boundingClientRect: {
184 | x: 0,
185 | y: 0,
186 | width: 0,
187 | height: 0,
188 | top: 0,
189 | right: 0,
190 | bottom: 0,
191 | left: 0
192 | }
193 | }); // observe our HTML element
194 |
195 | this.observer.observe(els[i]);
196 | }
197 | }
198 | /***
199 | Unobserve a cached element and remove it from our els array
200 | params:
201 | @element (object): an element of our els array
202 | ***/
203 |
204 | }, {
205 | key: "_unobserve",
206 | value: function _unobserve(element) {
207 | // unobserve HTML element
208 | this.observer.unobserve(element.el); // remove element from our els array
209 |
210 | this.els = this.els.filter(function (data) {
211 | return !data.el.isSameNode(element.el);
212 | });
213 | }
214 | /***
215 | Unobserve a cached element by specifying its HTML element property
216 | params:
217 | @htmlElement (HTML element): a HTML element to stop watching
218 | ***/
219 |
220 | }, {
221 | key: "unobserveEl",
222 | value: function unobserveEl(htmlElement) {
223 | // find our HTML element in our array els array and unobserve it
224 | var cachedEl = this.els.find(function (data) {
225 | return data.el.isSameNode(htmlElement);
226 | });
227 |
228 | if (cachedEl) {
229 | this._unobserve(cachedEl);
230 | }
231 | }
232 | /***
233 | Unobserve a cached element by specifying its HTML element property
234 | params:
235 | @htmlElements (HTML elements): an array of HTML elements to stop watching
236 | ***/
237 |
238 | }, {
239 | key: "unobserveEls",
240 | value: function unobserveEls(htmlElements) {
241 | for (var i = 0; i < htmlElements.length; i++) {
242 | this.unobserveEl(htmlElements[i]);
243 | }
244 | }
245 | /***
246 | Unobserve all elements
247 | ***/
248 |
249 | }, {
250 | key: "unobserveAll",
251 | value: function unobserveAll() {
252 | this.observer.disconnect(); // reset our els array
253 |
254 | this.els = [];
255 | }
256 | /*** EVENTS ***/
257 |
258 | /***
259 | This is called if the intersection observer API not supported
260 | params:
261 | @callback (function): a function to execute
262 | returns:
263 | @this: our ScrollObserver element to handle chaining
264 | ***/
265 |
266 | }, {
267 | key: "onError",
268 | value: function onError(callback) {
269 | if (callback) {
270 | this._onErrorCallback = callback;
271 | }
272 |
273 | return this;
274 | }
275 | /***
276 | This is called when an element state switches from hidden to visible
277 | params:
278 | @callback (function): a function to execute
279 | returns:
280 | @this: our ScrollObserver element to handle chaining
281 | ***/
282 |
283 | }, {
284 | key: "onBeforeElVisible",
285 | value: function onBeforeElVisible(callback) {
286 | if (callback) {
287 | this._onBeforeElVisibleCallback = callback;
288 | }
289 |
290 | return this;
291 | }
292 | /***
293 | This is called when an element state switches from hidden to visible
294 | params:
295 | @callback (function): a function to execute
296 | returns:
297 | @this: our ScrollObserver element to handle chaining
298 | ***/
299 |
300 | }, {
301 | key: "onElVisible",
302 | value: function onElVisible(callback) {
303 | if (callback) {
304 | this._onElVisibleCallback = callback;
305 | }
306 |
307 | return this;
308 | }
309 | /***
310 | This is called when an element state switches from visible to hidden
311 | params:
312 | @callback (function): a function to execute
313 | returns:
314 | @this: our ScrollObserver element to handle chaining
315 | ***/
316 |
317 | }, {
318 | key: "onElHidden",
319 | value: function onElHidden(callback) {
320 | if (callback) {
321 | this._onElHiddenCallback = callback;
322 | }
323 |
324 | return this;
325 | }
326 | }]);
327 |
328 | return ScrollObserver;
329 | }();
330 |
331 | exports.ScrollObserver = ScrollObserver;
332 | Object.defineProperty(exports, '__esModule', {
333 | value: true
334 | });
335 | });
336 |
--------------------------------------------------------------------------------
/dist/scroll.observer.umd.min.js:
--------------------------------------------------------------------------------
1 | function _classCallCheck(e,i){if(!(e instanceof i))throw new TypeError("Cannot call a class as a function")}function _defineProperties(e,i){for(var o=0;ot.triggerRatio){if(t.alwaysTrigger||!t.inView){t.onBeforeElVisible&&t.onBeforeElVisible(t),i._onBeforeElVisibleCallback&&i._onBeforeElVisibleCallback(t);var n=t.stagger;i._staggerEls.forEach((function(e){e.inView&&(n+=e.stagger)})),i._staggerEls.push(t),setTimeout((function(){t.onElVisible&&t.onElVisible(t),i._onElVisibleCallback&&i._onElVisibleCallback(t),i._staggerEls=i._staggerEls.filter((function(e){return t.id!==e.id}))}),n)}t.inView=!0}else t.inView&&e.intersectionRatio<=t.triggerRatio&&(t.inView=!1,t.onElHidden&&t.onElHidden(t),i._onElHiddenCallback&&i._onElHiddenCallback(t),t.keepObserving||i._unobserve(t));t.ratio=e.intersectionRatio,t.boundingClientRect=e.boundingClientRect}}))}},{key:"observe",value:function(e){for(var i=e.elements,o=void 0===i?[]:i,t=e.selector,n=e.keepObserving,r=void 0!==n&&n,l=e.triggerRatio,s=void 0===l?0:l,a=e.alwaysTrigger,u=void 0===a||a,c=e.stagger,f=void 0===c?0:c,b=e.onBeforeElVisible,d=void 0===b?function(){}:b,h=e.onElVisible,v=void 0===h?function(){}:h,g=e.onElHidden,y=void 0===g?function(){}:g,E=o.length?o:document.querySelectorAll(t),p=0;p {
49 | this._onErrorCallback && this._onErrorCallback();
50 | }, 0);
51 | }
52 | }
53 |
54 |
55 | /***
56 | This is our intersection observer callback
57 | called each time an entry crosses a threshold
58 | Can call callback functions based on element parameters
59 | ***/
60 | _callback(entries) {
61 | entries.forEach((entry, index) => {
62 | // find our entry in our cache elements array
63 | const cachedEl = this.els.find(data => data.el.isSameNode(entry.target));
64 |
65 | if(cachedEl) {
66 | // if intersection ratio is bigger than the triggerRatio property
67 | if(entry.intersectionRatio > cachedEl.triggerRatio) {
68 | // if we should always trigger it or if visibility hasn't been triggered yet
69 | if(cachedEl.alwaysTrigger || !cachedEl.inView) {
70 | // on before el visible callback
71 | cachedEl.onBeforeElVisible && cachedEl.onBeforeElVisible(cachedEl);
72 | this._onBeforeElVisibleCallback && this._onBeforeElVisibleCallback(cachedEl);
73 |
74 | // calculate staggering timeout
75 | let staggerTimeout = cachedEl.stagger;
76 | this._staggerEls.forEach(el => {
77 | if(el.inView) {
78 | staggerTimeout += el.stagger;
79 | }
80 | });
81 |
82 | this._staggerEls.push(cachedEl);
83 |
84 | // apply staggering timeout
85 | setTimeout(() => {
86 | cachedEl.onElVisible && cachedEl.onElVisible(cachedEl);
87 | this._onElVisibleCallback && this._onElVisibleCallback(cachedEl);
88 |
89 | // remove from the staggering els array
90 | this._staggerEls = this._staggerEls.filter(el => cachedEl.id !== el.id);
91 |
92 | }, staggerTimeout);
93 | }
94 |
95 | // element is now in view
96 | cachedEl.inView = true;
97 | }
98 | else if(cachedEl.inView && entry.intersectionRatio <= cachedEl.triggerRatio) {
99 | // if intersection ratio is smaller than our trigger ratio and our element is visible
100 |
101 | // element is no more visible
102 | cachedEl.inView = false;
103 | cachedEl.onElHidden && cachedEl.onElHidden(cachedEl);
104 | this._onElHiddenCallback && this._onElHiddenCallback(cachedEl);
105 |
106 | // if we should observe it just once, unobserve it now
107 | if(!cachedEl.keepObserving) {
108 | this._unobserve(cachedEl);
109 | }
110 | }
111 |
112 | // update its ratio property
113 | cachedEl.ratio = entry.intersectionRatio;
114 | // update its boundingClientRect object
115 | cachedEl.boundingClientRect = entry.boundingClientRect;
116 | }
117 | });
118 | }
119 |
120 |
121 | /***
122 | Start observing a set of elements
123 |
124 | params:
125 | @elements (array of HTML elements): elements to observe
126 | @selector (CSS selector): elements to query based on a CSS selector
127 | @keepObserving (bool): whether we should keep observing the element after it has been in and out of view, default to false
128 | @triggerRatio (float between 0 and 1): the ratio at which the visible/hidden callback should be called, default to 0
129 | @alwaysTrigger (bool): whether it should trigger the visible callback multiple times or just once, default to true
130 | @stagger (int): number of milliseconds to wait before calling the visible callback (used for staggering animations), default to 0
131 |
132 | @onBeforeElVisible (function): function to execute right before an element onElVisible callback is called, used it to change its stagger property for example
133 | @onElVisible (function): function to execute when an element become visible
134 | @onElHidden (function): function to execute when an element become hidden
135 | ***/
136 | observe({
137 | elements = [],
138 | selector,
139 | keepObserving = false,
140 | triggerRatio = 0,
141 | alwaysTrigger = true,
142 |
143 | stagger = 0,
144 |
145 | onBeforeElVisible = () => {},
146 | onElVisible = () => {},
147 | onElHidden = () => {},
148 | }) {
149 | const els = elements.length ? elements : document.querySelectorAll(selector);
150 |
151 | // add elements to our els array and start observing them
152 | for(let i = 0; i < els.length; i++) {
153 | this.els.push({
154 | el: els[i], // HTML element
155 | keepObserving: keepObserving,
156 | triggerRatio: Math.max(0, Math.min(1, triggerRatio)) || 0,
157 | alwaysTrigger: alwaysTrigger,
158 | ratio: 0,
159 | stagger: stagger,
160 | onBeforeElVisible: onBeforeElVisible,
161 | onElVisible: onElVisible,
162 | onElHidden: onElHidden,
163 | inView: false, // not in view on init
164 | boundingClientRect: {
165 | x: 0,
166 | y: 0,
167 | width: 0,
168 | height: 0,
169 | top: 0,
170 | right: 0,
171 | bottom: 0,
172 | left: 0,
173 | },
174 | });
175 | // observe our HTML element
176 | this.observer.observe(els[i]);
177 | }
178 | }
179 |
180 |
181 | /***
182 | Unobserve a cached element and remove it from our els array
183 |
184 | params:
185 | @element (object): an element of our els array
186 | ***/
187 | _unobserve(element) {
188 | // unobserve HTML element
189 | this.observer.unobserve(element.el);
190 |
191 | // remove element from our els array
192 | this.els = this.els.filter(data => !data.el.isSameNode(element.el));
193 | }
194 |
195 |
196 | /***
197 | Unobserve a cached element by specifying its HTML element property
198 |
199 | params:
200 | @htmlElement (HTML element): a HTML element to stop watching
201 | ***/
202 | unobserveEl(htmlElement) {
203 | // find our HTML element in our array els array and unobserve it
204 | const cachedEl = this.els.find(data => data.el.isSameNode(htmlElement));
205 | if(cachedEl) {
206 | this._unobserve(cachedEl);
207 | }
208 | }
209 |
210 |
211 | /***
212 | Unobserve a cached element by specifying its HTML element property
213 |
214 | params:
215 | @htmlElements (HTML elements): an array of HTML elements to stop watching
216 | ***/
217 | unobserveEls(htmlElements) {
218 | for(let i = 0; i < htmlElements.length; i++) {
219 | this.unobserveEl(htmlElements[i]);
220 | }
221 | }
222 |
223 |
224 | /***
225 | Unobserve all elements
226 | ***/
227 | unobserveAll() {
228 | this.observer.disconnect();
229 |
230 | // reset our els array
231 | this.els = [];
232 | }
233 |
234 |
235 |
236 | /*** EVENTS ***/
237 |
238 | /***
239 | This is called if the intersection observer API not supported
240 |
241 | params:
242 | @callback (function): a function to execute
243 |
244 | returns:
245 | @this: our ScrollObserver element to handle chaining
246 | ***/
247 | onError(callback) {
248 | if(callback) {
249 | this._onErrorCallback = callback;
250 | }
251 |
252 | return this;
253 | }
254 |
255 |
256 | /***
257 | This is called when an element state switches from hidden to visible
258 |
259 | params:
260 | @callback (function): a function to execute
261 |
262 | returns:
263 | @this: our ScrollObserver element to handle chaining
264 | ***/
265 | onBeforeElVisible(callback) {
266 | if(callback) {
267 | this._onBeforeElVisibleCallback = callback;
268 | }
269 |
270 | return this;
271 | }
272 |
273 |
274 | /***
275 | This is called when an element state switches from hidden to visible
276 |
277 | params:
278 | @callback (function): a function to execute
279 |
280 | returns:
281 | @this: our ScrollObserver element to handle chaining
282 | ***/
283 | onElVisible(callback) {
284 | if(callback) {
285 | this._onElVisibleCallback = callback;
286 | }
287 |
288 | return this;
289 | }
290 |
291 |
292 | /***
293 | This is called when an element state switches from visible to hidden
294 |
295 | params:
296 | @callback (function): a function to execute
297 |
298 | returns:
299 | @this: our ScrollObserver element to handle chaining
300 | ***/
301 | onElHidden(callback) {
302 | if(callback) {
303 | this._onElHiddenCallback = callback;
304 | }
305 |
306 | return this;
307 | }
308 | }
--------------------------------------------------------------------------------