1
0

ResizeSensor.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. 'use strict';
  2. /**
  3. * Copyright Marc J. Schmidt. See the LICENSE file at the top-level
  4. * directory of this distribution and at
  5. * https://github.com/marcj/css-element-queries/blob/master/LICENSE.
  6. */
  7. (function (root, factory) {
  8. if (typeof define === "function" && define.amd) {
  9. define(factory);
  10. } else if (typeof exports === "object") {
  11. module.exports = factory();
  12. } else {
  13. root.ResizeSensor = factory();
  14. }
  15. }(typeof window !== 'undefined' ? window : this, function () {
  16. // Make sure it does not throw in a SSR (Server Side Rendering) situation
  17. if (typeof window === "undefined") {
  18. return null;
  19. }
  20. // https://github.com/Semantic-Org/Semantic-UI/issues/3855
  21. // https://github.com/marcj/css-element-queries/issues/257
  22. var globalWindow = typeof window != 'undefined' && window.Math == Math
  23. ? window
  24. : typeof self != 'undefined' && self.Math == Math
  25. ? self
  26. : Function('return this')();
  27. // Only used for the dirty checking, so the event callback count is limited to max 1 call per fps per sensor.
  28. // In combination with the event based resize sensor this saves cpu time, because the sensor is too fast and
  29. // would generate too many unnecessary events.
  30. var requestAnimationFrame = globalWindow.requestAnimationFrame ||
  31. globalWindow.mozRequestAnimationFrame ||
  32. globalWindow.webkitRequestAnimationFrame ||
  33. function (fn) {
  34. return globalWindow.setTimeout(fn, 20);
  35. };
  36. /**
  37. * Iterate over each of the provided element(s).
  38. *
  39. * @param {HTMLElement|HTMLElement[]} elements
  40. * @param {Function} callback
  41. */
  42. function forEachElement(elements, callback){
  43. var elementsType = Object.prototype.toString.call(elements);
  44. var isCollectionTyped = ('[object Array]' === elementsType
  45. || ('[object NodeList]' === elementsType)
  46. || ('[object HTMLCollection]' === elementsType)
  47. || ('[object Object]' === elementsType)
  48. || ('undefined' !== typeof jQuery && elements instanceof jQuery) //jquery
  49. || ('undefined' !== typeof Elements && elements instanceof Elements) //mootools
  50. );
  51. var i = 0, j = elements.length;
  52. if (isCollectionTyped) {
  53. for (; i < j; i++) {
  54. callback(elements[i]);
  55. }
  56. } else {
  57. callback(elements);
  58. }
  59. }
  60. /**
  61. * Get element size
  62. * @param {HTMLElement} element
  63. * @returns {Object} {width, height}
  64. */
  65. function getElementSize(element) {
  66. if (!element.getBoundingClientRect) {
  67. return {
  68. width: element.offsetWidth,
  69. height: element.offsetHeight
  70. }
  71. }
  72. var rect = element.getBoundingClientRect();
  73. return {
  74. width: Math.round(rect.width),
  75. height: Math.round(rect.height)
  76. }
  77. }
  78. /**
  79. * Apply CSS styles to element.
  80. *
  81. * @param {HTMLElement} element
  82. * @param {Object} style
  83. */
  84. function setStyle(element, style) {
  85. Object.keys(style).forEach(function(key) {
  86. element.style[key] = style[key];
  87. });
  88. }
  89. /**
  90. * Class for dimension change detection.
  91. *
  92. * @param {Element|Element[]|Elements|jQuery} element
  93. * @param {Function} callback
  94. *
  95. * @constructor
  96. */
  97. var ResizeSensor = function(element, callback) {
  98. /**
  99. *
  100. * @constructor
  101. */
  102. function EventQueue() {
  103. var q = [];
  104. this.add = function(ev) {
  105. q.push(ev);
  106. };
  107. var i, j;
  108. this.call = function(sizeInfo) {
  109. for (i = 0, j = q.length; i < j; i++) {
  110. q[i].call(this, sizeInfo);
  111. }
  112. };
  113. this.remove = function(ev) {
  114. var newQueue = [];
  115. for(i = 0, j = q.length; i < j; i++) {
  116. if(q[i] !== ev) newQueue.push(q[i]);
  117. }
  118. q = newQueue;
  119. };
  120. this.length = function() {
  121. return q.length;
  122. }
  123. }
  124. /**
  125. *
  126. * @param {HTMLElement} element
  127. * @param {Function} resized
  128. */
  129. function attachResizeEvent(element, resized) {
  130. if (!element) return;
  131. if (element.resizedAttached) {
  132. element.resizedAttached.add(resized);
  133. return;
  134. }
  135. element.resizedAttached = new EventQueue();
  136. element.resizedAttached.add(resized);
  137. element.resizeSensor = document.createElement('div');
  138. element.resizeSensor.dir = 'ltr';
  139. element.resizeSensor.className = 'resize-sensor';
  140. var style = {
  141. pointerEvents: 'none',
  142. position: 'absolute',
  143. left: '0px',
  144. top: '0px',
  145. right: '0px',
  146. bottom: '0px',
  147. overflow: 'hidden',
  148. zIndex: '-1',
  149. visibility: 'hidden',
  150. maxWidth: '100%'
  151. };
  152. var styleChild = {
  153. position: 'absolute',
  154. left: '0px',
  155. top: '0px',
  156. transition: '0s',
  157. };
  158. setStyle(element.resizeSensor, style);
  159. var expand = document.createElement('div');
  160. expand.className = 'resize-sensor-expand';
  161. setStyle(expand, style);
  162. var expandChild = document.createElement('div');
  163. setStyle(expandChild, styleChild);
  164. expand.appendChild(expandChild);
  165. var shrink = document.createElement('div');
  166. shrink.className = 'resize-sensor-shrink';
  167. setStyle(shrink, style);
  168. var shrinkChild = document.createElement('div');
  169. setStyle(shrinkChild, styleChild);
  170. setStyle(shrinkChild, { width: '200%', height: '200%' });
  171. shrink.appendChild(shrinkChild);
  172. element.resizeSensor.appendChild(expand);
  173. element.resizeSensor.appendChild(shrink);
  174. element.appendChild(element.resizeSensor);
  175. var computedStyle = window.getComputedStyle(element);
  176. var position = computedStyle ? computedStyle.getPropertyValue('position') : null;
  177. if ('absolute' !== position && 'relative' !== position && 'fixed' !== position) {
  178. element.style.position = 'relative';
  179. }
  180. var dirty, rafId;
  181. var size = getElementSize(element);
  182. var lastWidth = 0;
  183. var lastHeight = 0;
  184. var initialHiddenCheck = true;
  185. var lastAnimationFrame = 0;
  186. var resetExpandShrink = function () {
  187. var width = element.offsetWidth;
  188. var height = element.offsetHeight;
  189. expandChild.style.width = (width + 10) + 'px';
  190. expandChild.style.height = (height + 10) + 'px';
  191. expand.scrollLeft = width + 10;
  192. expand.scrollTop = height + 10;
  193. shrink.scrollLeft = width + 10;
  194. shrink.scrollTop = height + 10;
  195. };
  196. var reset = function() {
  197. // Check if element is hidden
  198. if (initialHiddenCheck) {
  199. var invisible = element.offsetWidth === 0 && element.offsetHeight === 0;
  200. if (invisible) {
  201. // Check in next frame
  202. if (!lastAnimationFrame){
  203. lastAnimationFrame = requestAnimationFrame(function(){
  204. lastAnimationFrame = 0;
  205. reset();
  206. });
  207. }
  208. return;
  209. } else {
  210. // Stop checking
  211. initialHiddenCheck = false;
  212. }
  213. }
  214. resetExpandShrink();
  215. };
  216. element.resizeSensor.resetSensor = reset;
  217. var onResized = function() {
  218. rafId = 0;
  219. if (!dirty) return;
  220. lastWidth = size.width;
  221. lastHeight = size.height;
  222. if (element.resizedAttached) {
  223. element.resizedAttached.call(size);
  224. }
  225. };
  226. var onScroll = function() {
  227. size = getElementSize(element);
  228. dirty = size.width !== lastWidth || size.height !== lastHeight;
  229. if (dirty && !rafId) {
  230. rafId = requestAnimationFrame(onResized);
  231. }
  232. reset();
  233. };
  234. var addEvent = function(el, name, cb) {
  235. if (el.attachEvent) {
  236. el.attachEvent('on' + name, cb);
  237. } else {
  238. el.addEventListener(name, cb);
  239. }
  240. };
  241. addEvent(expand, 'scroll', onScroll);
  242. addEvent(shrink, 'scroll', onScroll);
  243. // Fix for custom Elements
  244. requestAnimationFrame(reset);
  245. }
  246. forEachElement(element, function(elem){
  247. attachResizeEvent(elem, callback);
  248. });
  249. this.detach = function(ev) {
  250. ResizeSensor.detach(element, ev);
  251. };
  252. this.reset = function() {
  253. element.resizeSensor.resetSensor();
  254. };
  255. };
  256. ResizeSensor.reset = function(element) {
  257. forEachElement(element, function(elem){
  258. elem.resizeSensor.resetSensor();
  259. });
  260. };
  261. ResizeSensor.detach = function(element, ev) {
  262. forEachElement(element, function(elem){
  263. if (!elem) return;
  264. if(elem.resizedAttached && typeof ev === "function"){
  265. elem.resizedAttached.remove(ev);
  266. if(elem.resizedAttached.length()) return;
  267. }
  268. if (elem.resizeSensor) {
  269. if (elem.contains(elem.resizeSensor)) {
  270. elem.removeChild(elem.resizeSensor);
  271. }
  272. delete elem.resizeSensor;
  273. delete elem.resizedAttached;
  274. }
  275. });
  276. };
  277. if (typeof MutationObserver !== "undefined") {
  278. var observer = new MutationObserver(function (mutations) {
  279. for (var i in mutations) {
  280. if (mutations.hasOwnProperty(i)) {
  281. var items = mutations[i].addedNodes;
  282. for (var j = 0; j < items.length; j++) {
  283. if (items[j].resizeSensor) {
  284. ResizeSensor.reset(items[j]);
  285. }
  286. }
  287. }
  288. }
  289. });
  290. document.addEventListener("DOMContentLoaded", function (event) {
  291. observer.observe(document.body, {
  292. childList: true,
  293. subtree: true,
  294. });
  295. });
  296. }
  297. return ResizeSensor;
  298. }));