ResizeSensor.js 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  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. }(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. * @param {HTMLElement} element
  90. * @param {String} prop
  91. * @returns {String|Number}
  92. */
  93. function getComputedStyle(element, prop) {
  94. if (element.currentStyle) {
  95. return element.currentStyle[prop];
  96. }
  97. if (window.getComputedStyle) {
  98. return window.getComputedStyle(element, null).getPropertyValue(prop);
  99. }
  100. return element.style[prop];
  101. }
  102. /**
  103. *
  104. * @param {HTMLElement} element
  105. * @param {Function} resized
  106. */
  107. function attachResizeEvent(element, resized) {
  108. if (element.resizedAttached) {
  109. element.resizedAttached.add(resized);
  110. return;
  111. }
  112. element.resizedAttached = new EventQueue();
  113. element.resizedAttached.add(resized);
  114. element.resizeSensor = document.createElement('div');
  115. element.resizeSensor.className = 'resize-sensor';
  116. var style = 'position: absolute; left: 0; top: 0; right: 0; bottom: 0; overflow: hidden; z-index: -1; visibility: hidden;';
  117. var styleChild = 'position: absolute; left: 0; top: 0; transition: 0s;';
  118. element.resizeSensor.style.cssText = style;
  119. element.resizeSensor.innerHTML =
  120. '<div class="resize-sensor-expand" style="' + style + '">' +
  121. '<div style="' + styleChild + '"></div>' +
  122. '</div>' +
  123. '<div class="resize-sensor-shrink" style="' + style + '">' +
  124. '<div style="' + styleChild + ' width: 200%; height: 200%"></div>' +
  125. '</div>';
  126. element.appendChild(element.resizeSensor);
  127. if (getComputedStyle(element, 'position') == 'static') {
  128. element.style.position = 'relative';
  129. }
  130. var expand = element.resizeSensor.childNodes[0];
  131. var expandChild = expand.childNodes[0];
  132. var shrink = element.resizeSensor.childNodes[1];
  133. var dirty, rafId, newWidth, newHeight;
  134. var lastWidth = element.offsetWidth;
  135. var lastHeight = element.offsetHeight;
  136. var reset = function() {
  137. expandChild.style.width = '100000px';
  138. expandChild.style.height = '100000px';
  139. expand.scrollLeft = 100000;
  140. expand.scrollTop = 100000;
  141. shrink.scrollLeft = 100000;
  142. shrink.scrollTop = 100000;
  143. };
  144. reset();
  145. var onResized = function() {
  146. rafId = 0;
  147. if (!dirty) return;
  148. lastWidth = newWidth;
  149. lastHeight = newHeight;
  150. if (element.resizedAttached) {
  151. element.resizedAttached.call();
  152. }
  153. };
  154. var onScroll = function() {
  155. newWidth = element.offsetWidth;
  156. newHeight = element.offsetHeight;
  157. dirty = newWidth != lastWidth || newHeight != lastHeight;
  158. if (dirty && !rafId) {
  159. rafId = requestAnimationFrame(onResized);
  160. }
  161. reset();
  162. };
  163. var addEvent = function(el, name, cb) {
  164. if (el.attachEvent) {
  165. el.attachEvent('on' + name, cb);
  166. } else {
  167. el.addEventListener(name, cb);
  168. }
  169. };
  170. addEvent(expand, 'scroll', onScroll);
  171. addEvent(shrink, 'scroll', onScroll);
  172. }
  173. forEachElement(element, function(elem){
  174. attachResizeEvent(elem, callback);
  175. });
  176. this.detach = function(ev) {
  177. ResizeSensor.detach(element, ev);
  178. };
  179. };
  180. ResizeSensor.detach = function(element, ev) {
  181. forEachElement(element, function(elem){
  182. if(elem.resizedAttached && typeof ev == "function"){
  183. elem.resizedAttached.remove(ev);
  184. if(elem.resizedAttached.length()) return;
  185. }
  186. if (elem.resizeSensor) {
  187. if (elem.contains(elem.resizeSensor)) {
  188. elem.removeChild(elem.resizeSensor);
  189. }
  190. delete elem.resizeSensor;
  191. delete elem.resizedAttached;
  192. }
  193. });
  194. };
  195. return ResizeSensor;
  196. }));