mui.lazyload.js 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. (function($, window, document) {
  2. var mid = 0;
  3. $.Lazyload = $.Class.extend({
  4. init: function(element, options) {
  5. var self = this;
  6. this.container = this.element = element;
  7. this.options = $.extend({
  8. selector: '',
  9. diff: false,
  10. force: false,
  11. autoDestroy: true,
  12. duration: 100
  13. }, options);
  14. this._key = 0;
  15. this._containerIsNotDocument = this.container.nodeType !== 9;
  16. this._callbacks = {};
  17. this._init();
  18. },
  19. _init: function() {
  20. this._initLoadFn();
  21. this.addElements();
  22. this._loadFn();
  23. $.ready(function() {
  24. this._loadFn();
  25. }.bind(this));
  26. this.resume();
  27. },
  28. _initLoadFn: function() {
  29. var self = this;
  30. self._loadFn = this._buffer(function() { // 加载延迟项
  31. if (self.options.autoDestroy && self._counter == 0 && $.isEmptyObject(self._callbacks)) {
  32. self.destroy();
  33. }
  34. self._loadItems();
  35. }, self.options.duration, self);
  36. },
  37. /**
  38. *根据加载函数实现加载器
  39. *@param {Function} load 加载函数
  40. *@returns {Function} 加载器
  41. */
  42. _createLoader: function(load) {
  43. var value, loading, handles = [],
  44. h;
  45. return function(handle) {
  46. if (!loading) {
  47. loading = true;
  48. load(function(v) {
  49. value = v;
  50. while (h = handles.shift()) {
  51. try {
  52. h && h.apply(null, [value]);
  53. } catch (e) {
  54. setTimeout(function() {
  55. throw e;
  56. }, 0)
  57. }
  58. }
  59. })
  60. }
  61. if (value) {
  62. handle && handle.apply(null, [value]);
  63. return value;
  64. }
  65. handle && handles.push(handle);
  66. return value;
  67. }
  68. },
  69. _buffer: function(fn, ms, context) {
  70. var timer;
  71. var lastStart = 0;
  72. var lastEnd = 0;
  73. var ms = ms || 150;
  74. function run() {
  75. if (timer) {
  76. timer.cancel();
  77. timer = 0;
  78. }
  79. lastStart = $.now();
  80. fn.apply(context || this, arguments);
  81. lastEnd = $.now();
  82. }
  83. return $.extend(function() {
  84. if (
  85. (!lastStart) || // 从未运行过
  86. (lastEnd >= lastStart && $.now() - lastEnd > ms) || // 上次运行成功后已经超过ms毫秒
  87. (lastEnd < lastStart && $.now() - lastStart > ms * 8) // 上次运行或未完成,后8*ms毫秒
  88. ) {
  89. run();
  90. } else {
  91. if (timer) {
  92. timer.cancel();
  93. }
  94. timer = $.later(run, ms, null, arguments);
  95. }
  96. }, {
  97. stop: function() {
  98. if (timer) {
  99. timer.cancel();
  100. timer = 0;
  101. }
  102. }
  103. });
  104. },
  105. _getBoundingRect: function(c) {
  106. var vh, vw, left, top;
  107. if (c !== undefined) {
  108. vh = c.offsetHeight;
  109. vw = c.offsetWidth;
  110. var offset = $.offset(c);
  111. left = offset.left;
  112. top = offset.top;
  113. } else {
  114. vh = window.innerHeight;
  115. vw = window.innerWidth;
  116. left = 0;
  117. top = window.pageYOffset;
  118. }
  119. var diff = this.options.diff;
  120. var diffX = diff === false ? vw : diff;
  121. var diffX0 = 0;
  122. var diffX1 = diffX;
  123. var diffY = diff === false ? vh : diff;
  124. var diffY0 = 0;
  125. var diffY1 = diffY;
  126. var right = left + vw;
  127. var bottom = top + vh;
  128. left -= diffX0;
  129. right += diffX1;
  130. top -= diffY0;
  131. bottom += diffY1;
  132. return {
  133. left: left,
  134. top: top,
  135. right: right,
  136. bottom: bottom
  137. };
  138. },
  139. _cacheWidth: function(el) {
  140. if (el._mui_lazy_width) {
  141. return el._mui_lazy_width;
  142. }
  143. return el._mui_lazy_width = el.offsetWidth;
  144. },
  145. _cacheHeight: function(el) {
  146. if (el._mui_lazy_height) {
  147. return el._mui_lazy_height;
  148. }
  149. return el._mui_lazy_height = el.offsetHeight;
  150. },
  151. _isCross: function(r1, r2) {
  152. var r = {};
  153. r.top = Math.max(r1.top, r2.top);
  154. r.bottom = Math.min(r1.bottom, r2.bottom);
  155. r.left = Math.max(r1.left, r2.left);
  156. r.right = Math.min(r1.right, r2.right);
  157. return r.bottom >= r.top && r.right >= r.left;
  158. },
  159. _elementInViewport: function(elem, windowRegion, containerRegion) {
  160. // display none or inside display none
  161. if (!elem.offsetWidth) {
  162. return false;
  163. }
  164. var elemOffset = $.offset(elem);
  165. var inContainer = true;
  166. var inWin;
  167. var left = elemOffset.left;
  168. var top = elemOffset.top;
  169. var elemRegion = {
  170. left: left,
  171. top: top,
  172. right: left + this._cacheWidth(elem),
  173. bottom: top + this._cacheHeight(elem)
  174. };
  175. inWin = this._isCross(windowRegion, elemRegion);
  176. if (inWin && containerRegion) {
  177. inContainer = this._isCross(containerRegion, elemRegion);
  178. }
  179. // 确保在容器内出现
  180. // 并且在视窗内也出现
  181. return inContainer && inWin;
  182. },
  183. _loadItems: function() {
  184. var self = this;
  185. // container is display none
  186. if (self._containerIsNotDocument && !self.container.offsetWidth) {
  187. return;
  188. }
  189. self._windowRegion = self._getBoundingRect();
  190. if (self._containerIsNotDocument) {
  191. self._containerRegion = self._getBoundingRect(this.container);
  192. }
  193. $.each(self._callbacks, function(key, callback) {
  194. callback && self._loadItem(key, callback);
  195. });
  196. },
  197. _loadItem: function(key, callback) {
  198. var self = this;
  199. callback = callback || self._callbacks[key];
  200. if (!callback) {
  201. return true;
  202. }
  203. var el = callback.el;
  204. var remove = false;
  205. var fn = callback.fn;
  206. if (self.options.force || self._elementInViewport(el, self._windowRegion, self._containerRegion)) {
  207. try {
  208. remove = fn.call(self, el, key);
  209. } catch (e) {
  210. setTimeout(function() {
  211. throw e;
  212. }, 0);
  213. }
  214. }
  215. if (remove !== false) {
  216. delete self._callbacks[key];
  217. }
  218. return remove;
  219. },
  220. addCallback: function(el, fn) {
  221. var self = this;
  222. var callbacks = self._callbacks;
  223. var callback = {
  224. el: el,
  225. fn: fn || $.noop
  226. };
  227. var key = ++this._key;
  228. callbacks[key] = callback;
  229. // add 立即检测,防止首屏元素问题
  230. if (self._windowRegion) {
  231. self._loadItem(key, callback);
  232. } else {
  233. self.refresh();
  234. }
  235. },
  236. addElements: function(elements) {
  237. var self = this;
  238. self._counter = self._counter || 0;
  239. var lazyloads = [];
  240. if (!elements && self.options.selector) {
  241. lazyloads = self.container.querySelectorAll(self.options.selector);
  242. } else {
  243. $.each(elements, function(index, el) {
  244. lazyloads = lazyloads.concat($.qsa(self.options.selector, el));
  245. });
  246. }
  247. $.each(lazyloads, function(index, el) {
  248. if (!el.getAttribute('data-lazyload-id')) {
  249. if (self.addElement(el)) {
  250. el.setAttribute('data-lazyload-id', mid++);
  251. self.addCallback(el, self.handle);
  252. }
  253. }
  254. });
  255. },
  256. addElement: function(el) {
  257. return true;
  258. },
  259. handle: function() {
  260. //throw new Error('需子类实现');
  261. },
  262. refresh: function(check) {
  263. if (check) { //检查新的lazyload
  264. this.addElements();
  265. }
  266. this._loadFn();
  267. },
  268. pause: function() {
  269. var load = this._loadFn;
  270. if (this._destroyed) {
  271. return;
  272. }
  273. window.removeEventListener('scroll', load);
  274. window.removeEventListener('touchmove', load);
  275. window.removeEventListener('resize', load);
  276. if (this._containerIsNotDocument) {
  277. this.container.removeEventListener('scrollend', load);
  278. this.container.removeEventListener('scroll', load);
  279. this.container.removeEventListener('touchmove', load);
  280. }
  281. },
  282. resume: function() {
  283. var load = this._loadFn;
  284. if (this._destroyed) {
  285. return;
  286. }
  287. window.addEventListener('scroll', load, false);
  288. window.addEventListener('touchmove', load, false);
  289. window.addEventListener('resize', load, false);
  290. if (this._containerIsNotDocument) {
  291. this.container.addEventListener('scrollend', load, false);
  292. this.container.addEventListener('scroll', load, false);
  293. this.container.addEventListener('touchmove', load, false);
  294. }
  295. },
  296. destroy: function() {
  297. var self = this;
  298. self.pause();
  299. self._callbacks = {};
  300. $.trigger(this.container, 'destory', self);
  301. self._destroyed = 1;
  302. }
  303. });
  304. })(mui, window, document);