diff --git a/README.md b/README.md index 406170f..b69d930 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,9 @@ new JuejinLazyload(Element || ElementList || selector, { // 是否自动监听 window 的 scroll 和 resize 事件 reactive: true, + // true - 图片头部加载完成后立即显示 | false - 全图加载完成后才显示 + eagerShowing: false, + // 初始化及 addOrUpdateElement 时调用 infoGetter: (Element) => ({ url: String, // 图片地址,用以设置 IMG 元素的 src 或其它元素的 background-image diff --git a/dist/juejin-lazyload.min.js b/dist/juejin-lazyload.min.js index 678d78e..3445c07 100644 --- a/dist/juejin-lazyload.min.js +++ b/dist/juejin-lazyload.min.js @@ -1,6 +1,6 @@ /** - * juejin-lazyload v0.1.4 + * juejin-lazyload v0.1.5 * (c) 2017 WEBuster * @license MIT */ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.JuejinLazyload=t()}(this,function(){"use strict";function e(e,t,n){var i=arguments.length>3&&void 0!==arguments[3]&&arguments[3];return e.addEventListener(t,n,i),function(){e.removeEventListener(t,n,i)}}!function(){function e(e){this.value=e}function t(t){function n(o,r){try{var a=t[o](r),s=a.value;s instanceof e?Promise.resolve(s.value).then(function(e){n("next",e)},function(e){n("throw",e)}):i(a.done?"return":"normal",a.value)}catch(e){i("throw",e)}}function i(e,t){switch(e){case"return":o.resolve({value:t,done:!0});break;case"throw":o.reject(t);break;default:o.resolve({value:t,done:!1})}(o=o.next)?n(o.key,o.arg):r=null}var o,r;this._invoke=function(e,t){return new Promise(function(i,a){var s={key:e,arg:t,resolve:i,reject:a,next:null};r?r=r.next=s:(o=r=s,n(e,t))})},"function"!=typeof t.return&&(this.return=void 0)}"function"==typeof Symbol&&Symbol.asyncIterator&&(t.prototype[Symbol.asyncIterator]=function(){return this}),t.prototype.next=function(e){return this._invoke("next",e)},t.prototype.throw=function(e){return this._invoke("throw",e)},t.prototype.return=function(e){return this._invoke("return",e)}}();var t=function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")},n=function(){function e(e,t){for(var n=0;n=t&&(n=Date.now(),setTimeout(function(){e.apply(null,o)},t))}}(function(){return t.updateState()},this.options.interval);this.removeScrollEventListener=e(window,"scroll",n),this.removeResizeEventListener=e(window,"resize",n)}}},{key:"removeEventListener",value:function(){this.removeScrollEventListener&&this.removeScrollEventListener(),this.removeResizeEventListener&&this.removeResizeEventListener()}},{key:"addOrUpdateElement",value:function(e){var t=this.getElementList(e),n=t.filter(function(e){return!e.__JUEJIN_LAZYLOAD});this.elementList=(this.elementList||[]).concat(n),t.forEach(this.initElement.bind(this)),this.updateState()}},{key:"removeElement",value:function(e){var t=this.getElementList(e);this.elementList=this.elementList.filter(function(e){return-1===t.indexOf(e)}),t.forEach(this.removeInfo.bind(this))}},{key:"clean",value:function(){this.elementList.forEach(this.removeInfo.bind(this)),this.elementList=[]}},{key:"getElementList",value:function(e){return e?"string"==typeof e?[].slice.call(document.querySelectorAll(e)):"number"==typeof e.length?[].slice.call(e):[e]:[]}},{key:"initElement",value:function(e){var t=this.options.infoGetter&&this.options.infoGetter(e),n=i({},t,{isImg:"IMG"===e.nodeName,loading:!1});n.hasPlaceholder=n.isImg&&n.width&&n.height,n.hasPlaceholder&&(e.src=function(e,t){return["data:image/svg+xml;utf8,",'','"].join("")}(n.width,n.height)),e.__JUEJIN_LAZYLOAD=n,this.updateElementClassByState("inited",e),this.invokeStateHook("inited",n.url,e)}},{key:"removeInfo",value:function(e){e.__JUEJIN_LAZYLOAD&&(e.__JUEJIN_LAZYLOAD=null)}},{key:"updateState",value:function(){var e=this;if(this.elementList.length){var t=this.getActiveArea();this.elementList.forEach(function(n){var i=n.__JUEJIN_LAZYLOAD.loading,o=n.getBoundingClientRect(),r=function(e,t){return!(t.bottome.bottom||t.righte.right)}(t,o);!i&&r&&e.loadIamge(n)})}}},{key:"getActiveArea",value:function(){var e=this.getVisibleArea(),t=this.options.threshold||0;return{top:e.top-t,left:e.left-t,right:e.right+t,bottom:e.bottom+t}}},{key:"getVisibleArea",value:function(){if(this.options.visibleAreaGetter)return this.options.visibleAreaGetter();var e=function(){var e=document.documentElement;return{width:Math.max(e.clientWidth,window.innerWidth||0),height:Math.max(e.clientHeight,window.innerHeight||0)}}();return{top:0,left:0,right:e.width,bottom:e.height}}},{key:"loadIamge",value:function(e){var t=this,n=e.__JUEJIN_LAZYLOAD,i=n.url,o=n.isImg;n.loading=!0,this.updateElementClassByState("loading",e),this.invokeStateHook("loading",i,e),function(e,t,n){if(e){var i=new Image;i.onload=function(){t&&t(e)},i.onerror=function(){n&&n(e)},i.src=e}}(i,function(){o?e.src=i:e.style.backgroundImage="url("+i+")",t.removeElement(e),t.updateElementClassByState("loaded",e),t.invokeStateHook("loaded",i,e)},function(){t.removeElement(e),t.updateElementClassByState("error",e),t.invokeStateHook("error",i,e)})}},{key:"updateElementClassByState",value:function(e,t){switch(e){case"inited":t.classList.add("inited"),t.classList.remove("loading"),t.classList.remove("loaded"),t.classList.remove("error");break;case"loading":t.classList.add("loading");break;case"loaded":t.classList.remove("loading"),t.classList.add("loaded");break;case"error":t.classList.remove("loading"),t.classList.add("error")}}},{key:"invokeStateHook",value:function(e,t,n){this.options.onStateChange&&this.options.onStateChange(e,t,n,this)}},{key:"destroy",value:function(){this.removeEventListener(),this.clean(),this.setOptions({})}}]),r}()}); +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.JuejinLazyload=t()}(this,function(){"use strict";function e(e,t,n){var i=arguments.length>3&&void 0!==arguments[3]&&arguments[3];return e.addEventListener(t,n,i),function(){e.removeEventListener(t,n,i)}}!function(){function e(e){this.value=e}function t(t){function n(o,r){try{var a=t[o](r),s=a.value;s instanceof e?Promise.resolve(s.value).then(function(e){n("next",e)},function(e){n("throw",e)}):i(a.done?"return":"normal",a.value)}catch(e){i("throw",e)}}function i(e,t){switch(e){case"return":o.resolve({value:t,done:!0});break;case"throw":o.reject(t);break;default:o.resolve({value:t,done:!1})}(o=o.next)?n(o.key,o.arg):r=null}var o,r;this._invoke=function(e,t){return new Promise(function(i,a){var s={key:e,arg:t,resolve:i,reject:a,next:null};r?r=r.next=s:(o=r=s,n(e,t))})},"function"!=typeof t.return&&(this.return=void 0)}"function"==typeof Symbol&&Symbol.asyncIterator&&(t.prototype[Symbol.asyncIterator]=function(){return this}),t.prototype.next=function(e){return this._invoke("next",e)},t.prototype.throw=function(e){return this._invoke("throw",e)},t.prototype.return=function(e){return this._invoke("return",e)}}();var t=function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")},n=function(){function e(e,t){for(var n=0;n=t&&(n=Date.now(),setTimeout(function(){e.apply(null,o)},t))}}(function(){return t.updateState()},this.options.interval);this.removeScrollEventListener=e(window,"scroll",n),this.removeResizeEventListener=e(window,"resize",n)}}},{key:"removeEventListener",value:function(){this.removeScrollEventListener&&this.removeScrollEventListener(),this.removeResizeEventListener&&this.removeResizeEventListener()}},{key:"addOrUpdateElement",value:function(e){var t=this.getElementList(e),n=t.filter(function(e){return!e[r]});this.elementList=(this.elementList||[]).concat(n),t.forEach(this.initElement.bind(this)),this.updateState()}},{key:"removeElement",value:function(e){var t=this.getElementList(e);this.elementList=this.elementList.filter(function(e){return-1===t.indexOf(e)}),t.forEach(this.removeInfo.bind(this))}},{key:"clean",value:function(){this.elementList.forEach(this.removeInfo.bind(this)),this.elementList=[]}},{key:"getElementList",value:function(e){return e?"string"==typeof e?[].slice.call(document.querySelectorAll(e)):"number"==typeof e.length?[].slice.call(e):[e]:[]}},{key:"initElement",value:function(e){this.attachInfo(e),this.setPlaceholder(e),this.updateElementClassByState("inited",e),this.invokeStateHook("inited",e)}},{key:"attachInfo",value:function(e){var t=this.options.infoGetter&&this.options.infoGetter(e),n=i({},t,{loading:!1});e[r]=n}},{key:"setPlaceholder",value:function(e){var t=e[r];"IMG"===e.nodeName&&t.width&&t.height&&(e.src=function(e,t){return["data:image/svg+xml;utf8,",'','"].join("")}(t.width,t.height))}},{key:"removeInfo",value:function(e){e[r]&&(e[r]=null)}},{key:"updateState",value:function(){var e=this;if(this.elementList.length){var t=this.getActiveArea();this.elementList.forEach(function(n){var i=n[r].loading,o=n.getBoundingClientRect(),a=function(e,t){return!(t.bottome.bottom||t.righte.right)}(t,o);!i&&a&&e.loadIamge(n)})}}},{key:"getActiveArea",value:function(){var e=this.getVisibleArea(),t=this.options.threshold||0;return{top:e.top-t,left:e.left-t,right:e.right+t,bottom:e.bottom+t}}},{key:"getVisibleArea",value:function(){if(this.options.visibleAreaGetter)return this.options.visibleAreaGetter();var e=function(){var e=document.documentElement;return{width:Math.max(e.clientWidth,window.innerWidth||0),height:Math.max(e.clientHeight,window.innerHeight||0)}}();return{top:0,left:0,right:e.width,bottom:e.height}}},{key:"loadIamge",value:function(e){var t=this,n=e[r],i=n.url;n.loading=!0,this.updateElementClassByState("loading",e),this.invokeStateHook("loading",e),function(e,t){var n=t.onStart,i=t.onLoaded,o=t.onError,r=new Image;r.onload=function(){i&&i(e,r)},r.onerror=function(){o&&o(e,r)},r.src=e,n&&n(e,r)}(i,{onStart:function(n,i){t.options.eagerShowing&&t.onMetaLoaded(i,function(){e.removeAttribute("src"),e.setAttribute("src",n)})},onLoaded:function(){t.setElementWithImageUrl(i,e),t.updateElementClassByState("loaded",e),t.invokeStateHook("loaded",e),t.removeElement(e)},onError:function(){t.updateElementClassByState("error",e),t.invokeStateHook("error",e),t.removeElement(e)}})}},{key:"updateElementClassByState",value:function(e,t){switch(e){case"inited":t.classList.add("inited"),t.classList.remove("loading"),t.classList.remove("loaded"),t.classList.remove("error");break;case"loading":t.classList.add("loading");break;case"loaded":t.classList.remove("loading"),t.classList.add("loaded");break;case"error":t.classList.remove("loading"),t.classList.add("error")}}},{key:"invokeStateHook",value:function(e,t){if(this.options.onStateChange){var n=t[r].url;this.options.onStateChange(e,n,t,this)}}},{key:"setElementWithImageUrl",value:function(e,t){"IMG"===t.nodeName?t.src=e:t.style.backgroundImage="url("+e+")"}},{key:"onMetaLoaded",value:function(t,n){var i=setInterval(function(){t.naturalWidth&&(o(),n())},300),o=function(){return clearInterval(i)};e(t,"load",o),e(t,"error",o)}},{key:"destroy",value:function(){this.removeEventListener(),this.clean(),this.setOptions({})}}]),a}()}); diff --git a/package.json b/package.json index 1094409..2640663 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "juejin-lazyload", - "version": "0.1.4", + "version": "0.1.5", "description": "juejin lazyload", "main": "dist/juejin-lazyload.min.js", "scripts": { diff --git a/src/juejin-lazyload.js b/src/juejin-lazyload.js index a8727d9..3b22c93 100644 --- a/src/juejin-lazyload.js +++ b/src/juejin-lazyload.js @@ -13,12 +13,14 @@ const DEFAULT_OPTIONS = { interval: 200, debounce: false, reactive: true, + eagerShowing: false, infoGetter: null, visibleAreaGetter: null, onStateChange: null } const INFO_PROP_NAME = '__JUEJIN_LAZYLOAD' +const META_CHECK_INTERVAL = 300 export default class JuejinLazyload { @@ -81,18 +83,26 @@ export default class JuejinLazyload { } initElement (element) { + this.attachInfo(element) + this.setPlaceholder(element) + this.updateElementClassByState('inited', element) + this.invokeStateHook('inited', element) + } + + attachInfo (element) { const imgInfo = this.options.infoGetter && this.options.infoGetter(element) const info = Object.assign({}, imgInfo, { - isImg: element.nodeName === 'IMG', loading: false }) - info.hasPlaceholder = info.isImg && info.width && info.height - if (info.hasPlaceholder) { + element[INFO_PROP_NAME] = info + } + + setPlaceholder (element) { + const info = element[INFO_PROP_NAME] + const isImg = element.nodeName === 'IMG' + if (isImg && info.width && info.height) { element.src = getPlaceholderDataUrl(info.width, info.height) } - element[INFO_PROP_NAME] = info - this.updateElementClassByState('inited', element) - this.invokeStateHook('inited', info.url, element) } removeInfo (element) { @@ -142,23 +152,30 @@ export default class JuejinLazyload { loadIamge (element) { const info = element[INFO_PROP_NAME] - const { url, isImg } = info + const { url } = info info.loading = true this.updateElementClassByState('loading', element) - this.invokeStateHook('loading', url, element) - loadIamge(url, () => { - if (isImg) { - element.src = url - } else { - element.style.backgroundImage = `url(${url})` + this.invokeStateHook('loading', element) + loadIamge(url, { + onStart: (url, image) => { + if (this.options.eagerShowing) { + this.onMetaLoaded(image, () => { + element.removeAttribute('src') // necessary + element.setAttribute('src', url) + }) + } + }, + onLoaded: () => { + this.setElementWithImageUrl(url, element) + this.updateElementClassByState('loaded', element) + this.invokeStateHook('loaded', element) + this.removeElement(element) + }, + onError: () => { + this.updateElementClassByState('error', element) + this.invokeStateHook('error', element) + this.removeElement(element) } - this.removeElement(element) - this.updateElementClassByState('loaded', element) - this.invokeStateHook('loaded', url, element) - }, () => { - this.removeElement(element) - this.updateElementClassByState('error', element) - this.invokeStateHook('error', url, element) }) } @@ -186,12 +203,33 @@ export default class JuejinLazyload { } } - invokeStateHook (state, url, element) { + invokeStateHook (state, element) { if (this.options.onStateChange) { + const { url } = element[INFO_PROP_NAME] this.options.onStateChange(state, url, element, this) } } + setElementWithImageUrl (url, element) { + if (element.nodeName === 'IMG') { + element.src = url + } else { + element.style.backgroundImage = `url(${url})` + } + } + + onMetaLoaded (image, callback) { + const token = setInterval(() => { + if (image.naturalWidth) { + stop() + callback() + } + }, META_CHECK_INTERVAL) + const stop = () => clearInterval(token) + on(image, 'load', stop) + on(image, 'error', stop) + } + destroy () { this.removeEventListener() this.clean() diff --git a/src/util.js b/src/util.js index 58b74e4..a482c50 100644 --- a/src/util.js +++ b/src/util.js @@ -13,12 +13,12 @@ export function getViewportSize () { } } -export function loadIamge (url, onLoaded, onError) { - if (!url) { return } +export function loadIamge (url, { onStart, onLoaded, onError }) { const image = new Image() - image.onload = function () { onLoaded && onLoaded(url) } - image.onerror = function () { onError && onError(url) } + image.onload = function () { onLoaded && onLoaded(url, image) } + image.onerror = function () { onError && onError(url, image) } image.src = url + onStart && onStart(url, image) } export function throttle (fn, interval) {