mui.lazyload.js 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  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. //addElements时,自动初始化一次
  249. if(self._containerIsNotDocument) {
  250. self._containerRegion = self._getBoundingRect(self.container);
  251. }
  252. $.each(lazyloads, function(index, el) {
  253. if(!el.getAttribute('data-lazyload-id')) {
  254. if(self.addElement(el)) {
  255. el.setAttribute('data-lazyload-id', mid++);
  256. self.addCallback(el, self.handle);
  257. }
  258. }
  259. });
  260. },
  261. addElement: function(el) {
  262. return true;
  263. },
  264. handle: function() {
  265. //throw new Error('需子类实现');
  266. },
  267. refresh: function(check) {
  268. if(check) { //检查新的lazyload
  269. this.addElements();
  270. }
  271. this._loadFn();
  272. },
  273. pause: function() {
  274. var load = this._loadFn;
  275. if(this._destroyed) {
  276. return;
  277. }
  278. window.removeEventListener('scroll', load);
  279. window.removeEventListener($.EVENT_MOVE, load);
  280. window.removeEventListener('resize', load);
  281. if(this._containerIsNotDocument) {
  282. this.container.removeEventListener('scrollend', load);
  283. this.container.removeEventListener('scroll', load);
  284. this.container.removeEventListener($.EVENT_MOVE, load);
  285. }
  286. },
  287. resume: function() {
  288. var load = this._loadFn;
  289. if(this._destroyed) {
  290. return;
  291. }
  292. window.addEventListener('scroll', load, false);
  293. window.addEventListener($.EVENT_MOVE, load, false);
  294. window.addEventListener('resize', load, false);
  295. if(this._containerIsNotDocument) {
  296. this.container.addEventListener('scrollend', load, false);
  297. this.container.addEventListener('scroll', load, false);
  298. this.container.addEventListener($.EVENT_MOVE, load, false);
  299. }
  300. },
  301. destroy: function() {
  302. var self = this;
  303. self.pause();
  304. self._callbacks = {};
  305. $.trigger(this.container, 'destroy', self);
  306. self._destroyed = 1;
  307. }
  308. });
  309. })(mui, window, document);