From 684883f5d5c2aff2586a544596c82789d9398acd Mon Sep 17 00:00:00 2001 From: Lucas Serven Date: Fri, 25 Apr 2014 21:49:19 -0400 Subject: [PATCH] Account for case where all elements are rerendered This commit accounts for an edge case where Postpone would not correctly load elemnts when they had scrolled into view. Specifically, it takes care of the case when a set of already postponed elements are completely re-rendered by an application. Re-rendering a page with the same content may result in completely equivalent markup to the markup that existed before, but from a browser's point of view, the DOM nodes represented by those sets of markup are not identical. Postpone was treating these nodes as identical, so if your Backbone app re-rendered a bunch of still un-loaded images, Postpone would be unable to load them. --- .DS_Store | Bin 0 -> 6148 bytes bower.json | 2 +- component.json | 2 +- index.js | 356 ++++++++++++++++++++++++------------------------- package.json | 2 +- test/build.js | 356 ++++++++++++++++++++++++------------------------- 6 files changed, 357 insertions(+), 361 deletions(-) create mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..c3ac046f6c51a2cd4bc2df32c603e493458c382e GIT binary patch literal 6148 zcmeH~F^{dt=u>Bl;zddfqZjB=JE!l7EiIwCR znHYd=e=Zwf1hAw#vGy=AV?N-4FWm9?zTD5J+wJN_+D8XGrH`2G=e8gPq<|EV0#ZN< z%t(Pe#+RQndL})J6p#Y*P{6+rh3>4$))}7;h8O|Jf#oo+W0oL`7s#4yovhF-rw7Yc zi!sFO(N31Ut|nV&Z-?dZVR>isDTZdf9afmotOgXMfE1W0u;}^d=l`Dm-~2ymQ7Q$b zz?&&x!|t%#@}=@@{quTWKW5d}jZVhp3{O7+O#CQb(Zjf3d_mS^>tuzdAAyiTK?=N7 FfnTX=61e~X literal 0 HcmV?d00001 diff --git a/bower.json b/bower.json index 9dfb7db..590fd79 100644 --- a/bower.json +++ b/bower.json @@ -1,7 +1,7 @@ { "name": "postpone", "main": "index.js", - "version": "0.6.0", + "version": "0.6.1", "homepage": "https://github.com/lsvx/postpone", "authors": [ "Lucas Serven " diff --git a/component.json b/component.json index 36d4662..ed4fb92 100644 --- a/component.json +++ b/component.json @@ -2,7 +2,7 @@ "name": "postpone", "repo": "lsvx/postpone", "description": "A polyfill for postponing the loading of media.", - "version": "0.6.0", + "version": "0.6.1", "keywords": ["postpone", "media", "resource", "priority", "download"], "dependencies": {}, "development": {}, diff --git a/index.js b/index.js index 08c57b0..60c003d 100644 --- a/index.js +++ b/index.js @@ -10,34 +10,39 @@ "use strict"; /** - * Creates a new Postpone instance. - * @constructor - * @param {number|string} [threshold] - The distance from an edge at which an - * element should be considered to be at the edge. - */ + * Creates a new Postpone instance. + * @constructor + * @param {number|string} [threshold] - The distance from an edge at which an + * element should be considered to be at the edge. + */ var Postpone = function( threshold ) { if ( !( this instanceof Postpone ) ) return new Postpone( threshold ); /** - * The init method for Postpone gets the object running. It runs - * postpone.postpone() to attach scroll event handlers and check if any - * elements are already visible. Then, init will start the watch process. - * @returns this - */ + * The init method for Postpone gets the object running. It runs + * postpone.postpone() to attach scroll event handlers and check if any + * elements are already visible. Then, init will start the watch process. + * @returns this + */ this.init = function( threshold ) { /** - * @property {string} tags - A list of all the tags for which postpone - * will work; - */ + * @property {string} tags - A list of all the tags for which postpone + * will work; + */ this.tags = "audio, embed, iframe, img, image, object, picture, use, video, tref"; /** - * @property {array} elements - An array of all the postponed elements in the document. - */ + * @property {array} elements - An array of all the postponed elements in the document. + */ this.elements = []; /** - * @property {object} scrollElements - A variable to keep track of the - * elements with respoect to which the postponed elements scroll. - */ + * @property {array} visible - An array of all the non-hidden postponed + * elements in the document. + */ + this.elements.visible = []; + /** + * @property {object} scrollElements - A variable to keep track of the + * elements with respoect to which the postponed elements scroll. + */ this.scrollElements = []; this.setThreshold( threshold ); this.postpone(); @@ -50,31 +55,31 @@ }; /** - * The main postpone method. This method iterates over all the elements with - * a `postpone` attribute and links them to a scroll event so that they are not - * loaded until they become visible. - * @returns this - */ + * The main postpone method. This method iterates over all the elements with + * a `postpone` attribute and links them to a scroll event so that they are not + * loaded until they become visible. + * @returns this + */ Postpone.prototype.postpone = function() { /** - * Remove any previous event handlers so they can be reattached for new - * postponed elements without duplicating old ones. This must be done - * before updating the scroll elements so the references to the event - * callbacks still exist. - */ + * Remove any previous event handlers so they can be reattached for new + * postponed elements without duplicating old ones. This must be done + * before updating the scroll elements so the references to the event + * callbacks still exist. + */ this.unbindEvents(); /** - * Update the elements and scroll elements to properly load or postpone - * them. - */ + * Update the elements and scroll elements to properly load or postpone + * them. + */ this.getElements(); this.getScrollElements(); /** - * If any of the postponed elements should be visible to begin with, - * load them. - */ + * If any of the postponed elements should be visible to begin with, + * load them. + */ for ( var id in this.scrollElements ) { for ( var i = 0, element = {}; i < this.scrollElements[ id ].length; i++ ) { element = this.scrollElements[ id ][ i ]; @@ -85,7 +90,7 @@ } if ( this.elements.length ) { - /** Attach scroll event listeners. */ + /** Attach scroll event listeners. */ this.bindEvents(); } @@ -93,9 +98,9 @@ }; /** - * A helper method to unbind the scroll event callbacks. - * @returns this - */ + * A helper method to unbind the scroll event callbacks. + * @returns this + */ Postpone.prototype.unbindEvents = function() { for ( var id in this.scrollElements ) { this._removeEventListener( id === "window" ? window : this.scrollElements[ id ].element, this.scrollElements[ id ].callback ); @@ -103,25 +108,29 @@ }; /** - * A helper method to bind the scroll event callbacks. - * @returns this - */ + * A helper method to bind the scroll event callbacks. + * @returns this + */ Postpone.prototype.bindEvents = function() { for ( var id in this.scrollElements ) { this.scrollElements[ id ].callback = Function.prototype.bind ? this.scrollHandler.bind( this ) : function( _this ) { return function() { return _this.scrollHandler.apply( _this, arguments ); }; }( this ); this._addEventListener( id === "window" ? window : this.scrollElements[ id ].element, this.scrollElements[ id ].callback ); } + + return this; }; /** - * A helper method to find all of the elements with a postponed attribute. - * @returns {array} An array of nodes with a `truthy` postpone attribute. - */ + * A helper method to find all of the elements with a postponed attribute. + * @returns {Boolean} A boolean stating whether new postpone elements have been + * found. + */ Postpone.prototype.getElements = function() { var elements = [], visible = [], matches = this._slice( document.querySelectorAll( this.tags ) ), - postpone = null; + postpone = null, + change = false; for ( var i = 0; i < matches.length; i++ ) { postpone = matches[ i ].getAttribute( "postpone" ); @@ -129,26 +138,33 @@ elements.push( matches[ i ] ); if ( this.isVisible( matches[ i ] ) ) { visible.push( matches[ i ] ); + /** Check if this element is not already postponed. */ + if ( !~this.elements.visible.indexOf( matches[ i ] ) ) { + change = true; + } } } } + + /** Check if old postponed elements are no longer on the page. */ + if ( this.elements.visible.length !== visible.length ) { + change = true; + } this.elements = elements; - /** - * @property {array} visible - An array of all the non-hidden postponed - * elements in the document. - */ this.elements.visible = visible; + + return change; }; /** - * A helper method to find all of the elements with respect to which - * postponed elements scroll. The elements are stored with a unique ID as - * their key. - * @returns {object} A hash with arrays of postponed elements associated with - * IDs of their scroll elements. - */ + * A helper method to find all of the elements with respect to which + * postponed elements scroll. The elements are stored with a unique ID as + * their key. + * @returns {object} A hash with arrays of postponed elements associated with + * IDs of their scroll elements. + */ Postpone.prototype.getScrollElements = function() { - this.scrollElements = {}; + this.scrollElements = {}; var id = "", element = {}, @@ -157,31 +173,31 @@ for ( var i = 0; i < this.elements.visible.length; i++ ) { element = this.elements.visible[ i ]; /** - * Find the element relative to which the postponed element's - * position should be calculated. - */ + * Find the element relative to which the postponed element's + * position should be calculated. + */ if ( element.getAttribute( "data-scroll-element" ) ) { scrollElement = document.querySelector( element.getAttribute( "data-scroll-element" ) ); /** - * If the scroll element does not have an ID, generate one and - * assign it as a data attribute. - */ + * If the scroll element does not have an ID, generate one and + * assign it as a data attribute. + */ id = scrollElement.getAttribute( "data-id" ); if ( !id ) { scrollElement.setAttribute( "data-id", id = new Date().getTime() ); } /** - * If the element does not have a scroll element specified then - * assume its position should be calculated relative to the window. - */ + * If the element does not have a scroll element specified then + * assume its position should be calculated relative to the window. + */ } else { scrollElement = "window"; id = "window"; } /** - * If the array already has this id as a key, then add the current - * element to the array in its value, otherwise create a new key. - */ + * If the array already has this id as a key, then add the current + * element to the array in its value, otherwise create a new key. + */ if ( this.scrollElements[ id ] ) { this.scrollElements[ id ].push( element ); } else { @@ -193,43 +209,25 @@ }; /** - * A small helper that finds the posponed elements and returns them in a - * string. - * @param {array} elements - An array of elements to stringify. - * @returns {string} A string containing all the HTML of postponed elements. - */ - Postpone.prototype.stringifyElements = function( elements ) { - var elementsString = ""; - - for ( var i = 0; i < elements.length; i++ ) { - elementsString += elements[ i ].outerHTML; - } - - return elementsString; - }; - - /** - * Method to watch the document for new postponed elements. - * @param {string} [elementsString] - A string of non-visually hidden - * postponed elements. - * @returns this - */ - Postpone.prototype.watch = function( elementsString ) { - /** Refresh the array of postponed elements, this.elements. */ - this.getElements(); - var newElementsString = this.stringifyElements( this.elements.visible ); - /** If the postponed elements have changed, then postpone them. */ - if ( elementsString !== newElementsString ) { + * Method to watch the document for new postponed elements. + * @returns this + */ + Postpone.prototype.watch = function() { + /** + * Refresh the array of postponed elements, this.elements. If the postponed + * elements have changed, then process them. + */ + if ( this.getElements() ) { this.postpone(); } /** - * This timeout calls the watch method every 500ms. In other words, - * postpone will look for new postponed elements twice a second. - * @property {number} timeout - The ID for the current timeout. - */ + * This timeout calls the watch method every 500ms. In other words, + * postpone will look for new postponed elements twice a second. + * @property {number} timeout - The ID for the current timeout. + */ this.timeout = window.setTimeout( (function( _this ) { return function() { - return _this.watch( newElementsString ); + return _this.watch(); }; })( this ), 500); @@ -237,9 +235,9 @@ }; /** - * Method to start watching for elements that should postponed. - * @returns this - */ + * Method to start watching for elements that should postponed. + * @returns this + */ Postpone.prototype.start = function() { /** Ensure that watching has stopped before starting to watch. */ if ( this.timeout ) this.stop(); @@ -250,10 +248,10 @@ }; /** - * Method to stop watching for elements that should postponed and unbind events - * associated with postponed elements. - * @returns this - */ + * Method to stop watching for elements that should postponed and unbind events + * associated with postponed elements. + * @returns this + */ Postpone.prototype.stop = function() { if ( this.timeout ) window.clearTimeout( this.timeout ); @@ -264,11 +262,11 @@ }; /** - * This method defines the scroll event handler used to test if postponed - * elementes are visible. - * @param {object} e - Event object. - * @returns this - */ + * This method defines the scroll event handler used to test if postponed + * elementes are visible. + * @param {object} e - Event object. + * @returns this + */ Postpone.prototype.scrollHandler = function( e ) { var scrollElement = e.srcElement || e.target || window.document, elements = this.scrollElements[ scrollElement === window.document ? scrollElement = "window" : scrollElement.getAttribute( "data-id" ) ], @@ -279,9 +277,9 @@ element = elements[ i ]; /** - * If an element is visible then we no longer need to postpone it - * and can download it. - */ + * If an element is visible then we no longer need to postpone it + * and can download it. + */ if ( this.isInViewport( element, scrollElement ) ) { this.load( element ); } @@ -291,39 +289,39 @@ }; /** - * A convenience method to easily set the threshold property of postpone. - * @param {number|string} threshold - The distance from an edge at which an - * element should be considered to be at the edge. - * @returns this - */ + * A convenience method to easily set the threshold property of postpone. + * @param {number|string} threshold - The distance from an edge at which an + * element should be considered to be at the edge. + * @returns this + */ Postpone.prototype.setThreshold = function( threshold ) { threshold = threshold ? threshold : 0; /** - * @property {object} threshold - A hash containing the value and unit of - * measurement of the desired postpone threshold. - */ + * @property {object} threshold - A hash containing the value and unit of + * measurement of the desired postpone threshold. + */ this.threshold = {}; /** - * @property {number} value - The number of units from an edge at - * which an element should be considered to be at the edge. This is - * useful to start loading images or other resources before they scroll - * into view to prevent flash of content. - */ + * @property {number} value - The number of units from an edge at + * which an element should be considered to be at the edge. This is + * useful to start loading images or other resources before they scroll + * into view to prevent flash of content. + */ this.threshold.value = parseInt( threshold, 10 ); /** - * @property {string} unit - The unit of measurement for the threshold - * value. Currently, only `vh` and `px` are supported. By default, the unit - * is `vh`. - */ + * @property {string} unit - The unit of measurement for the threshold + * value. Currently, only `vh` and `px` are supported. By default, the unit + * is `vh`. + */ this.threshold.unit = ( typeof threshold === "number" ) ? "vh" : ( threshold.match(/[a-zA-Z]+/)[ 0 ] || "vh" ); return this; }; /** - * Small helper method to find the total vertical offset of an element. - * @param {object} el - The element we wish to locate. - * @returns {number} The total vertical offset of the element. - */ + * Small helper method to find the total vertical offset of an element. + * @param {object} el - The element we wish to locate. + * @returns {number} The total vertical offset of the element. + */ Postpone.prototype.offsetTop = function( el ) { var temp = el, o = 0; @@ -337,20 +335,20 @@ }; /** - * Small helper method to determine if an element is visually hidden or not. - * This method check if the element provided, or any of its parents have the - * style `display: none;`. - * @param {object} el - The element we wish to locate. - * @returns {boolean} Returns true if the element is visible and false if it is - * hidden. - */ + * Small helper method to determine if an element is visually hidden or not. + * This method check if the element provided, or any of its parents have the + * style `display: none;`. + * @param {object} el - The element we wish to locate. + * @returns {boolean} Returns true if the element is visible and false if it is + * hidden. + */ Postpone.prototype.isVisible = function( el ) { var temp = el, isVisible = true; /** - * Iterate over all parents of el up to HTML to find if el or a parent is - * hidden. - */ + * Iterate over all parents of el up to HTML to find if el or a parent is + * hidden. + */ while ( temp && temp.parentElement && isVisible ) { isVisible = temp.currentStyle ? temp.currentStyle.display !== "none" : document.defaultView.getComputedStyle( temp ).getPropertyValue( "display" ) !== "none"; temp = temp.parentElement; @@ -360,11 +358,11 @@ }; /** - * Helper method to determine if an element is in the browser's viewport. - * @param {object} el - The element we wish to test. - * @param {object} [scrollElement] - The element with respect to which `el` scrolls. - * @returns {boolean} Return true if the `el` is in view and false if it is not. - */ + * Helper method to determine if an element is in the browser's viewport. + * @param {object} el - The element we wish to test. + * @param {object} [scrollElement] - The element with respect to which `el` scrolls. + * @returns {boolean} Return true if the `el` is in view and false if it is not. + */ Postpone.prototype.isInViewport = function( el, scrollElement ) { /** If no scroll element is specified, then assume the scroll element is the window. */ scrollElement = scrollElement ? scrollElement : "window"; @@ -396,11 +394,11 @@ }; /** - * This method takes care of loading the media that should no longer be - * postponed. - * @param {object} el - The element that should be loaded. - * @returns {object} The element that was loaded. - */ + * This method takes care of loading the media that should no longer be + * postponed. + * @param {object} el - The element that should be loaded. + * @returns {object} The element that was loaded. + */ Postpone.prototype.load = function( el ) { var child = {}, i = 0; @@ -442,10 +440,10 @@ el.setAttribute( "data", el.getAttribute( "data-data" )); /** - * This is necessary to make Safari (and, apparently, old versions of Chrome) - * re-render the new content; see: - * stackoverflow.com/questions/11245385/object-works-in-every-browser-except-google-chrome - */ + * This is necessary to make Safari (and, apparently, old versions of Chrome) + * re-render the new content; see: + * stackoverflow.com/questions/11245385/object-works-in-every-browser-except-google-chrome + */ el.innerHTML = el.innerHTML; } @@ -453,11 +451,11 @@ }; /** - * A helper method to convert array-like objects into arrays. - * @param {object} arr - The object to be converted. - * @returns {array} An array representation of the supplied object. - * @api private - */ + * A helper method to convert array-like objects into arrays. + * @param {object} arr - The object to be converted. + * @returns {array} An array representation of the supplied object. + * @api private + */ Postpone.prototype._slice = function( object ) { /** Try to use `slice` to convert the object. */ try { @@ -476,13 +474,13 @@ }; /** - * A helper method to abstract event listener creation. - * @param {object} el - The element to which the event should be added. - * @param {function} callback - The callback to be executed when the event - * is fired. - * @returns undefined - * @api private - */ + * A helper method to abstract event listener creation. + * @param {object} el - The element to which the event should be added. + * @param {function} callback - The callback to be executed when the event + * is fired. + * @returns undefined + * @api private + */ Postpone.prototype._addEventListener = function( el, callback ) { /** Try to add the event using `addEventListener`. */ try { @@ -494,13 +492,13 @@ }; /** - * A helper method to abstract event listener removal. - * @param {object} el - The element from which the event should be removed. - * @param {function} callback - The callback to be executed when the event - * is fired. - * @returns undefined - * @api private - */ + * A helper method to abstract event listener removal. + * @param {object} el - The element from which the event should be removed. + * @param {function} callback - The callback to be executed when the event + * is fired. + * @returns undefined + * @api private + */ Postpone.prototype._removeEventListener = function( el, callback ) { /** Try to remove the event using `removeEventListener`. */ try { diff --git a/package.json b/package.json index c2b67cd..5301841 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "postpone", - "version": "0.6.0", + "version": "0.6.1", "description": "A polyfill for postponing the loading of media.", "main": "index.js", "directories": { diff --git a/test/build.js b/test/build.js index f7ebe6a..866fb2b 100644 --- a/test/build.js +++ b/test/build.js @@ -212,34 +212,39 @@ require.register("postpone/index.js", Function("exports, require, module", \"use strict\";\n\ \n\ /**\n\ - * Creates a new Postpone instance.\n\ - * @constructor\n\ - * @param {number|string} [threshold] - The distance from an edge at which an\n\ - * element should be considered to be at the edge.\n\ - */\n\ + * Creates a new Postpone instance.\n\ + * @constructor\n\ + * @param {number|string} [threshold] - The distance from an edge at which an\n\ + * element should be considered to be at the edge.\n\ + */\n\ var Postpone = function( threshold ) {\n\ if ( !( this instanceof Postpone ) ) return new Postpone( threshold );\n\ \n\ /**\n\ - * The init method for Postpone gets the object running. It runs\n\ - * postpone.postpone() to attach scroll event handlers and check if any\n\ - * elements are already visible. Then, init will start the watch process.\n\ - * @returns this\n\ - */\n\ + * The init method for Postpone gets the object running. It runs\n\ + * postpone.postpone() to attach scroll event handlers and check if any\n\ + * elements are already visible. Then, init will start the watch process.\n\ + * @returns this\n\ + */\n\ this.init = function( threshold ) {\n\ /**\n\ - * @property {string} tags - A list of all the tags for which postpone\n\ - * will work;\n\ - */\n\ + * @property {string} tags - A list of all the tags for which postpone\n\ + * will work;\n\ + */\n\ this.tags = \"audio, embed, iframe, img, image, object, picture, use, video, tref\";\n\ /**\n\ - * @property {array} elements - An array of all the postponed elements in the document.\n\ - */\n\ + * @property {array} elements - An array of all the postponed elements in the document.\n\ + */\n\ this.elements = [];\n\ /**\n\ - * @property {object} scrollElements - A variable to keep track of the\n\ - * elements with respoect to which the postponed elements scroll.\n\ - */\n\ + * @property {array} visible - An array of all the non-hidden postponed\n\ + * elements in the document.\n\ + */\n\ + this.elements.visible = [];\n\ + /**\n\ + * @property {object} scrollElements - A variable to keep track of the\n\ + * elements with respoect to which the postponed elements scroll.\n\ + */\n\ this.scrollElements = [];\n\ this.setThreshold( threshold );\n\ this.postpone();\n\ @@ -252,31 +257,31 @@ require.register("postpone/index.js", Function("exports, require, module", };\n\ \n\ /**\n\ - * The main postpone method. This method iterates over all the elements with\n\ - * a `postpone` attribute and links them to a scroll event so that they are not\n\ - * loaded until they become visible.\n\ - * @returns this\n\ - */\n\ + * The main postpone method. This method iterates over all the elements with\n\ + * a `postpone` attribute and links them to a scroll event so that they are not\n\ + * loaded until they become visible.\n\ + * @returns this\n\ + */\n\ Postpone.prototype.postpone = function() {\n\ /**\n\ - * Remove any previous event handlers so they can be reattached for new\n\ - * postponed elements without duplicating old ones. This must be done\n\ - * before updating the scroll elements so the references to the event\n\ - * callbacks still exist.\n\ - */\n\ + * Remove any previous event handlers so they can be reattached for new\n\ + * postponed elements without duplicating old ones. This must be done\n\ + * before updating the scroll elements so the references to the event\n\ + * callbacks still exist.\n\ + */\n\ this.unbindEvents();\n\ \n\ /**\n\ - * Update the elements and scroll elements to properly load or postpone\n\ - * them.\n\ - */\n\ + * Update the elements and scroll elements to properly load or postpone\n\ + * them.\n\ + */\n\ this.getElements();\n\ this.getScrollElements();\n\ \n\ /**\n\ - * If any of the postponed elements should be visible to begin with,\n\ - * load them.\n\ - */\n\ + * If any of the postponed elements should be visible to begin with,\n\ + * load them.\n\ + */\n\ for ( var id in this.scrollElements ) {\n\ for ( var i = 0, element = {}; i < this.scrollElements[ id ].length; i++ ) {\n\ element = this.scrollElements[ id ][ i ];\n\ @@ -287,7 +292,7 @@ require.register("postpone/index.js", Function("exports, require, module", }\n\ \n\ if ( this.elements.length ) {\n\ - /** Attach scroll event listeners. */\n\ + /** Attach scroll event listeners. */\n\ this.bindEvents();\n\ }\n\ \n\ @@ -295,9 +300,9 @@ require.register("postpone/index.js", Function("exports, require, module", };\n\ \n\ /**\n\ - * A helper method to unbind the scroll event callbacks.\n\ - * @returns this\n\ - */\n\ + * A helper method to unbind the scroll event callbacks.\n\ + * @returns this\n\ + */\n\ Postpone.prototype.unbindEvents = function() {\n\ for ( var id in this.scrollElements ) {\n\ this._removeEventListener( id === \"window\" ? window : this.scrollElements[ id ].element, this.scrollElements[ id ].callback );\n\ @@ -305,25 +310,29 @@ require.register("postpone/index.js", Function("exports, require, module", };\n\ \n\ /**\n\ - * A helper method to bind the scroll event callbacks.\n\ - * @returns this\n\ - */\n\ + * A helper method to bind the scroll event callbacks.\n\ + * @returns this\n\ + */\n\ Postpone.prototype.bindEvents = function() {\n\ for ( var id in this.scrollElements ) {\n\ this.scrollElements[ id ].callback = Function.prototype.bind ? this.scrollHandler.bind( this ) : function( _this ) { return function() { return _this.scrollHandler.apply( _this, arguments ); }; }( this );\n\ this._addEventListener( id === \"window\" ? window : this.scrollElements[ id ].element, this.scrollElements[ id ].callback );\n\ }\n\ +\n\ + return this;\n\ };\n\ \n\ /**\n\ - * A helper method to find all of the elements with a postponed attribute.\n\ - * @returns {array} An array of nodes with a `truthy` postpone attribute.\n\ - */\n\ + * A helper method to find all of the elements with a postponed attribute.\n\ + * @returns {Boolean} A boolean stating whether new postpone elements have been\n\ + * found.\n\ + */\n\ Postpone.prototype.getElements = function() {\n\ var elements = [],\n\ visible = [],\n\ matches = this._slice( document.querySelectorAll( this.tags ) ),\n\ - postpone = null;\n\ + postpone = null,\n\ + change = false;\n\ \n\ for ( var i = 0; i < matches.length; i++ ) {\n\ postpone = matches[ i ].getAttribute( \"postpone\" );\n\ @@ -331,26 +340,33 @@ require.register("postpone/index.js", Function("exports, require, module", elements.push( matches[ i ] );\n\ if ( this.isVisible( matches[ i ] ) ) {\n\ visible.push( matches[ i ] );\n\ + /** Check if this element is not already postponed. */\n\ + if ( !~this.elements.visible.indexOf( matches[ i ] ) ) {\n\ + change = true;\n\ + }\n\ }\n\ }\n\ }\n\ +\n\ + /** Check if old postponed elements are no longer on the page. */\n\ + if ( this.elements.visible.length !== visible.length ) {\n\ + change = true;\n\ + }\n\ this.elements = elements;\n\ - /**\n\ - * @property {array} visible - An array of all the non-hidden postponed\n\ - * elements in the document.\n\ - */\n\ this.elements.visible = visible;\n\ +\n\ + return change;\n\ };\n\ \n\ /**\n\ - * A helper method to find all of the elements with respect to which\n\ - * postponed elements scroll. The elements are stored with a unique ID as\n\ - * their key.\n\ - * @returns {object} A hash with arrays of postponed elements associated with\n\ - * IDs of their scroll elements.\n\ - */\n\ + * A helper method to find all of the elements with respect to which\n\ + * postponed elements scroll. The elements are stored with a unique ID as\n\ + * their key.\n\ + * @returns {object} A hash with arrays of postponed elements associated with\n\ + * IDs of their scroll elements.\n\ + */\n\ Postpone.prototype.getScrollElements = function() {\n\ - this.scrollElements = {};\n\ + this.scrollElements = {};\n\ \n\ var id = \"\",\n\ element = {},\n\ @@ -359,31 +375,31 @@ require.register("postpone/index.js", Function("exports, require, module", for ( var i = 0; i < this.elements.visible.length; i++ ) {\n\ element = this.elements.visible[ i ];\n\ /**\n\ - * Find the element relative to which the postponed element's\n\ - * position should be calculated.\n\ - */\n\ + * Find the element relative to which the postponed element's\n\ + * position should be calculated.\n\ + */\n\ if ( element.getAttribute( \"data-scroll-element\" ) ) {\n\ scrollElement = document.querySelector( element.getAttribute( \"data-scroll-element\" ) );\n\ /**\n\ - * If the scroll element does not have an ID, generate one and\n\ - * assign it as a data attribute.\n\ - */\n\ + * If the scroll element does not have an ID, generate one and\n\ + * assign it as a data attribute.\n\ + */\n\ id = scrollElement.getAttribute( \"data-id\" );\n\ if ( !id ) {\n\ scrollElement.setAttribute( \"data-id\", id = new Date().getTime() );\n\ }\n\ /**\n\ - * If the element does not have a scroll element specified then\n\ - * assume its position should be calculated relative to the window.\n\ - */\n\ + * If the element does not have a scroll element specified then\n\ + * assume its position should be calculated relative to the window.\n\ + */\n\ } else {\n\ scrollElement = \"window\";\n\ id = \"window\";\n\ }\n\ /**\n\ - * If the array already has this id as a key, then add the current\n\ - * element to the array in its value, otherwise create a new key.\n\ - */\n\ + * If the array already has this id as a key, then add the current\n\ + * element to the array in its value, otherwise create a new key.\n\ + */\n\ if ( this.scrollElements[ id ] ) {\n\ this.scrollElements[ id ].push( element );\n\ } else {\n\ @@ -395,43 +411,25 @@ require.register("postpone/index.js", Function("exports, require, module", };\n\ \n\ /**\n\ - * A small helper that finds the posponed elements and returns them in a\n\ - * string.\n\ - * @param {array} elements - An array of elements to stringify.\n\ - * @returns {string} A string containing all the HTML of postponed elements.\n\ - */\n\ - Postpone.prototype.stringifyElements = function( elements ) {\n\ - var elementsString = \"\";\n\ -\n\ - for ( var i = 0; i < elements.length; i++ ) {\n\ - elementsString += elements[ i ].outerHTML;\n\ - }\n\ -\n\ - return elementsString;\n\ - };\n\ -\n\ - /**\n\ - * Method to watch the document for new postponed elements.\n\ - * @param {string} [elementsString] - A string of non-visually hidden\n\ - * postponed elements.\n\ - * @returns this\n\ - */\n\ - Postpone.prototype.watch = function( elementsString ) {\n\ - /** Refresh the array of postponed elements, this.elements. */\n\ - this.getElements();\n\ - var newElementsString = this.stringifyElements( this.elements.visible );\n\ - /** If the postponed elements have changed, then postpone them. */\n\ - if ( elementsString !== newElementsString ) {\n\ + * Method to watch the document for new postponed elements.\n\ + * @returns this\n\ + */\n\ + Postpone.prototype.watch = function() {\n\ + /**\n\ + * Refresh the array of postponed elements, this.elements. If the postponed\n\ + * elements have changed, then process them.\n\ + */\n\ + if ( this.getElements() ) {\n\ this.postpone();\n\ }\n\ /**\n\ - * This timeout calls the watch method every 500ms. In other words,\n\ - * postpone will look for new postponed elements twice a second.\n\ - * @property {number} timeout - The ID for the current timeout.\n\ - */\n\ + * This timeout calls the watch method every 500ms. In other words,\n\ + * postpone will look for new postponed elements twice a second.\n\ + * @property {number} timeout - The ID for the current timeout.\n\ + */\n\ this.timeout = window.setTimeout( (function( _this ) {\n\ return function() {\n\ - return _this.watch( newElementsString );\n\ + return _this.watch();\n\ };\n\ })( this ), 500);\n\ \n\ @@ -439,9 +437,9 @@ require.register("postpone/index.js", Function("exports, require, module", };\n\ \n\ /**\n\ - * Method to start watching for elements that should postponed.\n\ - * @returns this\n\ - */\n\ + * Method to start watching for elements that should postponed.\n\ + * @returns this\n\ + */\n\ Postpone.prototype.start = function() {\n\ /** Ensure that watching has stopped before starting to watch. */\n\ if ( this.timeout ) this.stop();\n\ @@ -452,10 +450,10 @@ require.register("postpone/index.js", Function("exports, require, module", };\n\ \n\ /**\n\ - * Method to stop watching for elements that should postponed and unbind events\n\ - * associated with postponed elements.\n\ - * @returns this\n\ - */\n\ + * Method to stop watching for elements that should postponed and unbind events\n\ + * associated with postponed elements.\n\ + * @returns this\n\ + */\n\ Postpone.prototype.stop = function() {\n\ if ( this.timeout ) window.clearTimeout( this.timeout );\n\ \n\ @@ -466,11 +464,11 @@ require.register("postpone/index.js", Function("exports, require, module", };\n\ \n\ /**\n\ - * This method defines the scroll event handler used to test if postponed\n\ - * elementes are visible.\n\ - * @param {object} e - Event object.\n\ - * @returns this\n\ - */\n\ + * This method defines the scroll event handler used to test if postponed\n\ + * elementes are visible.\n\ + * @param {object} e - Event object.\n\ + * @returns this\n\ + */\n\ Postpone.prototype.scrollHandler = function( e ) {\n\ var scrollElement = e.srcElement || e.target || window.document,\n\ elements = this.scrollElements[ scrollElement === window.document ? scrollElement = \"window\" : scrollElement.getAttribute( \"data-id\" ) ],\n\ @@ -481,9 +479,9 @@ require.register("postpone/index.js", Function("exports, require, module", element = elements[ i ];\n\ \n\ /**\n\ - * If an element is visible then we no longer need to postpone it\n\ - * and can download it.\n\ - */\n\ + * If an element is visible then we no longer need to postpone it\n\ + * and can download it.\n\ + */\n\ if ( this.isInViewport( element, scrollElement ) ) {\n\ this.load( element );\n\ }\n\ @@ -493,39 +491,39 @@ require.register("postpone/index.js", Function("exports, require, module", };\n\ \n\ /**\n\ - * A convenience method to easily set the threshold property of postpone.\n\ - * @param {number|string} threshold - The distance from an edge at which an\n\ - * element should be considered to be at the edge.\n\ - * @returns this\n\ - */\n\ + * A convenience method to easily set the threshold property of postpone.\n\ + * @param {number|string} threshold - The distance from an edge at which an\n\ + * element should be considered to be at the edge.\n\ + * @returns this\n\ + */\n\ Postpone.prototype.setThreshold = function( threshold ) {\n\ threshold = threshold ? threshold : 0;\n\ /**\n\ - * @property {object} threshold - A hash containing the value and unit of\n\ - * measurement of the desired postpone threshold.\n\ - */\n\ + * @property {object} threshold - A hash containing the value and unit of\n\ + * measurement of the desired postpone threshold.\n\ + */\n\ this.threshold = {};\n\ /**\n\ - * @property {number} value - The number of units from an edge at\n\ - * which an element should be considered to be at the edge. This is\n\ - * useful to start loading images or other resources before they scroll\n\ - * into view to prevent flash of content.\n\ - */\n\ + * @property {number} value - The number of units from an edge at\n\ + * which an element should be considered to be at the edge. This is\n\ + * useful to start loading images or other resources before they scroll\n\ + * into view to prevent flash of content.\n\ + */\n\ this.threshold.value = parseInt( threshold, 10 );\n\ /**\n\ - * @property {string} unit - The unit of measurement for the threshold\n\ - * value. Currently, only `vh` and `px` are supported. By default, the unit\n\ - * is `vh`.\n\ - */\n\ + * @property {string} unit - The unit of measurement for the threshold\n\ + * value. Currently, only `vh` and `px` are supported. By default, the unit\n\ + * is `vh`.\n\ + */\n\ this.threshold.unit = ( typeof threshold === \"number\" ) ? \"vh\" : ( threshold.match(/[a-zA-Z]+/)[ 0 ] || \"vh\" );\n\ \n\ return this;\n\ };\n\ /**\n\ - * Small helper method to find the total vertical offset of an element.\n\ - * @param {object} el - The element we wish to locate.\n\ - * @returns {number} The total vertical offset of the element.\n\ - */\n\ + * Small helper method to find the total vertical offset of an element.\n\ + * @param {object} el - The element we wish to locate.\n\ + * @returns {number} The total vertical offset of the element.\n\ + */\n\ Postpone.prototype.offsetTop = function( el ) {\n\ var temp = el,\n\ o = 0;\n\ @@ -539,20 +537,20 @@ require.register("postpone/index.js", Function("exports, require, module", };\n\ \n\ /**\n\ - * Small helper method to determine if an element is visually hidden or not.\n\ - * This method check if the element provided, or any of its parents have the\n\ - * style `display: none;`.\n\ - * @param {object} el - The element we wish to locate.\n\ - * @returns {boolean} Returns true if the element is visible and false if it is\n\ - * hidden.\n\ - */\n\ + * Small helper method to determine if an element is visually hidden or not.\n\ + * This method check if the element provided, or any of its parents have the\n\ + * style `display: none;`.\n\ + * @param {object} el - The element we wish to locate.\n\ + * @returns {boolean} Returns true if the element is visible and false if it is\n\ + * hidden.\n\ + */\n\ Postpone.prototype.isVisible = function( el ) {\n\ var temp = el,\n\ isVisible = true;\n\ /**\n\ - * Iterate over all parents of el up to HTML to find if el or a parent is\n\ - * hidden.\n\ - */\n\ + * Iterate over all parents of el up to HTML to find if el or a parent is\n\ + * hidden.\n\ + */\n\ while ( temp && temp.parentElement && isVisible ) {\n\ isVisible = temp.currentStyle ? temp.currentStyle.display !== \"none\" : document.defaultView.getComputedStyle( temp ).getPropertyValue( \"display\" ) !== \"none\";\n\ temp = temp.parentElement;\n\ @@ -562,11 +560,11 @@ require.register("postpone/index.js", Function("exports, require, module", };\n\ \n\ /**\n\ - * Helper method to determine if an element is in the browser's viewport.\n\ - * @param {object} el - The element we wish to test.\n\ - * @param {object} [scrollElement] - The element with respect to which `el` scrolls.\n\ - * @returns {boolean} Return true if the `el` is in view and false if it is not.\n\ - */\n\ + * Helper method to determine if an element is in the browser's viewport.\n\ + * @param {object} el - The element we wish to test.\n\ + * @param {object} [scrollElement] - The element with respect to which `el` scrolls.\n\ + * @returns {boolean} Return true if the `el` is in view and false if it is not.\n\ + */\n\ Postpone.prototype.isInViewport = function( el, scrollElement ) {\n\ /** If no scroll element is specified, then assume the scroll element is the window. */\n\ scrollElement = scrollElement ? scrollElement : \"window\";\n\ @@ -598,11 +596,11 @@ require.register("postpone/index.js", Function("exports, require, module", };\n\ \n\ /**\n\ - * This method takes care of loading the media that should no longer be\n\ - * postponed.\n\ - * @param {object} el - The element that should be loaded.\n\ - * @returns {object} The element that was loaded.\n\ - */\n\ + * This method takes care of loading the media that should no longer be\n\ + * postponed.\n\ + * @param {object} el - The element that should be loaded.\n\ + * @returns {object} The element that was loaded.\n\ + */\n\ Postpone.prototype.load = function( el ) {\n\ var child = {},\n\ i = 0;\n\ @@ -644,10 +642,10 @@ require.register("postpone/index.js", Function("exports, require, module", el.setAttribute( \"data\", el.getAttribute( \"data-data\" ));\n\ \n\ /**\n\ - * This is necessary to make Safari (and, apparently, old versions of Chrome)\n\ - * re-render the new content; see:\n\ - * stackoverflow.com/questions/11245385/object-works-in-every-browser-except-google-chrome\n\ - */\n\ + * This is necessary to make Safari (and, apparently, old versions of Chrome)\n\ + * re-render the new content; see:\n\ + * stackoverflow.com/questions/11245385/object-works-in-every-browser-except-google-chrome\n\ + */\n\ el.innerHTML = el.innerHTML;\n\ }\n\ \n\ @@ -655,11 +653,11 @@ require.register("postpone/index.js", Function("exports, require, module", };\n\ \n\ /**\n\ - * A helper method to convert array-like objects into arrays.\n\ - * @param {object} arr - The object to be converted.\n\ - * @returns {array} An array representation of the supplied object.\n\ - * @api private\n\ - */\n\ + * A helper method to convert array-like objects into arrays.\n\ + * @param {object} arr - The object to be converted.\n\ + * @returns {array} An array representation of the supplied object.\n\ + * @api private\n\ + */\n\ Postpone.prototype._slice = function( object ) {\n\ /** Try to use `slice` to convert the object. */\n\ try {\n\ @@ -678,13 +676,13 @@ require.register("postpone/index.js", Function("exports, require, module", };\n\ \n\ /**\n\ - * A helper method to abstract event listener creation.\n\ - * @param {object} el - The element to which the event should be added.\n\ - * @param {function} callback - The callback to be executed when the event\n\ - * is fired.\n\ - * @returns undefined\n\ - * @api private\n\ - */\n\ + * A helper method to abstract event listener creation.\n\ + * @param {object} el - The element to which the event should be added.\n\ + * @param {function} callback - The callback to be executed when the event\n\ + * is fired.\n\ + * @returns undefined\n\ + * @api private\n\ + */\n\ Postpone.prototype._addEventListener = function( el, callback ) {\n\ /** Try to add the event using `addEventListener`. */\n\ try {\n\ @@ -696,13 +694,13 @@ require.register("postpone/index.js", Function("exports, require, module", };\n\ \n\ /**\n\ - * A helper method to abstract event listener removal.\n\ - * @param {object} el - The element from which the event should be removed.\n\ - * @param {function} callback - The callback to be executed when the event\n\ - * is fired.\n\ - * @returns undefined\n\ - * @api private\n\ - */\n\ + * A helper method to abstract event listener removal.\n\ + * @param {object} el - The element from which the event should be removed.\n\ + * @param {function} callback - The callback to be executed when the event\n\ + * is fired.\n\ + * @returns undefined\n\ + * @api private\n\ + */\n\ Postpone.prototype._removeEventListener = function( el, callback ) {\n\ /** Try to remove the event using `removeEventListener`. */\n\ try {\n\