intersection-observer-polyfill-rails 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e3d172ac7a99592be1bd7c9d23b12c39ccc212ac
4
+ data.tar.gz: 14c54a2eb8d6406d109c87d2d197ba2411157030
5
+ SHA512:
6
+ metadata.gz: 441a5cde2996c8175e512a713e05635efe77d33fd7fe0c4ac6a4fec085acd232859687c69cd67146ed47db93a6e8517cc6a8d53c44d7d9b323fcaaef462fec96
7
+ data.tar.gz: cef9492eef2534b754132c0a0e500921f2a1fb69c9917143b410f6ba4bb4b569f535ce7a63157ec7d96d0f77325c7b2c5da57f35f9eb4effc30f24d781f813d5
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 W3C
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,15 @@
1
+ # intersection-observer-polyfill-rails
2
+
3
+ [Intersection Observer](https://github.com/w3c/IntersectionObserver/tree/master/polyfill) JavaScript polyfill, written and maintained by the [w3c](https://github.com/w3c), packaged for the Rails asset pipeline.
4
+
5
+ ## Usage
6
+
7
+ Add the following to your JavaScript application manifest:
8
+
9
+ ```
10
+ //= require intersection-observer
11
+ ```
12
+
13
+ ## Disclaimer
14
+
15
+ This repo is a Rails gem package. All issues with the library should be reported on the library's [GitHub page](https://github.com/w3c/IntersectionObserver/tree/master/polyfill). This repo will be updated each time a new version of the library is released.
@@ -0,0 +1,8 @@
1
+ require 'intersection-observer-polyfill-rails/version'
2
+
3
+ module IntersectionObserverPolyfillRails
4
+ module Rails
5
+ class Engine < ::Rails::Engine
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,5 @@
1
+ module IntersectionObserverPolyfillRails
2
+ module Rails
3
+ VERSION = '0.5.0'
4
+ end
5
+ end
@@ -0,0 +1,724 @@
1
+ /**
2
+ * Copyright 2016 Google Inc. All Rights Reserved.
3
+ *
4
+ * Licensed under the W3C SOFTWARE AND DOCUMENT NOTICE AND LICENSE.
5
+ *
6
+ * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document
7
+ *
8
+ */
9
+
10
+ (function(window, document) {
11
+ 'use strict';
12
+
13
+
14
+ // Exits early if all IntersectionObserver and IntersectionObserverEntry
15
+ // features are natively supported.
16
+ if ('IntersectionObserver' in window &&
17
+ 'IntersectionObserverEntry' in window &&
18
+ 'intersectionRatio' in window.IntersectionObserverEntry.prototype) {
19
+
20
+ // Minimal polyfill for Edge 15's lack of `isIntersecting`
21
+ // See: https://github.com/w3c/IntersectionObserver/issues/211
22
+ if (!('isIntersecting' in window.IntersectionObserverEntry.prototype)) {
23
+ Object.defineProperty(window.IntersectionObserverEntry.prototype,
24
+ 'isIntersecting', {
25
+ get: function () {
26
+ return this.intersectionRatio > 0;
27
+ }
28
+ });
29
+ }
30
+ return;
31
+ }
32
+
33
+
34
+ /**
35
+ * An IntersectionObserver registry. This registry exists to hold a strong
36
+ * reference to IntersectionObserver instances currently observering a target
37
+ * element. Without this registry, instances without another reference may be
38
+ * garbage collected.
39
+ */
40
+ var registry = [];
41
+
42
+
43
+ /**
44
+ * Creates the global IntersectionObserverEntry constructor.
45
+ * https://w3c.github.io/IntersectionObserver/#intersection-observer-entry
46
+ * @param {Object} entry A dictionary of instance properties.
47
+ * @constructor
48
+ */
49
+ function IntersectionObserverEntry(entry) {
50
+ this.time = entry.time;
51
+ this.target = entry.target;
52
+ this.rootBounds = entry.rootBounds;
53
+ this.boundingClientRect = entry.boundingClientRect;
54
+ this.intersectionRect = entry.intersectionRect || getEmptyRect();
55
+ this.isIntersecting = !!entry.intersectionRect;
56
+
57
+ // Calculates the intersection ratio.
58
+ var targetRect = this.boundingClientRect;
59
+ var targetArea = targetRect.width * targetRect.height;
60
+ var intersectionRect = this.intersectionRect;
61
+ var intersectionArea = intersectionRect.width * intersectionRect.height;
62
+
63
+ // Sets intersection ratio.
64
+ if (targetArea) {
65
+ this.intersectionRatio = intersectionArea / targetArea;
66
+ } else {
67
+ // If area is zero and is intersecting, sets to 1, otherwise to 0
68
+ this.intersectionRatio = this.isIntersecting ? 1 : 0;
69
+ }
70
+ }
71
+
72
+
73
+ /**
74
+ * Creates the global IntersectionObserver constructor.
75
+ * https://w3c.github.io/IntersectionObserver/#intersection-observer-interface
76
+ * @param {Function} callback The function to be invoked after intersection
77
+ * changes have queued. The function is not invoked if the queue has
78
+ * been emptied by calling the `takeRecords` method.
79
+ * @param {Object=} opt_options Optional configuration options.
80
+ * @constructor
81
+ */
82
+ function IntersectionObserver(callback, opt_options) {
83
+
84
+ var options = opt_options || {};
85
+
86
+ if (typeof callback != 'function') {
87
+ throw new Error('callback must be a function');
88
+ }
89
+
90
+ if (options.root && options.root.nodeType != 1) {
91
+ throw new Error('root must be an Element');
92
+ }
93
+
94
+ // Binds and throttles `this._checkForIntersections`.
95
+ this._checkForIntersections = throttle(
96
+ this._checkForIntersections.bind(this), this.THROTTLE_TIMEOUT);
97
+
98
+ // Private properties.
99
+ this._callback = callback;
100
+ this._observationTargets = [];
101
+ this._queuedEntries = [];
102
+ this._rootMarginValues = this._parseRootMargin(options.rootMargin);
103
+
104
+ // Public properties.
105
+ this.thresholds = this._initThresholds(options.threshold);
106
+ this.root = options.root || null;
107
+ this.rootMargin = this._rootMarginValues.map(function(margin) {
108
+ return margin.value + margin.unit;
109
+ }).join(' ');
110
+ }
111
+
112
+
113
+ /**
114
+ * The minimum interval within which the document will be checked for
115
+ * intersection changes.
116
+ */
117
+ IntersectionObserver.prototype.THROTTLE_TIMEOUT = 100;
118
+
119
+
120
+ /**
121
+ * The frequency in which the polyfill polls for intersection changes.
122
+ * this can be updated on a per instance basis and must be set prior to
123
+ * calling `observe` on the first target.
124
+ */
125
+ IntersectionObserver.prototype.POLL_INTERVAL = null;
126
+
127
+ /**
128
+ * Use a mutation observer on the root element
129
+ * to detect intersection changes.
130
+ */
131
+ IntersectionObserver.prototype.USE_MUTATION_OBSERVER = true;
132
+
133
+
134
+ /**
135
+ * Starts observing a target element for intersection changes based on
136
+ * the thresholds values.
137
+ * @param {Element} target The DOM element to observe.
138
+ */
139
+ IntersectionObserver.prototype.observe = function(target) {
140
+ var isTargetAlreadyObserved = this._observationTargets.some(function(item) {
141
+ return item.element == target;
142
+ });
143
+
144
+ if (isTargetAlreadyObserved) {
145
+ return;
146
+ }
147
+
148
+ if (!(target && target.nodeType == 1)) {
149
+ throw new Error('target must be an Element');
150
+ }
151
+
152
+ this._registerInstance();
153
+ this._observationTargets.push({element: target, entry: null});
154
+ this._monitorIntersections();
155
+ this._checkForIntersections();
156
+ };
157
+
158
+
159
+ /**
160
+ * Stops observing a target element for intersection changes.
161
+ * @param {Element} target The DOM element to observe.
162
+ */
163
+ IntersectionObserver.prototype.unobserve = function(target) {
164
+ this._observationTargets =
165
+ this._observationTargets.filter(function(item) {
166
+
167
+ return item.element != target;
168
+ });
169
+ if (!this._observationTargets.length) {
170
+ this._unmonitorIntersections();
171
+ this._unregisterInstance();
172
+ }
173
+ };
174
+
175
+
176
+ /**
177
+ * Stops observing all target elements for intersection changes.
178
+ */
179
+ IntersectionObserver.prototype.disconnect = function() {
180
+ this._observationTargets = [];
181
+ this._unmonitorIntersections();
182
+ this._unregisterInstance();
183
+ };
184
+
185
+
186
+ /**
187
+ * Returns any queue entries that have not yet been reported to the
188
+ * callback and clears the queue. This can be used in conjunction with the
189
+ * callback to obtain the absolute most up-to-date intersection information.
190
+ * @return {Array} The currently queued entries.
191
+ */
192
+ IntersectionObserver.prototype.takeRecords = function() {
193
+ var records = this._queuedEntries.slice();
194
+ this._queuedEntries = [];
195
+ return records;
196
+ };
197
+
198
+
199
+ /**
200
+ * Accepts the threshold value from the user configuration object and
201
+ * returns a sorted array of unique threshold values. If a value is not
202
+ * between 0 and 1 and error is thrown.
203
+ * @private
204
+ * @param {Array|number=} opt_threshold An optional threshold value or
205
+ * a list of threshold values, defaulting to [0].
206
+ * @return {Array} A sorted list of unique and valid threshold values.
207
+ */
208
+ IntersectionObserver.prototype._initThresholds = function(opt_threshold) {
209
+ var threshold = opt_threshold || [0];
210
+ if (!Array.isArray(threshold)) threshold = [threshold];
211
+
212
+ return threshold.sort().filter(function(t, i, a) {
213
+ if (typeof t != 'number' || isNaN(t) || t < 0 || t > 1) {
214
+ throw new Error('threshold must be a number between 0 and 1 inclusively');
215
+ }
216
+ return t !== a[i - 1];
217
+ });
218
+ };
219
+
220
+
221
+ /**
222
+ * Accepts the rootMargin value from the user configuration object
223
+ * and returns an array of the four margin values as an object containing
224
+ * the value and unit properties. If any of the values are not properly
225
+ * formatted or use a unit other than px or %, and error is thrown.
226
+ * @private
227
+ * @param {string=} opt_rootMargin An optional rootMargin value,
228
+ * defaulting to '0px'.
229
+ * @return {Array<Object>} An array of margin objects with the keys
230
+ * value and unit.
231
+ */
232
+ IntersectionObserver.prototype._parseRootMargin = function(opt_rootMargin) {
233
+ var marginString = opt_rootMargin || '0px';
234
+ var margins = marginString.split(/\s+/).map(function(margin) {
235
+ var parts = /^(-?\d*\.?\d+)(px|%)$/.exec(margin);
236
+ if (!parts) {
237
+ throw new Error('rootMargin must be specified in pixels or percent');
238
+ }
239
+ return {value: parseFloat(parts[1]), unit: parts[2]};
240
+ });
241
+
242
+ // Handles shorthand.
243
+ margins[1] = margins[1] || margins[0];
244
+ margins[2] = margins[2] || margins[0];
245
+ margins[3] = margins[3] || margins[1];
246
+
247
+ return margins;
248
+ };
249
+
250
+
251
+ /**
252
+ * Starts polling for intersection changes if the polling is not already
253
+ * happening, and if the page's visibilty state is visible.
254
+ * @private
255
+ */
256
+ IntersectionObserver.prototype._monitorIntersections = function() {
257
+ if (!this._monitoringIntersections) {
258
+ this._monitoringIntersections = true;
259
+
260
+ // If a poll interval is set, use polling instead of listening to
261
+ // resize and scroll events or DOM mutations.
262
+ if (this.POLL_INTERVAL) {
263
+ this._monitoringInterval = setInterval(
264
+ this._checkForIntersections, this.POLL_INTERVAL);
265
+ }
266
+ else {
267
+ addEvent(window, 'resize', this._checkForIntersections, true);
268
+ addEvent(document, 'scroll', this._checkForIntersections, true);
269
+
270
+ if (this.USE_MUTATION_OBSERVER && 'MutationObserver' in window) {
271
+ this._domObserver = new MutationObserver(this._checkForIntersections);
272
+ this._domObserver.observe(document, {
273
+ attributes: true,
274
+ childList: true,
275
+ characterData: true,
276
+ subtree: true
277
+ });
278
+ }
279
+ }
280
+ }
281
+ };
282
+
283
+
284
+ /**
285
+ * Stops polling for intersection changes.
286
+ * @private
287
+ */
288
+ IntersectionObserver.prototype._unmonitorIntersections = function() {
289
+ if (this._monitoringIntersections) {
290
+ this._monitoringIntersections = false;
291
+
292
+ clearInterval(this._monitoringInterval);
293
+ this._monitoringInterval = null;
294
+
295
+ removeEvent(window, 'resize', this._checkForIntersections, true);
296
+ removeEvent(document, 'scroll', this._checkForIntersections, true);
297
+
298
+ if (this._domObserver) {
299
+ this._domObserver.disconnect();
300
+ this._domObserver = null;
301
+ }
302
+ }
303
+ };
304
+
305
+
306
+ /**
307
+ * Scans each observation target for intersection changes and adds them
308
+ * to the internal entries queue. If new entries are found, it
309
+ * schedules the callback to be invoked.
310
+ * @private
311
+ */
312
+ IntersectionObserver.prototype._checkForIntersections = function() {
313
+ var rootIsInDom = this._rootIsInDom();
314
+ var rootRect = rootIsInDom ? this._getRootRect() : getEmptyRect();
315
+
316
+ this._observationTargets.forEach(function(item) {
317
+ var target = item.element;
318
+ var targetRect = getBoundingClientRect(target);
319
+ var rootContainsTarget = this._rootContainsTarget(target);
320
+ var oldEntry = item.entry;
321
+ var intersectionRect = rootIsInDom && rootContainsTarget &&
322
+ this._computeTargetAndRootIntersection(target, rootRect);
323
+
324
+ var newEntry = item.entry = new IntersectionObserverEntry({
325
+ time: now(),
326
+ target: target,
327
+ boundingClientRect: targetRect,
328
+ rootBounds: rootRect,
329
+ intersectionRect: intersectionRect
330
+ });
331
+
332
+ if (!oldEntry) {
333
+ this._queuedEntries.push(newEntry);
334
+ } else if (rootIsInDom && rootContainsTarget) {
335
+ // If the new entry intersection ratio has crossed any of the
336
+ // thresholds, add a new entry.
337
+ if (this._hasCrossedThreshold(oldEntry, newEntry)) {
338
+ this._queuedEntries.push(newEntry);
339
+ }
340
+ } else {
341
+ // If the root is not in the DOM or target is not contained within
342
+ // root but the previous entry for this target had an intersection,
343
+ // add a new record indicating removal.
344
+ if (oldEntry && oldEntry.isIntersecting) {
345
+ this._queuedEntries.push(newEntry);
346
+ }
347
+ }
348
+ }, this);
349
+
350
+ if (this._queuedEntries.length) {
351
+ this._callback(this.takeRecords(), this);
352
+ }
353
+ };
354
+
355
+
356
+ /**
357
+ * Accepts a target and root rect computes the intersection between then
358
+ * following the algorithm in the spec.
359
+ * TODO(philipwalton): at this time clip-path is not considered.
360
+ * https://w3c.github.io/IntersectionObserver/#calculate-intersection-rect-algo
361
+ * @param {Element} target The target DOM element
362
+ * @param {Object} rootRect The bounding rect of the root after being
363
+ * expanded by the rootMargin value.
364
+ * @return {?Object} The final intersection rect object or undefined if no
365
+ * intersection is found.
366
+ * @private
367
+ */
368
+ IntersectionObserver.prototype._computeTargetAndRootIntersection =
369
+ function(target, rootRect) {
370
+
371
+ // If the element isn't displayed, an intersection can't happen.
372
+ if (window.getComputedStyle(target).display == 'none') return;
373
+
374
+ var targetRect = getBoundingClientRect(target);
375
+ var intersectionRect = targetRect;
376
+ var parent = getParentNode(target);
377
+ var atRoot = false;
378
+
379
+ while (!atRoot) {
380
+ var parentRect = null;
381
+ var parentComputedStyle = parent.nodeType == 1 ?
382
+ window.getComputedStyle(parent) : {};
383
+
384
+ // If the parent isn't displayed, an intersection can't happen.
385
+ if (parentComputedStyle.display == 'none') return;
386
+
387
+ if (parent == this.root || parent == document) {
388
+ atRoot = true;
389
+ parentRect = rootRect;
390
+ } else {
391
+ // If the element has a non-visible overflow, and it's not the <body>
392
+ // or <html> element, update the intersection rect.
393
+ // Note: <body> and <html> cannot be clipped to a rect that's not also
394
+ // the document rect, so no need to compute a new intersection.
395
+ if (parent != document.body &&
396
+ parent != document.documentElement &&
397
+ parentComputedStyle.overflow != 'visible') {
398
+ parentRect = getBoundingClientRect(parent);
399
+ }
400
+ }
401
+
402
+ // If either of the above conditionals set a new parentRect,
403
+ // calculate new intersection data.
404
+ if (parentRect) {
405
+ intersectionRect = computeRectIntersection(parentRect, intersectionRect);
406
+
407
+ if (!intersectionRect) break;
408
+ }
409
+ parent = getParentNode(parent);
410
+ }
411
+ return intersectionRect;
412
+ };
413
+
414
+
415
+ /**
416
+ * Returns the root rect after being expanded by the rootMargin value.
417
+ * @return {Object} The expanded root rect.
418
+ * @private
419
+ */
420
+ IntersectionObserver.prototype._getRootRect = function() {
421
+ var rootRect;
422
+ if (this.root) {
423
+ rootRect = getBoundingClientRect(this.root);
424
+ } else {
425
+ // Use <html>/<body> instead of window since scroll bars affect size.
426
+ var html = document.documentElement;
427
+ var body = document.body;
428
+ rootRect = {
429
+ top: 0,
430
+ left: 0,
431
+ right: html.clientWidth || body.clientWidth,
432
+ width: html.clientWidth || body.clientWidth,
433
+ bottom: html.clientHeight || body.clientHeight,
434
+ height: html.clientHeight || body.clientHeight
435
+ };
436
+ }
437
+ return this._expandRectByRootMargin(rootRect);
438
+ };
439
+
440
+
441
+ /**
442
+ * Accepts a rect and expands it by the rootMargin value.
443
+ * @param {Object} rect The rect object to expand.
444
+ * @return {Object} The expanded rect.
445
+ * @private
446
+ */
447
+ IntersectionObserver.prototype._expandRectByRootMargin = function(rect) {
448
+ var margins = this._rootMarginValues.map(function(margin, i) {
449
+ return margin.unit == 'px' ? margin.value :
450
+ margin.value * (i % 2 ? rect.width : rect.height) / 100;
451
+ });
452
+ var newRect = {
453
+ top: rect.top - margins[0],
454
+ right: rect.right + margins[1],
455
+ bottom: rect.bottom + margins[2],
456
+ left: rect.left - margins[3]
457
+ };
458
+ newRect.width = newRect.right - newRect.left;
459
+ newRect.height = newRect.bottom - newRect.top;
460
+
461
+ return newRect;
462
+ };
463
+
464
+
465
+ /**
466
+ * Accepts an old and new entry and returns true if at least one of the
467
+ * threshold values has been crossed.
468
+ * @param {?IntersectionObserverEntry} oldEntry The previous entry for a
469
+ * particular target element or null if no previous entry exists.
470
+ * @param {IntersectionObserverEntry} newEntry The current entry for a
471
+ * particular target element.
472
+ * @return {boolean} Returns true if a any threshold has been crossed.
473
+ * @private
474
+ */
475
+ IntersectionObserver.prototype._hasCrossedThreshold =
476
+ function(oldEntry, newEntry) {
477
+
478
+ // To make comparing easier, an entry that has a ratio of 0
479
+ // but does not actually intersect is given a value of -1
480
+ var oldRatio = oldEntry && oldEntry.isIntersecting ?
481
+ oldEntry.intersectionRatio || 0 : -1;
482
+ var newRatio = newEntry.isIntersecting ?
483
+ newEntry.intersectionRatio || 0 : -1;
484
+
485
+ // Ignore unchanged ratios
486
+ if (oldRatio === newRatio) return;
487
+
488
+ for (var i = 0; i < this.thresholds.length; i++) {
489
+ var threshold = this.thresholds[i];
490
+
491
+ // Return true if an entry matches a threshold or if the new ratio
492
+ // and the old ratio are on the opposite sides of a threshold.
493
+ if (threshold == oldRatio || threshold == newRatio ||
494
+ threshold < oldRatio !== threshold < newRatio) {
495
+ return true;
496
+ }
497
+ }
498
+ };
499
+
500
+
501
+ /**
502
+ * Returns whether or not the root element is an element and is in the DOM.
503
+ * @return {boolean} True if the root element is an element and is in the DOM.
504
+ * @private
505
+ */
506
+ IntersectionObserver.prototype._rootIsInDom = function() {
507
+ return !this.root || containsDeep(document, this.root);
508
+ };
509
+
510
+
511
+ /**
512
+ * Returns whether or not the target element is a child of root.
513
+ * @param {Element} target The target element to check.
514
+ * @return {boolean} True if the target element is a child of root.
515
+ * @private
516
+ */
517
+ IntersectionObserver.prototype._rootContainsTarget = function(target) {
518
+ return containsDeep(this.root || document, target);
519
+ };
520
+
521
+
522
+ /**
523
+ * Adds the instance to the global IntersectionObserver registry if it isn't
524
+ * already present.
525
+ * @private
526
+ */
527
+ IntersectionObserver.prototype._registerInstance = function() {
528
+ if (registry.indexOf(this) < 0) {
529
+ registry.push(this);
530
+ }
531
+ };
532
+
533
+
534
+ /**
535
+ * Removes the instance from the global IntersectionObserver registry.
536
+ * @private
537
+ */
538
+ IntersectionObserver.prototype._unregisterInstance = function() {
539
+ var index = registry.indexOf(this);
540
+ if (index != -1) registry.splice(index, 1);
541
+ };
542
+
543
+
544
+ /**
545
+ * Returns the result of the performance.now() method or null in browsers
546
+ * that don't support the API.
547
+ * @return {number} The elapsed time since the page was requested.
548
+ */
549
+ function now() {
550
+ return window.performance && performance.now && performance.now();
551
+ }
552
+
553
+
554
+ /**
555
+ * Throttles a function and delays its executiong, so it's only called at most
556
+ * once within a given time period.
557
+ * @param {Function} fn The function to throttle.
558
+ * @param {number} timeout The amount of time that must pass before the
559
+ * function can be called again.
560
+ * @return {Function} The throttled function.
561
+ */
562
+ function throttle(fn, timeout) {
563
+ var timer = null;
564
+ return function () {
565
+ if (!timer) {
566
+ timer = setTimeout(function() {
567
+ fn();
568
+ timer = null;
569
+ }, timeout);
570
+ }
571
+ };
572
+ }
573
+
574
+
575
+ /**
576
+ * Adds an event handler to a DOM node ensuring cross-browser compatibility.
577
+ * @param {Node} node The DOM node to add the event handler to.
578
+ * @param {string} event The event name.
579
+ * @param {Function} fn The event handler to add.
580
+ * @param {boolean} opt_useCapture Optionally adds the even to the capture
581
+ * phase. Note: this only works in modern browsers.
582
+ */
583
+ function addEvent(node, event, fn, opt_useCapture) {
584
+ if (typeof node.addEventListener == 'function') {
585
+ node.addEventListener(event, fn, opt_useCapture || false);
586
+ }
587
+ else if (typeof node.attachEvent == 'function') {
588
+ node.attachEvent('on' + event, fn);
589
+ }
590
+ }
591
+
592
+
593
+ /**
594
+ * Removes a previously added event handler from a DOM node.
595
+ * @param {Node} node The DOM node to remove the event handler from.
596
+ * @param {string} event The event name.
597
+ * @param {Function} fn The event handler to remove.
598
+ * @param {boolean} opt_useCapture If the event handler was added with this
599
+ * flag set to true, it should be set to true here in order to remove it.
600
+ */
601
+ function removeEvent(node, event, fn, opt_useCapture) {
602
+ if (typeof node.removeEventListener == 'function') {
603
+ node.removeEventListener(event, fn, opt_useCapture || false);
604
+ }
605
+ else if (typeof node.detatchEvent == 'function') {
606
+ node.detatchEvent('on' + event, fn);
607
+ }
608
+ }
609
+
610
+
611
+ /**
612
+ * Returns the intersection between two rect objects.
613
+ * @param {Object} rect1 The first rect.
614
+ * @param {Object} rect2 The second rect.
615
+ * @return {?Object} The intersection rect or undefined if no intersection
616
+ * is found.
617
+ */
618
+ function computeRectIntersection(rect1, rect2) {
619
+ var top = Math.max(rect1.top, rect2.top);
620
+ var bottom = Math.min(rect1.bottom, rect2.bottom);
621
+ var left = Math.max(rect1.left, rect2.left);
622
+ var right = Math.min(rect1.right, rect2.right);
623
+ var width = right - left;
624
+ var height = bottom - top;
625
+
626
+ return (width >= 0 && height >= 0) && {
627
+ top: top,
628
+ bottom: bottom,
629
+ left: left,
630
+ right: right,
631
+ width: width,
632
+ height: height
633
+ };
634
+ }
635
+
636
+
637
+ /**
638
+ * Shims the native getBoundingClientRect for compatibility with older IE.
639
+ * @param {Element} el The element whose bounding rect to get.
640
+ * @return {Object} The (possibly shimmed) rect of the element.
641
+ */
642
+ function getBoundingClientRect(el) {
643
+ var rect;
644
+
645
+ try {
646
+ rect = el.getBoundingClientRect();
647
+ } catch (err) {
648
+ // Ignore Windows 7 IE11 "Unspecified error"
649
+ // https://github.com/w3c/IntersectionObserver/pull/205
650
+ }
651
+
652
+ if (!rect) return getEmptyRect();
653
+
654
+ // Older IE
655
+ if (!(rect.width && rect.height)) {
656
+ rect = {
657
+ top: rect.top,
658
+ right: rect.right,
659
+ bottom: rect.bottom,
660
+ left: rect.left,
661
+ width: rect.right - rect.left,
662
+ height: rect.bottom - rect.top
663
+ };
664
+ }
665
+ return rect;
666
+ }
667
+
668
+
669
+ /**
670
+ * Returns an empty rect object. An empty rect is returned when an element
671
+ * is not in the DOM.
672
+ * @return {Object} The empty rect.
673
+ */
674
+ function getEmptyRect() {
675
+ return {
676
+ top: 0,
677
+ bottom: 0,
678
+ left: 0,
679
+ right: 0,
680
+ width: 0,
681
+ height: 0
682
+ };
683
+ }
684
+
685
+ /**
686
+ * Checks to see if a parent element contains a child elemnt (including inside
687
+ * shadow DOM).
688
+ * @param {Node} parent The parent element.
689
+ * @param {Node} child The child element.
690
+ * @return {boolean} True if the parent node contains the child node.
691
+ */
692
+ function containsDeep(parent, child) {
693
+ var node = child;
694
+ while (node) {
695
+ if (node == parent) return true;
696
+
697
+ node = getParentNode(node);
698
+ }
699
+ return false;
700
+ }
701
+
702
+
703
+ /**
704
+ * Gets the parent node of an element or its host element if the parent node
705
+ * is a shadow root.
706
+ * @param {Node} node The node whose parent to get.
707
+ * @return {Node|null} The parent node or null if no parent exists.
708
+ */
709
+ function getParentNode(node) {
710
+ var parent = node.parentNode;
711
+
712
+ if (parent && parent.nodeType == 11 && parent.host) {
713
+ // If the parent is a shadow root, return the host element.
714
+ return parent.host;
715
+ }
716
+ return parent;
717
+ }
718
+
719
+
720
+ // Exposes the constructors globally.
721
+ window.IntersectionObserver = IntersectionObserver;
722
+ window.IntersectionObserverEntry = IntersectionObserverEntry;
723
+
724
+ }(window, document));
metadata ADDED
@@ -0,0 +1,52 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: intersection-observer-polyfill-rails
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.5.0
5
+ platform: ruby
6
+ authors:
7
+ - Curt Howard
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-08-02 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: |2
14
+ A build of the Intersection Observer Polyfill, written and maintained by the
15
+ W3C, packaged for the Rails asset pipeline.
16
+ email:
17
+ - choward@weblinc.com
18
+ executables: []
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - LICENSE
23
+ - README.md
24
+ - lib/intersection-observer-polyfill-rails.rb
25
+ - lib/intersection-observer-polyfill-rails/version.rb
26
+ - vendor/assets/javascripts/intersection-observer.js
27
+ homepage: https://github.com/meowsus/intersection-observer-polyfill-rails
28
+ licenses:
29
+ - MIT
30
+ metadata: {}
31
+ post_install_message:
32
+ rdoc_options: []
33
+ require_paths:
34
+ - lib
35
+ required_ruby_version: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ required_rubygems_version: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ requirements: []
46
+ rubyforge_project:
47
+ rubygems_version: 2.6.14.1
48
+ signing_key:
49
+ specification_version: 4
50
+ summary: A build of the Intersection Observer Polyfill, written and maintained by
51
+ the W3C, packaged for the Rails asset pipeline.
52
+ test_files: []