From e69581f26277d589221ea019bda42f2c61ba612c Mon Sep 17 00:00:00 2001 From: Surma Date: Wed, 16 Mar 2016 14:05:44 +0000 Subject: [PATCH 01/13] Add IntersectionObserver polyfill --- polyfill/intersectionobserver-polyfill.js | 188 +++++++++++++ polyfill/intersectionobserver_test.html | 315 ++++++++++++++++++++++ 2 files changed, 503 insertions(+) create mode 100644 polyfill/intersectionobserver-polyfill.js create mode 100644 polyfill/intersectionobserver_test.html diff --git a/polyfill/intersectionobserver-polyfill.js b/polyfill/intersectionobserver-polyfill.js new file mode 100644 index 0000000..0a3fc36 --- /dev/null +++ b/polyfill/intersectionobserver-polyfill.js @@ -0,0 +1,188 @@ +(function() { + // TODO: rootMargin are completely ignored for now + + // Constructor + window.IntersectionObserver = function(callback, options) { + options = options || {}; + + if(!(callback instanceof Function)) { + throw('callback needs to be a function'); + } + + this._callback = callback; + this._root = options.root || null; + this._rootMargin = options.rootMargin || [0, 0, 0, 0]; + this._thresholds = options.threshold || 0; + this._init(); + }; + + window.IntersectionObserver.prototype = { + // + // Public API + // + get root() { + return this._root || document; + }, + + get rootMargin() { + return '0'; + }, + + get thresholds() { + // 0 means call callback on every change + // See note at http://rawgit.com/WICG/IntersectionObserver/master/index.html#intersection-observer-init + if(this._thresholds === 0) { + return 0; + } + if(this._thresholds instanceof Array) { + return this._thresholds; + } + return [this._thresholds]; + }, + + observe: function(target) { + if(!(target instanceof HTMLElement)) { + throw('Target needs to be an HTMLelement'); + } + + var root = this.root; + var ancestor = target.parentNode; + while (ancestor != root) { + if (!ancestor) { + throw('Target must be decendant of root'); + } + ancestor = ancestor.parentNode; + } + + this._observationTargets.set(target, {}); + }, + + unobserve: function(target) { + this._observationTargets.delete(target); + }, + + disconnect: function() { + this._observationTargets.clear(); + this.root.removeEventListener('scroll', this._boundUpdate); + window.clearInterval(this._timeoutId); + this._descheduleCallback(); + }, + + takeRecords: function() { + this._update(); + this._descheduleCallback(); + var copy = this._queue.slice(); + this._queue = []; + return copy; + }, + + // + // Private API + // + _init: function() { + this._observationTargets = new Map(); + this._boundUpdate = this._update.bind(this); + this.root.addEventListener('scroll', this._boundUpdate); + this._intervalId = window.setInterval(this._boundUpdate, 100); + this._queue = []; + }, + + _update: function() { + var rootRect = this._rootRect(); + this._observationTargets.forEach(function(oldIntersectionEntry, target) { + var targetRect = target.getBoundingClientRect(); + var intersectionRect = intersectRects(rootRect, targetRect); + if(!intersectionRect) { + return; + } + var targetArea = targetRect.width * targetRect.height; + var intersectionArea = intersectionRect.width * intersectionRect.height; + var intersectionRatio = intersectionArea / targetArea; + if(!this._hasCrossedThreshold(oldIntersectionEntry.intersectionRatio, intersectionRatio)) { + return; + } + var intersectionEntry = { + time: window.performance.now(), + rootBounds: rootRect, + boundingClientRect: targetRect, + intersectionRect: intersectionRect, + intersectionRatio: intersectionRatio, + target: target + }; + Object.freeze(intersectionEntry); + this._queue.push(intersectionEntry); + this._scheduleCallback(); + this._observationTargets.set(target, intersectionEntry); + }.bind(this)); + }, + + _scheduleCallback: function() { + if(this._idleCallbackId) { + return; + } + this._idleCallbackId = window.requestIdleCallback(function() { + this._descheduleCallback(); + this._callback(this._queue, this); + this._queue = []; + }.bind(this), {timeout: 100}); + }, + + _descheduleCallback: function() { + if(!this._idleCallbackId) { + return; + } + window.cancelIdleCallback(this._idleCallbackId); + this._idleCallbackId = null; + }, + + _rootRect: function() { + if(this._root) { + return this.root.getBoundingClientRect(); + } + return { + top: 0, + left: 0, + right: window.innerWidth, + width: window.innerWidth, + bottom: window.innerHeight, + height: window.innerHeight + }; + }, + + // FIXME: so hack, much performance, very JSON + _hasCrossedThreshold: function(oldRatio, newRatio) { + if(this.thresholds === 0) { + return newRatio != oldRatio; + } + var b1 = this.thresholds.map(function(threshold) { + return threshold <= oldRatio; + }); + var b2 = this.thresholds.map(function(threshold) { + return threshold <= newRatio; + }); + return JSON.stringify(b1) !== JSON.stringify(b2); + } + }; + + var intersectRects = function(rect1, rect2) { + var top = Math.max(rect1.top, rect2.top); + var bottom = Math.min(rect1.bottom, rect2.bottom); + var left = Math.max(rect1.left, rect2.left); + var right = Math.min(rect1.right, rect2.right); + var intersectionRect = { + top: top, + bottom: bottom, + left: left, + right: right, + width: right-left, + height: bottom-top + }; + if(top > bottom) { + intersectionRect.height = 0; + } + if(left > right) { + intersectionRect.width = 0; + } + return intersectionRect; + }; +})(); \ No newline at end of file diff --git a/polyfill/intersectionobserver_test.html b/polyfill/intersectionobserver_test.html new file mode 100644 index 0000000..1a8c3cd --- /dev/null +++ b/polyfill/intersectionobserver_test.html @@ -0,0 +1,315 @@ + + + +
+
+ From 90d45f43a08d715d1728385972a7accfeda9b46d Mon Sep 17 00:00:00 2001 From: Surma Date: Sat, 19 Mar 2016 16:51:03 +0530 Subject: [PATCH 02/13] Check root element in constructor --- polyfill/intersectionobserver-polyfill.js | 42 +++++++++++++---------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/polyfill/intersectionobserver-polyfill.js b/polyfill/intersectionobserver-polyfill.js index 0a3fc36..f1e2814 100644 --- a/polyfill/intersectionobserver-polyfill.js +++ b/polyfill/intersectionobserver-polyfill.js @@ -1,14 +1,18 @@ (function() { // TODO: rootMargin are completely ignored for now - + // Constructor window.IntersectionObserver = function(callback, options) { options = options || {}; - + if(!(callback instanceof Function)) { throw('callback needs to be a function'); } - + + if(options.root && !(options.root instanceof HTMLElement)) { + throw('Target needs to be an HTMLelement'); + } + this._callback = callback; this._root = options.root || null; this._rootMargin = options.rootMargin || [0, 0, 0, 0]; @@ -23,11 +27,11 @@ get root() { return this._root || document; }, - + get rootMargin() { return '0'; }, - + get thresholds() { // 0 means call callback on every change // See note at http://rawgit.com/WICG/IntersectionObserver/master/index.html#intersection-observer-init @@ -39,35 +43,35 @@ } return [this._thresholds]; }, - + observe: function(target) { if(!(target instanceof HTMLElement)) { throw('Target needs to be an HTMLelement'); } - + var root = this.root; var ancestor = target.parentNode; while (ancestor != root) { if (!ancestor) { throw('Target must be decendant of root'); } - ancestor = ancestor.parentNode; + ancestor = ancestor.parentNode; } - + this._observationTargets.set(target, {}); }, - + unobserve: function(target) { this._observationTargets.delete(target); }, - + disconnect: function() { this._observationTargets.clear(); this.root.removeEventListener('scroll', this._boundUpdate); window.clearInterval(this._timeoutId); this._descheduleCallback(); }, - + takeRecords: function() { this._update(); this._descheduleCallback(); @@ -75,7 +79,7 @@ this._queue = []; return copy; }, - + // // Private API // @@ -86,7 +90,7 @@ this._intervalId = window.setInterval(this._boundUpdate, 100); this._queue = []; }, - + _update: function() { var rootRect = this._rootRect(); this._observationTargets.forEach(function(oldIntersectionEntry, target) { @@ -115,7 +119,7 @@ this._observationTargets.set(target, intersectionEntry); }.bind(this)); }, - + _scheduleCallback: function() { if(this._idleCallbackId) { return; @@ -126,7 +130,7 @@ this._queue = []; }.bind(this), {timeout: 100}); }, - + _descheduleCallback: function() { if(!this._idleCallbackId) { return; @@ -134,7 +138,7 @@ window.cancelIdleCallback(this._idleCallbackId); this._idleCallbackId = null; }, - + _rootRect: function() { if(this._root) { return this.root.getBoundingClientRect(); @@ -148,7 +152,7 @@ height: window.innerHeight }; }, - + // FIXME: so hack, much performance, very JSON _hasCrossedThreshold: function(oldRatio, newRatio) { if(this.thresholds === 0) { @@ -185,4 +189,4 @@ } return intersectionRect; }; -})(); \ No newline at end of file +})(); From e21b29b0e7029da623292fa5930dc2428ab653c2 Mon Sep 17 00:00:00 2001 From: Surma Date: Sat, 19 Mar 2016 16:52:16 +0530 Subject: [PATCH 03/13] Typo --- polyfill/intersectionobserver-polyfill.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/polyfill/intersectionobserver-polyfill.js b/polyfill/intersectionobserver-polyfill.js index f1e2814..8b828c9 100644 --- a/polyfill/intersectionobserver-polyfill.js +++ b/polyfill/intersectionobserver-polyfill.js @@ -53,7 +53,7 @@ var ancestor = target.parentNode; while (ancestor != root) { if (!ancestor) { - throw('Target must be decendant of root'); + throw('Target must be descendant of root'); } ancestor = ancestor.parentNode; } From 78a2214190eb5c05bc0106d935559cd9c818ff9f Mon Sep 17 00:00:00 2001 From: Surma Date: Sat, 19 Mar 2016 17:17:44 +0530 Subject: [PATCH 04/13] Debounce scroll event --- polyfill/intersectionobserver-polyfill.js | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/polyfill/intersectionobserver-polyfill.js b/polyfill/intersectionobserver-polyfill.js index 8b828c9..47d0667 100644 --- a/polyfill/intersectionobserver-polyfill.js +++ b/polyfill/intersectionobserver-polyfill.js @@ -86,7 +86,7 @@ _init: function() { this._observationTargets = new Map(); this._boundUpdate = this._update.bind(this); - this.root.addEventListener('scroll', this._boundUpdate); + this.root.addEventListener('scroll', debounce(this._boundUpdate, 100)); this._intervalId = window.setInterval(this._boundUpdate, 100); this._queue = []; }, @@ -189,4 +189,19 @@ } return intersectionRect; }; + + var debounce = function(fn, delay) { + var timer = null; + return function () { + if(timer) { + return; + } + var context = this, args = arguments; + clearTimeout(timer); + timer = setTimeout(function () { + timer = null; + fn.apply(context, args); + }, delay); + }; +} })(); From 0cbc753de58591bb2876d2437408f38f0ba1e60d Mon Sep 17 00:00:00 2001 From: Surma Date: Sat, 19 Mar 2016 17:17:50 +0530 Subject: [PATCH 05/13] Fix tests --- polyfill/intersectionobserver_test.html | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/polyfill/intersectionobserver_test.html b/polyfill/intersectionobserver_test.html index 1a8c3cd..e709e39 100644 --- a/polyfill/intersectionobserver_test.html +++ b/polyfill/intersectionobserver_test.html @@ -149,7 +149,7 @@ child.style.top = "-20px"; io.observe(child); - + var records = io.takeRecords(); io.disconnect(); return assert(records.length === 1 && records[0].intersectionRatio === 0); @@ -191,7 +191,7 @@ child.style.top = "-10px"; var records = io.takeRecords(); - output.push(assert(records.length == 0)); + output.push(assert(records.length == 1 && records[0].intersectionRatio === 0.5)); io.disconnect(); return output.join('\n'); @@ -230,7 +230,7 @@ child.style.top = "-11px"; var records = io.takeRecords(); output.push(assert(records.length == 1 && records[0].intersectionRatio <= 0.5 && records[0].intersectionRatio >= 0.25)); - + child.style.top = "-16px"; var records = io.takeRecords(); output.push(assert(records.length == 1 && records[0].intersectionRatio <= 0.25)); @@ -238,11 +238,11 @@ child.style.top = "-17px"; var records = io.takeRecords(); output.push(assert(records.length == 0)); - + child.style.top = "-15px"; var records = io.takeRecords(); - output.push(assert(records.length == 0)); - + output.push(assert(records.length == 1 && records[0].intersectionRatio === 0.25)); + io.disconnect(); return output.join('\n'); }, 'multiple thresholds') From dbab51e0a11f3a7e40f153c088e40c6f168e18a7 Mon Sep 17 00:00:00 2001 From: Surma Date: Mon, 28 Mar 2016 14:10:51 +0000 Subject: [PATCH 06/13] Use MutationObserver --- polyfill/intersectionobserver-polyfill.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/polyfill/intersectionobserver-polyfill.js b/polyfill/intersectionobserver-polyfill.js index 47d0667..82143c1 100644 --- a/polyfill/intersectionobserver-polyfill.js +++ b/polyfill/intersectionobserver-polyfill.js @@ -58,6 +58,12 @@ ancestor = ancestor.parentNode; } + this._mutationObserver.observe(target, { + attributes: true, + childList: true, + characterData: true, + subtree: true + }); this._observationTargets.set(target, {}); }, @@ -68,7 +74,7 @@ disconnect: function() { this._observationTargets.clear(); this.root.removeEventListener('scroll', this._boundUpdate); - window.clearInterval(this._timeoutId); + this._mutationObserver.disconnect(); this._descheduleCallback(); }, @@ -87,7 +93,7 @@ this._observationTargets = new Map(); this._boundUpdate = this._update.bind(this); this.root.addEventListener('scroll', debounce(this._boundUpdate, 100)); - this._intervalId = window.setInterval(this._boundUpdate, 100); + this._mutationObserver = new MutationObserver(debounce(this._boundUpdate, 100)); this._queue = []; }, From 9fca7fecb44f25f75f3d4dfe68f31fb16eebc234 Mon Sep 17 00:00:00 2001 From: Surma Date: Mon, 28 Mar 2016 14:14:57 +0000 Subject: [PATCH 07/13] Add license header --- polyfill/intersectionobserver-polyfill.js | 36 +++++++++++++++-------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/polyfill/intersectionobserver-polyfill.js b/polyfill/intersectionobserver-polyfill.js index 82143c1..05f5332 100644 --- a/polyfill/intersectionobserver-polyfill.js +++ b/polyfill/intersectionobserver-polyfill.js @@ -1,3 +1,15 @@ +/* +Copyright 2016 Google Inc. All Rights Reserved. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ (function() { // TODO: rootMargin are completely ignored for now @@ -197,17 +209,17 @@ }; var debounce = function(fn, delay) { - var timer = null; - return function () { - if(timer) { - return; - } - var context = this, args = arguments; - clearTimeout(timer); - timer = setTimeout(function () { - timer = null; - fn.apply(context, args); - }, delay); + var timer = null; + return function () { + if(timer) { + return; + } + var context = this, args = arguments; + clearTimeout(timer); + timer = setTimeout(function () { + timer = null; + fn.apply(context, args); + }, delay); + }; }; -} })(); From dcd9cc10369b30890f4c1a24fb4f76fd418e0e46 Mon Sep 17 00:00:00 2001 From: Surma Date: Fri, 1 Apr 2016 21:45:31 +0000 Subject: [PATCH 08/13] Calculate a rects width and height if not given --- polyfill/intersectionobserver-polyfill.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/polyfill/intersectionobserver-polyfill.js b/polyfill/intersectionobserver-polyfill.js index 05f5332..44b7917 100644 --- a/polyfill/intersectionobserver-polyfill.js +++ b/polyfill/intersectionobserver-polyfill.js @@ -112,7 +112,7 @@ limitations under the License. _update: function() { var rootRect = this._rootRect(); this._observationTargets.forEach(function(oldIntersectionEntry, target) { - var targetRect = target.getBoundingClientRect(); + var targetRect = augmentRect(target.getBoundingClientRect()); var intersectionRect = intersectRects(rootRect, targetRect); if(!intersectionRect) { return; @@ -159,7 +159,7 @@ limitations under the License. _rootRect: function() { if(this._root) { - return this.root.getBoundingClientRect(); + return augmentRect(this.root.getBoundingClientRect()); } return { top: 0, @@ -222,4 +222,10 @@ limitations under the License. }, delay); }; }; + + var augmentRect = function(r) { + r.width = r.width || r.right - r.left; + r.height = r.height || r.bottom - r.top; + return r; + }; })(); From 5b78388b69cf817dfecd6f277d15ce3aacd14d33 Mon Sep 17 00:00:00 2001 From: Surma Date: Mon, 4 Apr 2016 13:07:27 +0100 Subject: [PATCH 09/13] Code review --- polyfill/intersectionobserver-polyfill.js | 66 +++++++++++------------ 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/polyfill/intersectionobserver-polyfill.js b/polyfill/intersectionobserver-polyfill.js index 44b7917..948d65f 100644 --- a/polyfill/intersectionobserver-polyfill.js +++ b/polyfill/intersectionobserver-polyfill.js @@ -10,11 +10,13 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ -(function() { +(function(scope) { // TODO: rootMargin are completely ignored for now + var POLL_INTERVAL = 100; + // Constructor - window.IntersectionObserver = function(callback, options) { + var IntersectionObserver = function(callback, options) { options = options || {}; if(!(callback instanceof Function)) { @@ -22,7 +24,7 @@ limitations under the License. } if(options.root && !(options.root instanceof HTMLElement)) { - throw('Target needs to be an HTMLelement'); + throw('Root needs to be an HTMLElement'); } this._callback = callback; @@ -32,7 +34,7 @@ limitations under the License. this._init(); }; - window.IntersectionObserver.prototype = { + IntersectionObserver.prototype = { // // Public API // @@ -63,11 +65,8 @@ limitations under the License. var root = this.root; var ancestor = target.parentNode; - while (ancestor != root) { - if (!ancestor) { - throw('Target must be descendant of root'); - } - ancestor = ancestor.parentNode; + if(!root.contains(target)) { + throw('Target must be descendant of root'); } this._mutationObserver.observe(target, { @@ -103,16 +102,16 @@ limitations under the License. // _init: function() { this._observationTargets = new Map(); - this._boundUpdate = this._update.bind(this); - this.root.addEventListener('scroll', debounce(this._boundUpdate, 100)); - this._mutationObserver = new MutationObserver(debounce(this._boundUpdate, 100)); + this._boundUpdate = throttle(this._update.bind(this), POLL_INTERVAL); + this.root.addEventListener('scroll', this._boundUpdate); + this._mutationObserver = new MutationObserver(this._boundUpdate); this._queue = []; }, _update: function() { var rootRect = this._rootRect(); this._observationTargets.forEach(function(oldIntersectionEntry, target) { - var targetRect = augmentRect(target.getBoundingClientRect()); + var targetRect = getBoundingClientRect(target); var intersectionRect = intersectRects(rootRect, targetRect); if(!intersectionRect) { return; @@ -124,7 +123,7 @@ limitations under the License. return; } var intersectionEntry = { - time: window.performance.now(), + time: scope.performance.now(), rootBounds: rootRect, boundingClientRect: targetRect, intersectionRect: intersectionRect, @@ -139,35 +138,35 @@ limitations under the License. }, _scheduleCallback: function() { - if(this._idleCallbackId) { + if(this._timeoutId) { return; } - this._idleCallbackId = window.requestIdleCallback(function() { + this._timeoutId = scope.setTimeout(function() { this._descheduleCallback(); this._callback(this._queue, this); this._queue = []; - }.bind(this), {timeout: 100}); + }.bind(this), POLL_INTERVAL); }, _descheduleCallback: function() { - if(!this._idleCallbackId) { + if(!this._timeoutId) { return; } - window.cancelIdleCallback(this._idleCallbackId); - this._idleCallbackId = null; + scope.clearTimeout(this._timeoutId); + this._timeoutId = null; }, _rootRect: function() { if(this._root) { - return augmentRect(this.root.getBoundingClientRect()); + return getBoundingClientRect(this.root); } return { top: 0, left: 0, - right: window.innerWidth, - width: window.innerWidth, - bottom: window.innerHeight, - height: window.innerHeight + right: scope.innerWidth, + width: scope.innerWidth, + bottom: scope.innerHeight, + height: scope.innerHeight }; }, @@ -208,24 +207,25 @@ limitations under the License. return intersectionRect; }; - var debounce = function(fn, delay) { + scope.IntersectionObserver = IntersectionObserver; + + var throttle = function(fn, int) { var timer = null; return function () { - if(timer) { + if (timer) { return; } - var context = this, args = arguments; - clearTimeout(timer); + callback.apply(this, arguments); timer = setTimeout(function () { timer = null; - fn.apply(context, args); - }, delay); + }, int); }; }; - var augmentRect = function(r) { + var getBoundingClientRect = function(el) { + var r = el.getBoundingClientRect(); r.width = r.width || r.right - r.left; r.height = r.height || r.bottom - r.top; return r; }; -})(); +})(this); From d6da2e34e064ecd878fce463fecc070c8a806bba Mon Sep 17 00:00:00 2001 From: Surma Date: Mon, 4 Apr 2016 16:18:08 +0100 Subject: [PATCH 10/13] Minor typo --- polyfill/intersectionobserver-polyfill.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/polyfill/intersectionobserver-polyfill.js b/polyfill/intersectionobserver-polyfill.js index 948d65f..af58271 100644 --- a/polyfill/intersectionobserver-polyfill.js +++ b/polyfill/intersectionobserver-polyfill.js @@ -215,7 +215,7 @@ limitations under the License. if (timer) { return; } - callback.apply(this, arguments); + fn.apply(this, arguments); timer = setTimeout(function () { timer = null; }, int); From c98bdd64916e4452463d56c55769366125c7cafb Mon Sep 17 00:00:00 2001 From: Surma Date: Sat, 9 Apr 2016 15:43:03 +0100 Subject: [PATCH 11/13] Move tests to mocha --- polyfill/intersectionobserver-polyfill.js | 17 +- polyfill/intersectionobserver_test.html | 553 ++++++++++------------ 2 files changed, 255 insertions(+), 315 deletions(-) diff --git a/polyfill/intersectionobserver-polyfill.js b/polyfill/intersectionobserver-polyfill.js index af58271..decfa6c 100644 --- a/polyfill/intersectionobserver-polyfill.js +++ b/polyfill/intersectionobserver-polyfill.js @@ -59,6 +59,9 @@ limitations under the License. }, observe: function(target) { + if(this._observationTargets.has(target)) { + return; + } if(!(target instanceof HTMLElement)) { throw('Target needs to be an HTMLelement'); } @@ -68,7 +71,6 @@ limitations under the License. if(!root.contains(target)) { throw('Target must be descendant of root'); } - this._mutationObserver.observe(target, { attributes: true, childList: true, @@ -76,6 +78,7 @@ limitations under the License. subtree: true }); this._observationTargets.set(target, {}); + this._update(); }, unobserve: function(target) { @@ -105,6 +108,12 @@ limitations under the License. this._boundUpdate = throttle(this._update.bind(this), POLL_INTERVAL); this.root.addEventListener('scroll', this._boundUpdate); this._mutationObserver = new MutationObserver(this._boundUpdate); + this._mutationObserver.observe(this.root, { + attributes: true, + childList: true, + characterData: true, + subtree: true + }); this._queue = []; }, @@ -207,7 +216,7 @@ limitations under the License. return intersectionRect; }; - scope.IntersectionObserver = IntersectionObserver; + scope.IntersectionObserver = scope.IntersectionObserver || IntersectionObserver; var throttle = function(fn, int) { var timer = null; @@ -215,10 +224,10 @@ limitations under the License. if (timer) { return; } - fn.apply(this, arguments); timer = setTimeout(function () { + fn.apply(this, arguments); timer = null; - }, int); + }.bind(this), int); }; }; diff --git a/polyfill/intersectionobserver_test.html b/polyfill/intersectionobserver_test.html index e709e39..1ccb4ab 100644 --- a/polyfill/intersectionobserver_test.html +++ b/polyfill/intersectionobserver_test.html @@ -1,315 +1,246 @@ + + + + + + -
- +
+ + + + + + From d7cc95b643c8ab98bbac5e82fa956949b583d44a Mon Sep 17 00:00:00 2001 From: Surma Date: Sat, 9 Apr 2016 15:57:24 +0100 Subject: [PATCH 12/13] Fix margin tests --- polyfill/intersectionobserver_test.html | 49 ++++++++++++++----------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/polyfill/intersectionobserver_test.html b/polyfill/intersectionobserver_test.html index 1ccb4ab..d6be655 100644 --- a/polyfill/intersectionobserver_test.html +++ b/polyfill/intersectionobserver_test.html @@ -25,7 +25,7 @@ function promiseAnimationFrame() { return new Promise(function(resolve) { requestAnimationFrame(function() { - resolve(); + resolve(); }); }); } @@ -193,51 +193,58 @@ var callCounter = 0; io = new IntersectionObserver( - function(records) { - if(callCounter <= 4) { + function(records) { + if(callCounter <= 6) { expect(records.length).to.be(1); } else { throw new Error("Too many calls"); } callCounter++; - }, - {root: rootEl, threshold: [0.5, 0.25]} + }, + {root: rootEl, margin: "5px 10% 10% 15px"} ); io.observe(childEl); - childEl.style.top = "-25px"; + childEl.style.top = "-15px"; promiseAnimationFrame() - .then(await(function(){return callCounter == 0;}, 500)) - .then(function() { - childEl.style.top = "-10px"; - }) .then(await(function(){return callCounter == 1;}, 500)) .then(function() { - childEl.style.top = "90px"; - }) - .then(await(function(){return callCounter == 1;}, 500)) - .then(function() { - childEl.style.top = "96px"; + childEl.style.top = "-14px"; }) .then(await(function(){return callCounter == 2;}, 500)) .then(function() { - childEl.style.top = "0px"; - childEl.style.left = "-35px"; + childEl.style.top = "86px"; }) - .then(await(function(){return callCounter == 2;}, 500)) + .then(await(function(){return callCounter == 3;}, 500)) .then(function() { - childEl.style.left = "-15px"; + childEl.style.top = "87px"; }) + .then(promiseAnimationFrame) .then(await(function(){return callCounter == 3;}, 500)) .then(function() { - childEl.style.left = "-10px"; + childEl.style.top = "0px"; + childEl.style.left = "0px"; }) .then(await(function(){return callCounter == 4;}, 500)) .then(function() { - childEl.style.left = "110px"; + childEl.style.left = "-1px"; }) + .then(promiseAnimationFrame) .then(await(function(){return callCounter == 4;}, 500)) + .then(function() { + childEl.style.left = "1px"; + }) + .then(await(function(){return callCounter == 5;}, 500)) + .then(function() { + childEl.style.left = "180px"; + }) + .then(await(function(){return callCounter == 6;}, 500)) + .then(function() { + childEl.style.left = "181px"; + }) + .then(promiseAnimationFrame) + .then(await(function(){return callCounter == 6;}, 500)) .then(done); }); mocha.run(); From dcebb1a401520b89b9eb80b135e3764ea6b2d857 Mon Sep 17 00:00:00 2001 From: Surma Date: Sat, 9 Apr 2016 15:57:54 +0100 Subject: [PATCH 13/13] Use consistent indents --- polyfill/intersectionobserver_test.html | 480 ++++++++++++------------ 1 file changed, 240 insertions(+), 240 deletions(-) diff --git a/polyfill/intersectionobserver_test.html b/polyfill/intersectionobserver_test.html index d6be655..398937e 100644 --- a/polyfill/intersectionobserver_test.html +++ b/polyfill/intersectionobserver_test.html @@ -1,253 +1,253 @@ - - + } + - -
-
-
- - - - +
+
+
+ + + + + callCounter++; + }, + {root: rootEl, threshold: [0.5, 0.25]} + ); + + io.observe(childEl); + + promiseAnimationFrame() + .then(await(function(){return callCounter == 1;}, 500)) + .then(function() { + childEl.style.top = "-11px"; + }) + .then(await(function(){return callCounter == 2;}, 500)) + .then(function() { + childEl.style.top = "-16px"; + }) + .then(await(function(){return callCounter == 3;}, 500)) + .then(function() { + childEl.style.top = "-17px"; + }) + .then(promiseAnimationFrame) + .then(function() { + expect(io.takeRecords().length).to.be(0); + expect(callCounter).to.be(3); + childEl.style.top = "-15px"; + }) + .then(await(function(){return callCounter == 4;}, 500)) + .then(done); + }); + + it("supports margins", function(done) { + rootEl.appendChild(childEl); + document.body.appendChild(rootEl); + + var callCounter = 0; + io = new IntersectionObserver( + function(records) { + if(callCounter <= 6) { + expect(records.length).to.be(1); + } else { + throw new Error("Too many calls"); + } + callCounter++; + }, + {root: rootEl, margin: "5px 10% 10% 15px"} + ); + + io.observe(childEl); + + childEl.style.top = "-15px"; + promiseAnimationFrame() + .then(await(function(){return callCounter == 1;}, 500)) + .then(function() { + childEl.style.top = "-14px"; + }) + .then(await(function(){return callCounter == 2;}, 500)) + .then(function() { + childEl.style.top = "86px"; + }) + .then(await(function(){return callCounter == 3;}, 500)) + .then(function() { + childEl.style.top = "87px"; + }) + .then(promiseAnimationFrame) + .then(await(function(){return callCounter == 3;}, 500)) + .then(function() { + childEl.style.top = "0px"; + childEl.style.left = "0px"; + }) + .then(await(function(){return callCounter == 4;}, 500)) + .then(function() { + childEl.style.left = "-1px"; + }) + .then(promiseAnimationFrame) + .then(await(function(){return callCounter == 4;}, 500)) + .then(function() { + childEl.style.left = "1px"; + }) + .then(await(function(){return callCounter == 5;}, 500)) + .then(function() { + childEl.style.left = "180px"; + }) + .then(await(function(){return callCounter == 6;}, 500)) + .then(function() { + childEl.style.left = "181px"; + }) + .then(promiseAnimationFrame) + .then(await(function(){return callCounter == 6;}, 500)) + .then(done); + }); + mocha.run(); +