mui.lazyload.js 7.8 KB

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