'use strict'; /** * 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) //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); } } /** * 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 (event) { observer.observe(document.body, { childList: true, subtree: true, }); }); } return ResizeSensor; }));