arrive.js

arrive.js provides events to watch for DOM elements creation and removal. It makes use of Mutation Observers internally.

当前为 2016-08-02 提交的版本,查看 最新版本

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.gf.qytechs.cn/scripts/21927/139586/arrivejs.js

  1. /*
  2. * arrive.js
  3. * v2.3.1
  4. * https://github.com/uzairfarooq/arrive
  5. * MIT licensed
  6. *
  7. * Copyright (c) 2014-2016 Uzair Farooq
  8. */
  9.  
  10. var Arrive = (function(window, $, undefined) {
  11.  
  12. "use strict";
  13.  
  14. if(!window.MutationObserver || typeof HTMLElement === 'undefined'){
  15. return; //for unsupported browsers
  16. }
  17.  
  18. var arriveUniqueId = 0;
  19.  
  20. var utils = (function() {
  21. var matches = HTMLElement.prototype.matches || HTMLElement.prototype.webkitMatchesSelector || HTMLElement.prototype.mozMatchesSelector
  22. || HTMLElement.prototype.msMatchesSelector;
  23.  
  24. return {
  25. matchesSelector: function(elem, selector) {
  26. return elem instanceof HTMLElement && matches.call(elem, selector);
  27. },
  28. // to enable function overloading - By John Resig (MIT Licensed)
  29. addMethod: function (object, name, fn) {
  30. var old = object[ name ];
  31. object[ name ] = function(){
  32. if ( fn.length == arguments.length )
  33. return fn.apply( this, arguments );
  34. else if ( typeof old == 'function' )
  35. return old.apply( this, arguments );
  36. };
  37. },
  38. callCallbacks: function(callbacksToBeCalled) {
  39. for (var i = 0, cb; cb = callbacksToBeCalled[i]; i++) {
  40. cb.callback.call(cb.elem);
  41. }
  42. },
  43. // traverse through all descendants of a node to check if event should be fired for any descendant
  44. checkChildNodesRecursively: function(nodes, registrationData, matchFunc, callbacksToBeCalled) {
  45. // check each new node if it matches the selector
  46. for (var i=0, node; node = nodes[i]; i++) {
  47. if (matchFunc(node, registrationData, callbacksToBeCalled)) {
  48. callbacksToBeCalled.push({ callback: registrationData.callback, elem: node });
  49. }
  50.  
  51. if (node.childNodes.length > 0) {
  52. utils.checkChildNodesRecursively(node.childNodes, registrationData, matchFunc, callbacksToBeCalled);
  53. }
  54. }
  55. },
  56. mergeArrays: function(firstArr, secondArr){
  57. // Overwrites default options with user-defined options.
  58. var options = {},
  59. attrName;
  60. for (attrName in firstArr) {
  61. options[attrName] = firstArr[attrName];
  62. }
  63. for (attrName in secondArr) {
  64. options[attrName] = secondArr[attrName];
  65. }
  66. return options;
  67. },
  68. toElementsArray: function (elements) {
  69. // check if object is an array (or array like object)
  70. // Note: window object has .length property but it's not array of elements so don't consider it an array
  71. if (typeof elements !== "undefined" && (typeof elements.length !== "number" || elements === window)) {
  72. elements = [elements];
  73. }
  74. return elements;
  75. }
  76. };
  77. })();
  78.  
  79.  
  80. // Class to maintain state of all registered events of a single type
  81. var EventsBucket = (function() {
  82. var EventsBucket = function() {
  83. // holds all the events
  84.  
  85. this._eventsBucket = [];
  86. // function to be called while adding an event, the function should do the event initialization/registration
  87. this._beforeAdding = null;
  88. // function to be called while removing an event, the function should do the event destruction
  89. this._beforeRemoving = null;
  90. };
  91.  
  92. EventsBucket.prototype.addEvent = function(target, selector, options, callback) {
  93. var newEvent = {
  94. target: target,
  95. selector: selector,
  96. options: options,
  97. callback: callback,
  98. firedElems: []
  99. };
  100.  
  101. if (this._beforeAdding) {
  102. this._beforeAdding(newEvent);
  103. }
  104. this._eventsBucket.push(newEvent);
  105. return newEvent;
  106. };
  107.  
  108. EventsBucket.prototype.removeEvent = function(compareFunction) {
  109. for (var i=this._eventsBucket.length - 1, registeredEvent; registeredEvent = this._eventsBucket[i]; i--) {
  110. if (compareFunction(registeredEvent)) {
  111. if (this._beforeRemoving) {
  112. this._beforeRemoving(registeredEvent);
  113. }
  114. this._eventsBucket.splice(i, 1);
  115. }
  116. }
  117. };
  118.  
  119. EventsBucket.prototype.beforeAdding = function(beforeAdding) {
  120. this._beforeAdding = beforeAdding;
  121. };
  122.  
  123. EventsBucket.prototype.beforeRemoving = function(beforeRemoving) {
  124. this._beforeRemoving = beforeRemoving;
  125. };
  126.  
  127. return EventsBucket;
  128. })();
  129.  
  130.  
  131. /**
  132. * @constructor
  133. * General class for binding/unbinding arrive and leave events
  134. */
  135. var MutationEvents = function(getObserverConfig, onMutation) {
  136. var eventsBucket = new EventsBucket(),
  137. me = this;
  138.  
  139. var defaultOptions = {
  140. fireOnAttributesModification: false
  141. };
  142.  
  143. // actual event registration before adding it to bucket
  144. eventsBucket.beforeAdding(function(registrationData) {
  145. var
  146. target = registrationData.target,
  147. selector = registrationData.selector,
  148. callback = registrationData.callback,
  149. observer;
  150.  
  151. // mutation observer does not work on window or document
  152. if (target === window.document || target === window)
  153. target = document.getElementsByTagName("html")[0];
  154.  
  155. // Create an observer instance
  156. observer = new MutationObserver(function(e) {
  157. onMutation.call(this, e, registrationData);
  158. });
  159.  
  160. var config = getObserverConfig(registrationData.options);
  161. observer.observe(target, config);
  162.  
  163. registrationData.observer = observer;
  164. registrationData.me = me;
  165. });
  166.  
  167. // cleanup/unregister before removing an event
  168. eventsBucket.beforeRemoving(function (eventData) {
  169. eventData.observer.disconnect();
  170. });
  171.  
  172. this.bindEvent = function(selector, options, callback) {
  173. options = utils.mergeArrays(defaultOptions, options);
  174.  
  175. var elements = utils.toElementsArray(this);
  176.  
  177. for (var i = 0; i < elements.length; i++) {
  178. eventsBucket.addEvent(elements[i], selector, options, callback);
  179. }
  180. };
  181.  
  182. this.unbindEvent = function() {
  183. var elements = utils.toElementsArray(this);
  184. eventsBucket.removeEvent(function(eventObj) {
  185. for (var i = 0; i < elements.length; i++) {
  186. if (this === undefined || eventObj.target === elements[i]) {
  187. return true;
  188. }
  189. }
  190. return false;
  191. });
  192. };
  193.  
  194. this.unbindEventWithSelectorOrCallback = function(selector) {
  195. var elements = utils.toElementsArray(this),
  196. callback = selector,
  197. compareFunction;
  198.  
  199. if (typeof selector === "function") {
  200. compareFunction = function(eventObj) {
  201. for (var i = 0; i < elements.length; i++) {
  202. if ((this === undefined || eventObj.target === elements[i]) && eventObj.callback === callback) {
  203. return true;
  204. }
  205. }
  206. return false;
  207. };
  208. }
  209. else {
  210. compareFunction = function(eventObj) {
  211. for (var i = 0; i < elements.length; i++) {
  212. if ((this === undefined || eventObj.target === elements[i]) && eventObj.selector === selector) {
  213. return true;
  214. }
  215. }
  216. return false;
  217. };
  218. }
  219. eventsBucket.removeEvent(compareFunction);
  220. };
  221.  
  222. this.unbindEventWithSelectorAndCallback = function(selector, callback) {
  223. var elements = utils.toElementsArray(this);
  224. eventsBucket.removeEvent(function(eventObj) {
  225. for (var i = 0; i < elements.length; i++) {
  226. if ((this === undefined || eventObj.target === elements[i]) && eventObj.selector === selector && eventObj.callback === callback) {
  227. return true;
  228. }
  229. }
  230. return false;
  231. });
  232. };
  233.  
  234. return this;
  235. };
  236.  
  237.  
  238. /**
  239. * @constructor
  240. * Processes 'arrive' events
  241. */
  242. var ArriveEvents = function() {
  243. var mutationEvents,
  244. me = this;
  245.  
  246. // Default options for 'arrive' event
  247. var arriveDefaultOptions = {
  248. fireOnAttributesModification: false,
  249. onceOnly: false,
  250. existing: false
  251. };
  252.  
  253. function getArriveObserverConfig(options) {
  254. var config = {
  255. attributes: false,
  256. childList: true,
  257. subtree: true
  258. };
  259.  
  260. if (options.fireOnAttributesModification) {
  261. config.attributes = true;
  262. }
  263.  
  264. return config;
  265. }
  266.  
  267. function onArriveMutation(mutations, registrationData) {
  268. mutations.forEach(function( mutation ) {
  269. var newNodes = mutation.addedNodes,
  270. targetNode = mutation.target,
  271. callbacksToBeCalled = [];
  272.  
  273. // If new nodes are added
  274. if( newNodes !== null && newNodes.length > 0 ) {
  275. utils.checkChildNodesRecursively(newNodes, registrationData, nodeMatchFunc, callbacksToBeCalled);
  276. }
  277. else if (mutation.type === "attributes") {
  278. if (nodeMatchFunc(targetNode, registrationData, callbacksToBeCalled)) {
  279. callbacksToBeCalled.push({ callback: registrationData.callback, elem: node });
  280. }
  281. }
  282.  
  283. utils.callCallbacks(callbacksToBeCalled);
  284. });
  285. }
  286.  
  287. function nodeMatchFunc(node, registrationData, callbacksToBeCalled) {
  288. // check a single node to see if it matches the selector
  289. if (utils.matchesSelector(node, registrationData.selector)) {
  290. if(node._id === undefined) {
  291. node._id = arriveUniqueId++;
  292. }
  293. // make sure the arrive event is not already fired for the element
  294. if (registrationData.firedElems.indexOf(node._id) == -1) {
  295.  
  296. if (registrationData.options.onceOnly) {
  297. if (registrationData.firedElems.length === 0) {
  298. // On first callback, unbind event.
  299. registrationData.me.unbindEventWithSelectorAndCallback.call(
  300. registrationData.target, registrationData.selector, registrationData.callback);
  301. } else {
  302. // Ignore multiple mutations which may have been queued before the event was unbound.
  303. return;
  304. }
  305. }
  306.  
  307. registrationData.firedElems.push(node._id);
  308. callbacksToBeCalled.push({ callback: registrationData.callback, elem: node });
  309. }
  310. }
  311. }
  312.  
  313. arriveEvents = new MutationEvents(getArriveObserverConfig, onArriveMutation);
  314.  
  315. var mutationBindEvent = arriveEvents.bindEvent;
  316.  
  317. // override bindEvent function
  318. arriveEvents.bindEvent = function(selector, options, callback) {
  319.  
  320. if (typeof callback === "undefined") {
  321. callback = options;
  322. options = arriveDefaultOptions;
  323. } else {
  324. options = utils.mergeArrays(arriveDefaultOptions, options);
  325. }
  326.  
  327. var elements = utils.toElementsArray(this);
  328.  
  329. if (options.existing) {
  330. var existing = [];
  331.  
  332. for (var i = 0; i < elements.length; i++) {
  333. var nodes = elements[i].querySelectorAll(selector);
  334. for (var j = 0; j < nodes.length; j++) {
  335. existing.push({ callback: callback, elem: nodes[j] });
  336. }
  337. }
  338.  
  339. // no need to bind event if the callback has to be fired only once and we have already found the element
  340. if (options.onceOnly && existing.length) {
  341. return callback.call(existing[0].elem);
  342. }
  343.  
  344. setTimeout(utils.callCallbacks, 1, existing);
  345. }
  346.  
  347. mutationBindEvent.call(this, selector, options, callback);
  348. };
  349.  
  350. return arriveEvents;
  351. };
  352.  
  353.  
  354. /**
  355. * @constructor
  356. * Processes 'leave' events
  357. */
  358. var LeaveEvents = function() {
  359. var mutationEvents,
  360. me = this;
  361.  
  362. // Default options for 'leave' event
  363. var leaveDefaultOptions = {};
  364.  
  365. function getLeaveObserverConfig(options) {
  366. var config = {
  367. childList: true,
  368. subtree: true
  369. };
  370.  
  371. return config;
  372. }
  373.  
  374. function onLeaveMutation(mutations, registrationData) {
  375. mutations.forEach(function( mutation ) {
  376. var removedNodes = mutation.removedNodes,
  377. targetNode = mutation.target,
  378. callbacksToBeCalled = [];
  379.  
  380. if( removedNodes !== null && removedNodes.length > 0 ) {
  381. utils.checkChildNodesRecursively(removedNodes, registrationData, nodeMatchFunc, callbacksToBeCalled);
  382. }
  383.  
  384. utils.callCallbacks(callbacksToBeCalled);
  385. });
  386. }
  387.  
  388. function nodeMatchFunc(node, registrationData) {
  389. return utils.matchesSelector(node, registrationData.selector);
  390. }
  391.  
  392. leaveEvents = new MutationEvents(getLeaveObserverConfig, onLeaveMutation);
  393.  
  394. var mutationBindEvent = leaveEvents.bindEvent;
  395.  
  396. // override bindEvent function
  397. leaveEvents.bindEvent = function(selector, options, callback) {
  398.  
  399. if (typeof callback === "undefined") {
  400. callback = options;
  401. options = leaveDefaultOptions;
  402. } else {
  403. options = utils.mergeArrays(leaveDefaultOptions, options);
  404. }
  405.  
  406. mutationBindEvent.call(this, selector, options, callback);
  407. };
  408.  
  409. return leaveEvents;
  410. };
  411.  
  412.  
  413. var arriveEvents = new ArriveEvents(),
  414. leaveEvents = new LeaveEvents();
  415.  
  416. function exposeUnbindApi(eventObj, exposeTo, funcName) {
  417. // expose unbind function with function overriding
  418. utils.addMethod(exposeTo, funcName, eventObj.unbindEvent);
  419. utils.addMethod(exposeTo, funcName, eventObj.unbindEventWithSelectorOrCallback);
  420. utils.addMethod(exposeTo, funcName, eventObj.unbindEventWithSelectorAndCallback);
  421. }
  422.  
  423. /*** expose APIs ***/
  424. function exposeApi(exposeTo) {
  425. exposeTo.arrive = arriveEvents.bindEvent;
  426. exposeUnbindApi(arriveEvents, exposeTo, "unbindArrive");
  427.  
  428. exposeTo.leave = leaveEvents.bindEvent;
  429. exposeUnbindApi(leaveEvents, exposeTo, "unbindLeave");
  430. }
  431.  
  432. if ($) {
  433. exposeApi($.fn);
  434. }
  435. exposeApi(HTMLElement.prototype);
  436. exposeApi(NodeList.prototype);
  437. exposeApi(HTMLCollection.prototype);
  438. exposeApi(HTMLDocument.prototype);
  439. exposeApi(Window.prototype);
  440.  
  441. var Arrive = {};
  442. // expose functions to unbind all arrive/leave events
  443. exposeUnbindApi(arriveEvents, Arrive, "unbindAllArrive");
  444. exposeUnbindApi(leaveEvents, Arrive, "unbindAllLeave");
  445.  
  446. return Arrive;
  447.  
  448. })(window, typeof jQuery === 'undefined' ? null : jQuery, undefined);

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址