insQ.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. var insertionQ = (function () {
  2. "use strict";
  3. var sequence = 100,
  4. isAnimationSupported = false,
  5. animation_string = 'animationName',
  6. keyframeprefix = '',
  7. domPrefixes = 'Webkit Moz O ms Khtml'.split(' '),
  8. pfx = '',
  9. elm = document.createElement('div'),
  10. options = {
  11. strictlyNew: true,
  12. timeout: 20
  13. };
  14. if (elm.style.animationName) {
  15. isAnimationSupported = true;
  16. }
  17. if (isAnimationSupported === false) {
  18. for (var i = 0; i < domPrefixes.length; i++) {
  19. if (elm.style[domPrefixes[i] + 'AnimationName'] !== undefined) {
  20. pfx = domPrefixes[i];
  21. animation_string = pfx + 'AnimationName';
  22. keyframeprefix = '-' + pfx.toLowerCase() + '-';
  23. isAnimationSupported = true;
  24. break;
  25. }
  26. }
  27. }
  28. function listen(selector, callback) {
  29. var styleAnimation, animationName = 'insQ_' + (sequence++);
  30. var eventHandler = function (event) {
  31. if (event.animationName === animationName || event[animation_string] === animationName) {
  32. if (!isTagged(event.target)) {
  33. callback(event.target);
  34. }
  35. }
  36. };
  37. styleAnimation = document.createElement('style');
  38. styleAnimation.innerHTML = '@' + keyframeprefix + 'keyframes ' + animationName + ' { from { outline: 1px solid transparent } to { outline: 0px solid transparent } }' +
  39. "\n" + selector + ' { animation-duration: 0.001s; animation-name: ' + animationName + '; ' +
  40. keyframeprefix + 'animation-duration: 0.001s; ' + keyframeprefix + 'animation-name: ' + animationName + '; ' +
  41. ' } ';
  42. document.head.appendChild(styleAnimation);
  43. var bindAnimationLater = setTimeout(function () {
  44. document.addEventListener('animationstart', eventHandler, false);
  45. document.addEventListener('MSAnimationStart', eventHandler, false);
  46. document.addEventListener('webkitAnimationStart', eventHandler, false);
  47. //event support is not consistent with DOM prefixes
  48. }, options.timeout); //starts listening later to skip elements found on startup. this might need tweaking
  49. return {
  50. destroy: function () {
  51. clearTimeout(bindAnimationLater);
  52. if (styleAnimation) {
  53. document.head.removeChild(styleAnimation);
  54. styleAnimation = null;
  55. }
  56. document.removeEventListener('animationstart', eventHandler);
  57. document.removeEventListener('MSAnimationStart', eventHandler);
  58. document.removeEventListener('webkitAnimationStart', eventHandler);
  59. }
  60. };
  61. }
  62. function tag(el) {
  63. el.QinsQ = true; //bug in V8 causes memory leaks when weird characters are used as field names. I don't want to risk leaking DOM trees so the key is not '-+-' anymore
  64. }
  65. function isTagged(el) {
  66. return (options.strictlyNew && (el.QinsQ === true));
  67. }
  68. function topmostUntaggedParent(el) {
  69. if (isTagged(el.parentNode)) {
  70. return el;
  71. } else {
  72. return topmostUntaggedParent(el.parentNode);
  73. }
  74. }
  75. function tagAll(e) {
  76. tag(e);
  77. e = e.firstChild;
  78. for (; e; e = e.nextSibling) {
  79. if (e !== undefined && e.nodeType === 1) {
  80. tagAll(e);
  81. }
  82. }
  83. }
  84. //aggregates multiple insertion events into a common parent
  85. function catchInsertions(selector, callback) {
  86. var insertions = [];
  87. //throttle summary
  88. var sumUp = (function () {
  89. var to;
  90. return function () {
  91. clearTimeout(to);
  92. to = setTimeout(function () {
  93. insertions.forEach(tagAll);
  94. callback(insertions);
  95. insertions = [];
  96. }, 10);
  97. };
  98. })();
  99. return listen(selector, function (el) {
  100. if (isTagged(el)) {
  101. return;
  102. }
  103. tag(el);
  104. var myparent = topmostUntaggedParent(el);
  105. if (insertions.indexOf(myparent) < 0) {
  106. insertions.push(myparent);
  107. }
  108. sumUp();
  109. });
  110. }
  111. //insQ function
  112. var exports = function (selector) {
  113. if (isAnimationSupported && selector.match(/[^{}]/)) {
  114. if (options.strictlyNew) {
  115. tagAll(document.body); //prevents from catching things on show
  116. }
  117. return {
  118. every: function (callback) {
  119. return listen(selector, callback);
  120. },
  121. summary: function (callback) {
  122. return catchInsertions(selector, callback);
  123. }
  124. };
  125. } else {
  126. return false;
  127. }
  128. };
  129. //allows overriding defaults
  130. exports.config = function (opt) {
  131. for (var o in opt) {
  132. if (opt.hasOwnProperty(o)) {
  133. options[o] = opt[o];
  134. }
  135. }
  136. };
  137. return exports;
  138. })();
  139. if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
  140. module.exports = insertionQ;
  141. }