ResizeSensor.js 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. /**
  2. * Copyright Marc J. Schmidt. See the LICENSE file at the top-level
  3. * directory of this distribution and at
  4. * https://github.com/marcj/css-element-queries/blob/master/LICENSE.
  5. */
  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. // Only used for the dirty checking, so the event callback count is limited to max 1 call per fps per sensor.
  21. // In combination with the event based resize sensor this saves cpu time, because the sensor is too fast and
  22. // would generate too many unnecessary events.
  23. var requestAnimationFrame = window.requestAnimationFrame ||
  24. window.mozRequestAnimationFrame ||
  25. window.webkitRequestAnimationFrame ||
  26. function (fn) {
  27. return window.setTimeout(fn, 20);
  28. };
  29. /**
  30. * Iterate over each of the provided element(s).
  31. *
  32. * @param {HTMLElement|HTMLElement[]} elements
  33. * @param {Function} callback
  34. */
  35. function forEachElement(elements, callback){
  36. var elementsType = Object.prototype.toString.call(elements);
  37. var isCollectionTyped = ('[object Array]' === elementsType
  38. || ('[object NodeList]' === elementsType)
  39. || ('[object HTMLCollection]' === elementsType)
  40. || ('[object Object]' === elementsType)
  41. || ('undefined' !== typeof jQuery && elements instanceof jQuery) //jquery
  42. || ('undefined' !== typeof Elements && elements instanceof Elements) //mootools
  43. );
  44. var i = 0, j = elements.length;
  45. if (isCollectionTyped) {
  46. for (; i < j; i++) {
  47. callback(elements[i]);
  48. }
  49. } else {
  50. callback(elements);
  51. }
  52. }
  53. /**
  54. * Class for dimension change detection.
  55. *
  56. * @param {Element|Element[]|Elements|jQuery} element
  57. * @param {Function} callback
  58. *
  59. * @constructor
  60. */
  61. var ResizeSensor = function(element, callback) {
  62. /**
  63. *
  64. * @constructor
  65. */
  66. function EventQueue() {
  67. var q = [];
  68. this.add = function(ev) {
  69. q.push(ev);
  70. };
  71. var i, j;
  72. this.call = function() {
  73. for (i = 0, j = q.length; i < j; i++) {
  74. q[i].call();
  75. }
  76. };
  77. this.remove = function(ev) {
  78. var newQueue = [];
  79. for(i = 0, j = q.length; i < j; i++) {
  80. if(q[i] !== ev) newQueue.push(q[i]);
  81. }
  82. q = newQueue;
  83. }
  84. this.length = function() {
  85. return q.length;
  86. }
  87. }
  88. /**
  89. *
  90. * @param {HTMLElement} element
  91. * @param {Function} resized
  92. */
  93. function attachResizeEvent(element, resized) {
  94. if (!element) return;
  95. if (element.resizedAttached) {
  96. element.resizedAttached.add(resized);
  97. return;
  98. }
  99. element.resizedAttached = new EventQueue();
  100. element.resizedAttached.add(resized);
  101. element.resizeSensor = document.createElement('div');
  102. element.resizeSensor.className = 'resize-sensor';
  103. var style = 'position: absolute; left: 0; top: 0; right: 0; bottom: 0; overflow: hidden; z-index: -1; visibility: hidden;';
  104. var styleChild = 'position: absolute; left: 0; top: 0; transition: 0s;';
  105. element.resizeSensor.style.cssText = style;
  106. element.resizeSensor.innerHTML =
  107. '<div class="resize-sensor-expand" style="' + style + '">' +
  108. '<div style="' + styleChild + '"></div>' +
  109. '</div>' +
  110. '<div class="resize-sensor-shrink" style="' + style + '">' +
  111. '<div style="' + styleChild + ' width: 200%; height: 200%"></div>' +
  112. '</div>';
  113. element.appendChild(element.resizeSensor);
  114. if (element.resizeSensor.offsetParent !== element) {
  115. element.style.position = 'relative';
  116. }
  117. var expand = element.resizeSensor.childNodes[0];
  118. var expandChild = expand.childNodes[0];
  119. var shrink = element.resizeSensor.childNodes[1];
  120. var dirty, rafId, newWidth, newHeight;
  121. var lastWidth = element.offsetWidth;
  122. var lastHeight = element.offsetHeight;
  123. var reset = function() {
  124. expandChild.style.width = '100000px';
  125. expandChild.style.height = '100000px';
  126. expand.scrollLeft = 100000;
  127. expand.scrollTop = 100000;
  128. shrink.scrollLeft = 100000;
  129. shrink.scrollTop = 100000;
  130. };
  131. reset();
  132. var onResized = function() {
  133. rafId = 0;
  134. if (!dirty) return;
  135. lastWidth = newWidth;
  136. lastHeight = newHeight;
  137. if (element.resizedAttached) {
  138. element.resizedAttached.call();
  139. }
  140. };
  141. var onScroll = function() {
  142. newWidth = element.offsetWidth;
  143. newHeight = element.offsetHeight;
  144. dirty = newWidth != lastWidth || newHeight != lastHeight;
  145. if (dirty && !rafId) {
  146. rafId = requestAnimationFrame(onResized);
  147. }
  148. reset();
  149. };
  150. var addEvent = function(el, name, cb) {
  151. if (el.attachEvent) {
  152. el.attachEvent('on' + name, cb);
  153. } else {
  154. el.addEventListener(name, cb);
  155. }
  156. };
  157. addEvent(expand, 'scroll', onScroll);
  158. addEvent(shrink, 'scroll', onScroll);
  159. }
  160. forEachElement(element, function(elem){
  161. attachResizeEvent(elem, callback);
  162. });
  163. this.detach = function(ev) {
  164. ResizeSensor.detach(element, ev);
  165. };
  166. };
  167. ResizeSensor.detach = function(element, ev) {
  168. forEachElement(element, function(elem){
  169. if (!elem) return
  170. if(elem.resizedAttached && typeof ev == "function"){
  171. elem.resizedAttached.remove(ev);
  172. if(elem.resizedAttached.length()) return;
  173. }
  174. if (elem.resizeSensor) {
  175. if (elem.contains(elem.resizeSensor)) {
  176. elem.removeChild(elem.resizeSensor);
  177. }
  178. delete elem.resizeSensor;
  179. delete elem.resizedAttached;
  180. }
  181. });
  182. };
  183. return ResizeSensor;
  184. }));