/** * Copyright Marc J. Schmidt. See the LICENSE file at the top-level * directory of this distribution and at * https://github.com/marcj/css-element-queries/blob/master/LICENSE. */ ( function ( root, factory ) { if ( typeof define === 'function' && define.amd ) { define( factory ); } else if ( typeof exports === 'object' ) { module.exports = factory(); } else { root.ResizeSensor = factory(); } } )( typeof window !== 'undefined' ? window : this, function () { // Make sure it does not throw in a SSR (Server Side Rendering) situation if ( typeof window === 'undefined' ) { return null; } // https://github.com/Semantic-Org/Semantic-UI/issues/3855 // https://github.com/marcj/css-element-queries/issues/257 var globalWindow = typeof window !== 'undefined' && window.Math === Math ? window : typeof self !== 'undefined' && self.Math === Math ? self : Function( 'return this' )(); // Only used for the dirty checking, so the event callback count is limited 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 = globalWindow.requestAnimationFrame || globalWindow.mozRequestAnimationFrame || globalWindow.webkitRequestAnimationFrame || function ( fn ) { return globalWindow.setTimeout( fn, 20 ); }; var cancelAnimationFrame = globalWindow.cancelAnimationFrame || globalWindow.mozCancelAnimationFrame || globalWindow.webkitCancelAnimationFrame || function ( timer ) { globalWindow.clearTimeout( timer ); }; /** * 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 || '[object Object]' === elementsType || ( 'undefined' !== typeof jQuery && elements instanceof jQuery ) || ( 'undefined' !== typeof Elements && elements instanceof Elements ); var i = 0, j = elements.length; if ( isCollectionTyped ) { for ( ; i < j; i++ ) { callback( elements[ i ] ); } } else { callback( elements ); } } /** * Get element size * @param {HTMLElement} element * @returns {Object} {width, height} */ function getElementSize( element ) { if ( ! element.getBoundingClientRect ) { return { width: element.offsetWidth, height: element.offsetHeight, }; } var rect = element.getBoundingClientRect(); return { width: Math.round( rect.width ), height: Math.round( rect.height ), }; } /** * Apply CSS styles to element. * * @param {HTMLElement} element * @param {Object} style */ function setStyle( element, style ) { Object.keys( style ).forEach( function ( key ) { element.style[ key ] = style[ key ]; } ); } /** * Class for dimension change detection. * * @param {Element|Element[]|Elements|jQuery} element * @param {Function} callback * * @constructor */ var ResizeSensor = function ( element, callback ) { //Is used when checking in reset() only for invisible elements var lastAnimationFrameForInvisibleCheck = 0; /** * * @constructor */ function EventQueue() { var q = []; this.add = function ( ev ) { q.push( ev ); }; var i, j; this.call = function ( sizeInfo ) { for ( i = 0, j = q.length; i < j; i++ ) { q[ i ].call( this, sizeInfo ); } }; 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; }; this.length = function () { return q.length; }; } /** * * @param {HTMLElement} element * @param {Function} resized */ function attachResizeEvent( element, resized ) { if ( ! element ) return; if ( element.resizedAttached ) { element.resizedAttached.add( resized ); return; } element.resizedAttached = new EventQueue(); element.resizedAttached.add( resized ); element.resizeSensor = document.createElement( 'div' ); element.resizeSensor.dir = 'ltr'; element.resizeSensor.className = 'resize-sensor'; var style = { pointerEvents: 'none', position: 'absolute', left: '0px', top: '0px', right: '0px', bottom: '0px', overflow: 'hidden', zIndex: '-1', visibility: 'hidden', maxWidth: '100%', }; var styleChild = { position: 'absolute', left: '0px', top: '0px', transition: '0s', }; setStyle( element.resizeSensor, style ); var expand = document.createElement( 'div' ); expand.className = 'resize-sensor-expand'; setStyle( expand, style ); var expandChild = document.createElement( 'div' ); setStyle( expandChild, styleChild ); expand.appendChild( expandChild ); var shrink = document.createElement( 'div' ); shrink.className = 'resize-sensor-shrink'; setStyle( shrink, style ); var shrinkChild = document.createElement( 'div' ); setStyle( shrinkChild, styleChild ); setStyle( shrinkChild, {width: '200%', height: '200%'} ); shrink.appendChild( shrinkChild ); element.resizeSensor.appendChild( expand ); element.resizeSensor.appendChild( shrink ); element.appendChild( element.resizeSensor ); var computedStyle = window.getComputedStyle( element ); var position = computedStyle ? computedStyle.getPropertyValue( 'position' ) : null; if ( 'absolute' !== position && 'relative' !== position && 'fixed' !== position && 'sticky' !== position ) { element.style.position = 'relative'; } var dirty = false; //last request animation frame id used in onscroll event var rafId = 0; var size = getElementSize( element ); var lastWidth = 0; var lastHeight = 0; var initialHiddenCheck = true; lastAnimationFrameForInvisibleCheck = 0; var resetExpandShrink = function () { var width = element.offsetWidth; var height = element.offsetHeight; expandChild.style.width = width + 10 + 'px'; expandChild.style.height = height + 10 + 'px'; expand.scrollLeft = width + 10; expand.scrollTop = height + 10; shrink.scrollLeft = width + 10; shrink.scrollTop = height + 10; }; var reset = function () { // Check if element is hidden if ( initialHiddenCheck ) { var invisible = element.offsetWidth === 0 && element.offsetHeight === 0; if ( invisible ) { // Check in next frame if ( ! lastAnimationFrameForInvisibleCheck ) { lastAnimationFrameForInvisibleCheck = requestAnimationFrame( function () { lastAnimationFrameForInvisibleCheck = 0; reset(); } ); } return; } else { // Stop checking initialHiddenCheck = false; } } resetExpandShrink(); }; element.resizeSensor.resetSensor = reset; var onResized = function () { rafId = 0; if ( ! dirty ) return; lastWidth = size.width; lastHeight = size.height; if ( element.resizedAttached ) { element.resizedAttached.call( size ); } }; var onScroll = function () { size = getElementSize( element ); dirty = size.width !== lastWidth || size.height !== lastHeight; if ( dirty && ! rafId ) { rafId = requestAnimationFrame( onResized ); } reset(); }; var addEvent = function ( el, name, cb ) { if ( el.attachEvent ) { el.attachEvent( 'on' + name, cb ); } else { el.addEventListener( name, cb ); } }; addEvent( expand, 'scroll', onScroll ); addEvent( shrink, 'scroll', onScroll ); // Fix for custom Elements and invisible elements lastAnimationFrameForInvisibleCheck = requestAnimationFrame( function () { lastAnimationFrameForInvisibleCheck = 0; reset(); } ); } forEachElement( element, function ( elem ) { attachResizeEvent( elem, callback ); } ); this.detach = function ( ev ) { // clean up the unfinished animation frame to prevent a potential endless requestAnimationFrame of reset if ( lastAnimationFrameForInvisibleCheck ) { cancelAnimationFrame( lastAnimationFrameForInvisibleCheck ); lastAnimationFrameForInvisibleCheck = 0; } ResizeSensor.detach( element, ev ); }; this.reset = function () { //To prevent invoking element.resizeSensor.resetSensor if it's undefined if ( element.resizeSensor.resetSensor ) { element.resizeSensor.resetSensor(); } }; }; ResizeSensor.reset = function ( element ) { forEachElement( element, function ( elem ) { //To prevent invoking element.resizeSensor.resetSensor if it's undefined if ( element.resizeSensor.resetSensor ) { elem.resizeSensor.resetSensor(); } } ); }; ResizeSensor.detach = function ( element, ev ) { forEachElement( element, function ( elem ) { if ( ! elem ) return; if ( elem.resizedAttached && typeof ev === 'function' ) { elem.resizedAttached.remove( ev ); if ( elem.resizedAttached.length() ) return; } if ( elem.resizeSensor ) { if ( elem.contains( elem.resizeSensor ) ) { elem.removeChild( elem.resizeSensor ); } delete elem.resizeSensor; delete elem.resizedAttached; } } ); }; if ( typeof MutationObserver !== 'undefined' ) { var observer = new MutationObserver( function ( mutations ) { for ( var i in mutations ) { if ( mutations.hasOwnProperty( i ) ) { var items = mutations[ i ].addedNodes; for ( var j = 0; j < items.length; j++ ) { if ( items[ j ].resizeSensor ) { ResizeSensor.reset( items[ j ] ); } } } } } ); document.addEventListener( 'DOMContentLoaded', function () { observer.observe( document.body, { childList: true, subtree: true, } ); } ); } return ResizeSensor; } );