index.js 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. let raf = null;
  2. function requestAnimationFrame (callback) {
  3. if (!raf) {
  4. raf = (
  5. window.requestAnimationFrame ||
  6. window.webkitRequestAnimationFrame ||
  7. window.mozRequestAnimationFrame ||
  8. function (callback) {
  9. return setTimeout(callback, 16)
  10. }
  11. ).bind(window);
  12. }
  13. return raf(callback)
  14. }
  15. let caf = null;
  16. function cancelAnimationFrame (id) {
  17. if (!caf) {
  18. caf = (
  19. window.cancelAnimationFrame ||
  20. window.webkitCancelAnimationFrame ||
  21. window.mozCancelAnimationFrame ||
  22. function (id) {
  23. clearTimeout(id);
  24. }
  25. ).bind(window);
  26. }
  27. caf(id);
  28. }
  29. function createStyles (styleText) {
  30. var style = document.createElement('style');
  31. style.type = 'text/css';
  32. if (style.styleSheet) {
  33. style.styleSheet.cssText = styleText;
  34. } else {
  35. style.appendChild(document.createTextNode(styleText));
  36. }
  37. (document.querySelector('head') || document.body).appendChild(style);
  38. return style
  39. }
  40. function createElement (tagName, props = {}) {
  41. let elem = document.createElement(tagName);
  42. Object.keys(props).forEach(key => {
  43. elem[key] = props[key];
  44. });
  45. return elem
  46. }
  47. function getComputedStyle (elem, prop, pseudo) {
  48. // for older versions of Firefox, `getComputedStyle` required
  49. // the second argument and may return `null` for some elements
  50. // when `display: none`
  51. let computedStyle = window.getComputedStyle(elem, pseudo || null) || {
  52. display: 'none'
  53. };
  54. return computedStyle[prop]
  55. }
  56. function getRenderInfo (elem) {
  57. if (!document.documentElement.contains(elem)) {
  58. return {
  59. detached: true,
  60. rendered: false
  61. }
  62. }
  63. let current = elem;
  64. while (current !== document) {
  65. if (getComputedStyle(current, 'display') === 'none') {
  66. return {
  67. detached: false,
  68. rendered: false
  69. }
  70. }
  71. current = current.parentNode;
  72. }
  73. return {
  74. detached: false,
  75. rendered: true
  76. }
  77. }
  78. var css = ".resize-triggers{visibility:hidden;opacity:0}.resize-contract-trigger,.resize-contract-trigger:before,.resize-expand-trigger,.resize-triggers{content:\"\";position:absolute;top:0;left:0;height:100%;width:100%;overflow:hidden}.resize-contract-trigger,.resize-expand-trigger{background:#eee;overflow:auto}.resize-contract-trigger:before{width:200%;height:200%}";
  79. let total = 0;
  80. let style = null;
  81. function addListener (elem, callback) {
  82. if (!elem.__resize_mutation_handler__) {
  83. elem.__resize_mutation_handler__ = handleMutation.bind(elem);
  84. }
  85. let listeners = elem.__resize_listeners__;
  86. if (!listeners) {
  87. elem.__resize_listeners__ = [];
  88. if (window.ResizeObserver) {
  89. let { offsetWidth, offsetHeight } = elem;
  90. let ro = new ResizeObserver(() => {
  91. if (!elem.__resize_observer_triggered__) {
  92. elem.__resize_observer_triggered__ = true;
  93. if (elem.offsetWidth === offsetWidth && elem.offsetHeight === offsetHeight) {
  94. return
  95. }
  96. }
  97. runCallbacks(elem);
  98. });
  99. // initially display none won't trigger ResizeObserver callback
  100. let { detached, rendered } = getRenderInfo(elem);
  101. elem.__resize_observer_triggered__ = detached === false && rendered === false;
  102. elem.__resize_observer__ = ro;
  103. ro.observe(elem);
  104. } else if (elem.attachEvent && elem.addEventListener) {
  105. // targeting IE9/10
  106. elem.__resize_legacy_resize_handler__ = function handleLegacyResize () {
  107. runCallbacks(elem);
  108. };
  109. elem.attachEvent('onresize', elem.__resize_legacy_resize_handler__);
  110. document.addEventListener('DOMSubtreeModified', elem.__resize_mutation_handler__);
  111. } else {
  112. if (!total) {
  113. style = createStyles(css);
  114. }
  115. initTriggers(elem);
  116. elem.__resize_rendered__ = getRenderInfo(elem).rendered;
  117. if (window.MutationObserver) {
  118. let mo = new MutationObserver(elem.__resize_mutation_handler__);
  119. mo.observe(document, {
  120. attributes: true,
  121. childList: true,
  122. characterData: true,
  123. subtree: true
  124. });
  125. elem.__resize_mutation_observer__ = mo;
  126. }
  127. }
  128. }
  129. elem.__resize_listeners__.push(callback);
  130. total++;
  131. }
  132. function removeListener (elem, callback) {
  133. // targeting IE9/10
  134. if (elem.detachEvent && elem.removeEventListener) {
  135. elem.detachEvent('onresize', elem.__resize_legacy_resize_handler__);
  136. document.removeEventListener('DOMSubtreeModified', elem.__resize_mutation_handler__);
  137. return
  138. }
  139. let listeners = elem.__resize_listeners__;
  140. if (!listeners) {
  141. return
  142. }
  143. listeners.splice(listeners.indexOf(callback), 1);
  144. if (!listeners.length) {
  145. if (elem.__resize_observer__) {
  146. elem.__resize_observer__.unobserve(elem);
  147. elem.__resize_observer__.disconnect();
  148. elem.__resize_observer__ = null;
  149. } else {
  150. if (elem.__resize_mutation_observer__) {
  151. elem.__resize_mutation_observer__.disconnect();
  152. elem.__resize_mutation_observer__ = null;
  153. }
  154. elem.removeEventListener('scroll', handleScroll);
  155. elem.removeChild(elem.__resize_triggers__.triggers);
  156. elem.__resize_triggers__ = null;
  157. }
  158. elem.__resize_listeners__ = null;
  159. }
  160. if (!--total && style) {
  161. style.parentNode.removeChild(style);
  162. }
  163. }
  164. function getUpdatedSize (elem) {
  165. let { width, height } = elem.__resize_last__;
  166. let { offsetWidth, offsetHeight } = elem;
  167. if (offsetWidth !== width || offsetHeight !== height) {
  168. return {
  169. width: offsetWidth,
  170. height: offsetHeight
  171. }
  172. }
  173. return null
  174. }
  175. function handleMutation () {
  176. // `this` denotes the scrolling element
  177. let { rendered, detached } = getRenderInfo(this);
  178. if (rendered !== this.__resize_rendered__) {
  179. if (!detached && this.__resize_triggers__) {
  180. resetTriggers(this);
  181. this.addEventListener('scroll', handleScroll, true);
  182. }
  183. this.__resize_rendered__ = rendered;
  184. runCallbacks(this);
  185. }
  186. }
  187. function handleScroll () {
  188. // `this` denotes the scrolling element
  189. resetTriggers(this);
  190. if (this.__resize_raf__) {
  191. cancelAnimationFrame(this.__resize_raf__);
  192. }
  193. this.__resize_raf__ = requestAnimationFrame(() => {
  194. let updated = getUpdatedSize(this);
  195. if (updated) {
  196. this.__resize_last__ = updated;
  197. runCallbacks(this);
  198. }
  199. });
  200. }
  201. function runCallbacks (elem) {
  202. if (!elem || !elem.__resize_listeners__) {
  203. return
  204. }
  205. elem.__resize_listeners__.forEach(callback => {
  206. callback.call(elem);
  207. });
  208. }
  209. function initTriggers (elem) {
  210. let position = getComputedStyle(elem, 'position');
  211. if (!position || position === 'static') {
  212. elem.style.position = 'relative';
  213. }
  214. elem.__resize_old_position__ = position;
  215. elem.__resize_last__ = {};
  216. let triggers = createElement('div', {
  217. className: 'resize-triggers'
  218. });
  219. let expand = createElement('div', {
  220. className: 'resize-expand-trigger'
  221. });
  222. let expandChild = createElement('div');
  223. let contract = createElement('div', {
  224. className: 'resize-contract-trigger'
  225. });
  226. expand.appendChild(expandChild);
  227. triggers.appendChild(expand);
  228. triggers.appendChild(contract);
  229. elem.appendChild(triggers);
  230. elem.__resize_triggers__ = {
  231. triggers,
  232. expand,
  233. expandChild,
  234. contract
  235. };
  236. resetTriggers(elem);
  237. elem.addEventListener('scroll', handleScroll, true);
  238. elem.__resize_last__ = {
  239. width: elem.offsetWidth,
  240. height: elem.offsetHeight
  241. };
  242. }
  243. function resetTriggers (elem) {
  244. let { expand, expandChild, contract } = elem.__resize_triggers__;
  245. // batch read
  246. let { scrollWidth: csw, scrollHeight: csh } = contract;
  247. let { offsetWidth: eow, offsetHeight: eoh, scrollWidth: esw, scrollHeight: esh } = expand;
  248. // batch write
  249. contract.scrollLeft = csw;
  250. contract.scrollTop = csh;
  251. expandChild.style.width = eow + 1 + 'px';
  252. expandChild.style.height = eoh + 1 + 'px';
  253. expand.scrollLeft = esw;
  254. expand.scrollTop = esh;
  255. }
  256. export { addListener, removeListener };