diff --git a/src/ResizeSensor.js b/src/ResizeSensor.js index 648a713..5196def 100755 --- a/src/ResizeSensor.js +++ b/src/ResizeSensor.js @@ -4,49 +4,18 @@ * https://github.com/marcj/css-element-queries/blob/master/LICENSE. */ ; -(function (root, factory) { - if (typeof define === "function" && define.amd) { +(function ( root, factory ) { + if ( typeof define === "function" && define.amd ) { define(factory); - } else if (typeof exports === "object") { + } else if ( typeof exports === "object" ) { module.exports = factory(); } else { root.ResizeSensor = factory(); } -}(this, function () { - - // Only used for the dirty checking, so the event callback count is limted to max 1 call per fps per sensor. - // In combination with the event based resize sensor this saves cpu time, because the sensor is too fast and - // would generate too many unnecessary events. - var requestAnimationFrame = window.requestAnimationFrame || - window.mozRequestAnimationFrame || - window.webkitRequestAnimationFrame || - function (fn) { - return window.setTimeout(fn, 20); - }; +}( this, function () { - /** - * Iterate over each of the provided element(s). - * - * @param {HTMLElement|HTMLElement[]} elements - * @param {Function} callback - */ - function forEachElement(elements, callback){ - var elementsType = Object.prototype.toString.call(elements); - var isCollectionTyped = ('[object Array]' === elementsType - || ('[object NodeList]' === elementsType) - || ('[object HTMLCollection]' === elementsType) - || ('undefined' !== typeof jQuery && elements instanceof jQuery) //jquery - || ('undefined' !== typeof Elements && elements instanceof Elements) //mootools - ); - var i = 0, j = elements.length; - if (isCollectionTyped) { - for (; i < j; i++) { - callback(elements[i]); - } - } else { - callback(elements); - } - } + + var events = new EventQueue(); /** * Class for dimension change detection. @@ -56,163 +25,270 @@ * * @constructor */ - var ResizeSensor = function(element, callback) { - /** - * - * @constructor - */ - function EventQueue() { - var q = []; - this.add = function(ev) { - q.push(ev); - }; - - var i, j; - this.call = function() { - for (i = 0, j = q.length; i < j; i++) { - q[i].call(); - } - }; + var ResizeSensor = function ( element, callback ) { + var that = this; - this.remove = function(ev) { - var newQueue = []; - for(i = 0, j = q.length; i < j; i++) { - if(q[i] !== ev) newQueue.push(q[i]); - } - q = newQueue; - } + // Add class variables here + that.element = element; + that.callback = callback; - this.length = function() { - return q.length; + forEachElement( element, function( elem ){ + if( !elem.resizeSensor ) { + appendDetectors( that, elem ); } + + events.add( elem, callback ); + }); + }; + + ResizeSensor.prototype = { + constructor: ResizeSensor, + + detach: function ResizeSensorDetach ( ev, element ) { + ResizeSensor.detach( element || this.element, ev ); + }, + + reset: function ResizeSensorResize () { + forEachElement( element, function( elem ){ + if( elem.resizeSensor ) { + reset( that, elem ); + } + }); } + }; - /** - * @param {HTMLElement} element - * @param {String} prop - * @returns {String|Number} - */ - function getComputedStyle(element, prop) { - if (element.currentStyle) { - return element.currentStyle[prop]; - } else if (window.getComputedStyle) { - return window.getComputedStyle(element, null).getPropertyValue(prop); - } else { - return element.style[prop]; - } + /** + * Function for appending the detectors to the elements + */ + function appendDetectors ( that, element ) { + var style = 'display: block; position: absolute; left: 0; top: 0; right: 0; bottom: 0; overflow: scroll; z-index: -1; visibility: hidden;', + styleChild = 'position: absolute; left: 0; top: 0; transition: 0s;'; + + // Set the elements position to enable the ability to use top/right/bottom/left + if ( getComputedStyle(element, 'position') == 'static' ) { + element.style.position = 'relative'; } - /** - * - * @param {HTMLElement} element - * @param {Function} resized - */ - function attachResizeEvent(element, resized) { - if (!element.resizedAttached) { - element.resizedAttached = new EventQueue(); - element.resizedAttached.add(resized); - } else if (element.resizedAttached) { - element.resizedAttached.add(resized); - return; - } + // Create the sensor element and set the styles and innerHTML + var rSE = document.createElement( 'resize-sensor' ); + rSE.className = 'resize-sensor'; - element.resizeSensor = document.createElement('div'); - element.resizeSensor.className = 'resize-sensor'; - var style = 'position: absolute; left: 0; top: 0; right: 0; bottom: 0; overflow: hidden; z-index: -1; visibility: hidden;'; - var styleChild = 'position: absolute; left: 0; top: 0; transition: 0s;'; - - element.resizeSensor.style.cssText = style; - element.resizeSensor.innerHTML = - '
' + - '
' + - '
' + - '
' + - '
' + - '
'; - element.appendChild(element.resizeSensor); - - if (getComputedStyle(element, 'position') == 'static') { - element.style.position = 'relative'; - } + rSE.style.cssText = style; + rSE.innerHTML = + '
' + + '
' + + '
' + + '
' + + '
' + + '
' - var expand = element.resizeSensor.childNodes[0]; - var expandChild = expand.childNodes[0]; - var shrink = element.resizeSensor.childNodes[1]; + rSE.element = element; + + rSE.resetting = false; - var reset = function() { - expandChild.style.width = 100000 + 'px'; - expandChild.style.height = 100000 + 'px'; + rSE.lastWidth = element.offsetWidth; + rSE.lastHeight = element.offsetHeight; + rSE.cacheWidth = null; + rSE.cacheHeight = null; - expand.scrollLeft = 100000; - expand.scrollTop = 100000; + rSE.expand = rSE.childNodes[0]; + rSE.expandChild = rSE.expand.childNodes[0]; + rSE.shrink = rSE.childNodes[1]; + rSE.shrinkChild = rSE.shrink.childNodes[0]; - shrink.scrollLeft = 100000; - shrink.scrollTop = 100000; - }; + rSE.detach = function ResizeSensorDetach () { + that.detach( that.callback, element ); + }; - reset(); - var dirty = false; + rSE.reset = function ResizeSensorReset () { + reset( rSE ); + }; - var dirtyChecking = function() { - if (!element.resizedAttached) return; + element.resizeSensor = rSE; + + element.appendChild( element.resizeSensor ); + reset( element.resizeSensor ); + } - if (dirty) { - element.resizedAttached.call(); - dirty = false; + /** + * Class for event queueing and management + * + * @constructor + */ + function EventQueue () { + var that = this, + q = {}, + guid = 1; + this.add = function ( element, ev ) { + var guid = element.guid || 'rs-' + (guid++); + element.rsguid = guid; + q[guid] = q[guid] || []; + q[guid].push( ev ); + }; + + var i, j; + this.call = function ( element, context, arguments ) { + var guid = element.rsguid || ''; + if( !!q[guid] ) { + var evl = q[guid]; + for ( i = 0, j = evl.length; i < j; i++ ) { + evl[i].apply( context, arguments ); } + } + }; - requestAnimationFrame(dirtyChecking); - }; + this.remove = function ( element, ev ) { + var newQueue = []; + if( !!q[element.rsguid] ) { + var evl = q[element.rsguid]; + for( i = 0, j = evl.length; i < j; i++ ) { + if(evl[i] !== ev) { + newQueue.push( evl[i] ); + } + } + q[element.rsguid] = newQueue; + } + }; - requestAnimationFrame(dirtyChecking); - var lastWidth, lastHeight; - var cachedWidth, cachedHeight; //useful to not query offsetWidth twice + this.length = function ( element ) { + if( !!q[element.rsguid] ) { + return q[element.rsguid].length; + } + }; + } - var onScroll = function() { - if ((cachedWidth = element.offsetWidth) != lastWidth || (cachedHeight = element.offsetHeight) != lastHeight) { - dirty = true; - lastWidth = cachedWidth; - lastHeight = cachedHeight; - } - reset(); - }; + var collectionTypes = ['[object Array]', '[object NodeList]', '[object HTMLCollection]']; + /** + * Iterate over each of the provided element(s). + * + * @param {HTMLElement|HTMLElement[]} elements + * @param {Function} callback + */ + function forEachElement ( elements, callback ){ + if( !elements ) { + return; + } + var elementsType = Object.prototype.toString.call( elements ), + isVanillaCollection = collectionTypes.indexOf(elementsType) != -1, + isJQueryCollection = 'undefined' !== typeof Elements && elements instanceof Elements, + isMooToolsCollection = 'undefined' !== typeof Elements && elements instanceof Elements, + isCollectionTyped = isVanillaCollection || isJQueryCollection || isMooToolsCollection, - var addEvent = function(el, name, cb) { - if (el.attachEvent) { - el.attachEvent('on' + name, cb); - } else { - el.addEventListener(name, cb); - } - }; + i = 0, j = elements.length; + if ( isCollectionTyped ) { + for (; i < j; i++) { + callback( elements[i] ); + } + } else { + callback( elements ); + } + } - addEvent(expand, 'scroll', onScroll); - addEvent(shrink, 'scroll', onScroll); + /** + * @param {HTMLElement} element + * @param {String} prop + * @returns {String|Number} + */ + function getComputedStyle ( element, prop ) { + if ( element.currentStyle ) { + return element.currentStyle[prop]; + } else if ( window.getComputedStyle ) { + return window.getComputedStyle( element, null ).getPropertyValue( prop ); + } else { + return element.style[prop]; } + } - forEachElement(element, function(elem){ - attachResizeEvent(elem, callback); - }); + function hasClass ( element, className ) { + return (" "+ element.className +" ").replace(/[\n\t]/g, " ").indexOf(" "+ className +" ") > -1; + } - this.detach = function(ev) { - ResizeSensor.detach(element, ev); - }; - }; + function live (eventType, elementQuerySelector, cb) { + document.addEventListener( eventType, function (event) { + var qs = document.querySelectorAll(elementQuerySelector); - ResizeSensor.detach = function(element, ev) { - forEachElement(element, function(elem){ - if(elem.resizedAttached && typeof ev == "function"){ - elem.resizedAttached.remove(ev); - if(elem.resizedAttached.length()) return; + if (qs) { + var el = event.target, index = -1; + while (el && ((index = Array.prototype.indexOf.call(qs, el)) === -1)) { + el = el.parentElement; + } + + if (index > -1) { + cb.call(el, event); + } } - if (elem.resizeSensor) { - elem.removeChild(elem.resizeSensor); + }, true ); + } + + /** + * Reset the size and scroll position of the resizeSensor element + * @param {[type]} rSE resizeSensor Element + */ + function reset ( rSE ) { + rSE.resetting = true; + rSE.expandChild.style.width = '100000px'; + rSE.expandChild.style.height = '100000px'; + + + rSE.expand.scrollLeft = 100000; + rSE.expand.scrollTop = 100000; + + rSE.shrink.scrollLeft = 100000; + rSE.shrink.scrollTop = 100000; + rSE.resetting = false; + } + + function scrollHandler ( ev ) { + var + that = this, + detector = ev.target, + + type, + rSE = detector.parentNode, + elem = rSE.element; + if( elem && !rSE.resetting ) { + if ( + elem.offsetWidth != rSE.lastWidth + || elem.offsetHeight != rSE.lastHeight + ) { + events.call( elem, elem, [ + ev, + { + width: rSE.offsetWidth, + widthDifference: rSE.offsetWidth - rSE.lastWidth, + height: rSE.offsetHeight, + heightDifference: rSE.offsetHeight - rSE.lastHeight + } + ]); + + rSE.lastWidth = rSE.offsetWidth; + rSE.lastHeight = rSE.offsetHeight; + } + reset( rSE ); + } + } + + ResizeSensor.detach = function( element, ev ) { + forEachElement( element, function( elem ){ + if( typeof ev == "function" ){ + events.remove( elem, ev ); + if( elem.resizedAttached.length( elem ) ) { + return; + } + } + if ( elem.resizeSensor ) { + if(elem.resizeSensor.parentNode) { + elem.removeChild( elem.resizeSensor ); + } delete elem.resizeSensor; delete elem.resizedAttached; } }); }; + + // use only one event for all sensors + live( 'scroll', '.resize-sensor', scrollHandler ); return ResizeSensor; - }));