摘要:景科同學(xué)的想法很簡(jiǎn)單,因?yàn)楸救四壳斑€是一個(gè)前端小白,只有通過(guò)不斷的寫,不斷的學(xué),在與的相愛(ài)相殺中才能更快速的進(jìn)步。本項(xiàng)目是景科同學(xué)自寫自測(cè),雖然比較簡(jiǎn)單,但是不保證沒(méi)有隱藏的。所以如果看官同學(xué)發(fā)現(xiàn)還望留言指正,景科同學(xué)在此以示感謝。
本文相對(duì)比較初級(jí),為了節(jié)約時(shí)間,請(qǐng)小神及其以上級(jí)別的同學(xué)直接忽略。
有同學(xué)可能會(huì)問(wèn):那么多第三方庫(kù),為什么要自己動(dòng)手寫呢。景科同學(xué)的想法很簡(jiǎn)單,因?yàn)楸救四壳斑€是一個(gè)前端小白,只有通過(guò)不斷的寫,不斷的學(xué),在與bug的相愛(ài)相殺中才能更快速的進(jìn)步。在證明可行可用之后不僅可以減少項(xiàng)目的第三方依賴,即便出現(xiàn)bug,解決自己代碼的bug也要比解決別人代碼的bug要好過(guò)一些。話不多說(shuō),且入正題。
一. 基礎(chǔ)結(jié)構(gòu)手摸手第一步。在第一步,先把基礎(chǔ)結(jié)構(gòu)構(gòu)思搭建一下,以方便后續(xù)擼碼。圖片懶加載本身就不是什么復(fù)雜的實(shí)現(xiàn),所以基本結(jié)構(gòu)也比較簡(jiǎn)單,無(wú)非就是初始化一下參數(shù),容一下錯(cuò),綁定幾個(gè)函數(shù),實(shí)現(xiàn)一下功能而已。具體代碼且往下看:
(function (global, factory) { if (typeof exports === "object" && typeof module !== "undefined") { module.exports = factory(global) } else if (typeof define === "function" && define.amd) { define(factory) } else { global.Lazy = factory(global) } })(this, function () { // 全局變量,所有方法在此對(duì)象上擴(kuò)展 var Lazy = {}; // 計(jì)時(shí)器 var timer = null; // 節(jié)流延遲時(shí)間 var delay = 150; // 是否開啟節(jié)流 var debounce = true; // 是否開啟圖片懶加載圖片的重載。解釋一下:就是圖片離開懶加載區(qū)域要把圖片狀態(tài)復(fù)原,再次進(jìn)入懶加載區(qū)域要在視覺(jué)上呈現(xiàn)懶加載效果 // 先呵呵一下這個(gè)功能,正常的我肯定想不到這么個(gè)抽風(fēng)的需求,誰(shuí)讓我曾經(jīng)碰到過(guò)一個(gè)抽風(fēng)的產(chǎn)品經(jīng)理呢,實(shí)現(xiàn)不難,這里也順便實(shí)現(xiàn)一下 var unload = false; // 回掉函數(shù) var callback = function () {}; // 元素相對(duì)于視窗的位置集合 var boxRect = {}; /** * 判斷目標(biāo)元素是否可見(jiàn) #1 * @param {HTMLElement} element * @returns {boolean} */ var isHidden = function (element) {}; /** * 判斷目標(biāo)元素是否進(jìn)入懶加載區(qū)域 #2 * @param {HTMLElement} element * @returns {boolean} */ var canLoadImg = function (element) {}; /** * 節(jié)流函數(shù) #3 */ var onDebounceRender = function () {}; /** * 始化方法,外部直接調(diào)用,配置參數(shù)在此接收 #4 * @param {Object} options * @param {String} options.root - 圖片滾動(dòng)區(qū)域根元素選擇器 * @param {Number} options.offset - 懶加載閾值,當(dāng)沒(méi)有【上下左右】4個(gè)值時(shí)將以此為準(zhǔn) * @param {Number} options.offsetTop - 懶加載閾值【上】 * @param {Number} options.offsetRight - 懶加載閾值【右】 * @param {Number} options.offsetBottom - 懶加載閾值【下】 * @param {Number} options.offsetLeft - 懶加載閾值【左】 * @param {Boolean} options.debounce - 是否開啟函數(shù)節(jié)流 * @param {Number} options.delay - 函數(shù)節(jié)流時(shí)間閾值 * @param {Boolean} options.unload - 圖片重載 * @param {Function} options.callback - 懶加載操作完成時(shí)的回掉函數(shù) */ Lazy.init = function(options) {}; /** * 懶加載實(shí)現(xiàn) #5 * @param {HTMLElement} element */ Lazy.render = function(element) {}; /** * 不滿足懶加載條件時(shí)銷毀 #6 */ Lazy.destroy = function() {}; // 返回 return Lazy; });
由于項(xiàng)目本身并不復(fù)雜,所以基礎(chǔ)的結(jié)構(gòu)也比較簡(jiǎn)單,剩下的幾步我們只需要手摸手去做填空題(#1、#2、#3、#4、#5、#6)就好了。so easy,let`s go!
二. 功能函數(shù)實(shí)現(xiàn)手摸手第二步。在第二步我們先把#1、#2、#3三個(gè)功能函數(shù)實(shí)現(xiàn)一下。
首先是#1函數(shù)isHidden的實(shí)現(xiàn)。
/** * 判斷目標(biāo)元素是否可見(jiàn) * https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLElement/offsetParent * @param {HTMLElement} element * @returns {boolean} */ var isHidden = function (element) { return element.offsetParent === null; };
接下來(lái)是#2函數(shù)canLoadImg的實(shí)現(xiàn)。這兒用到了Element.getBoundingClientRect()方法返回元素的大小及其相對(duì)于視口的位置。關(guān)于Element.getBoundingClientRect()的信息請(qǐng)點(diǎn)擊傳送陣了解更多。
/** * 判斷目標(biāo)元素是否進(jìn)入懶加載區(qū)域 * 此函數(shù)依賴isHidden函數(shù)和boxRect全局變量 * @param {HTMLElement} element * @returns {boolean} */ var canLoadImg = function (element) { if (isHidden(element)) return false; var eleRect = element.getBoundingClientRect(); return (eleRect.top <= boxRect.b && eleRect.right >= boxRect.l && eleRect.bottom >= boxRect.t && eleRect.left <= boxRect.r); };
最后是#3函數(shù)onDebounceRender的實(shí)現(xiàn)。由于這里對(duì)節(jié)流函數(shù)沒(méi)什么特殊需求,所以實(shí)現(xiàn)的比較簡(jiǎn)單,如果看官同學(xué)需要完整的debounce函數(shù),請(qǐng)點(diǎn)擊lodash/debounce.js了解更多。
/** * 節(jié)流函數(shù) * 此函數(shù)依賴Lazy.render、debounce、timer、delay */ var onDebounceRender = function () { if (!debounce) { Lazy.render(); } else { clearTimeout(timer); timer = setTimeout(function () { Lazy.render(); timer = null; }, delay); } };三. 初始化函數(shù)
手摸手第三步。我們來(lái)實(shí)現(xiàn)一下Lazy.init初始化函數(shù)。這個(gè)函數(shù)的作用就是接收參數(shù)、綁定事件處理函數(shù),所以更簡(jiǎn)單。
/** * 始化方法,外部直接調(diào)用,配置參數(shù)在此接收 * @param {Object} options * @param {String} options.root - 圖片滾動(dòng)區(qū)域根元素選擇器 * @param {Number} options.offset - 懶加載閾值,當(dāng)沒(méi)有上下左右有4個(gè)值時(shí)將以此為準(zhǔn) * @param {Number} options.offsetTop - 懶加載閾值【上】 * @param {Number} options.offsetRight - 懶加載閾值【右】 * @param {Number} options.offsetBottom - 懶加載閾值【下】 * @param {Number} options.offsetLeft - 懶加載閾值【左】 * @param {Boolean} options.debounce - 是否開啟函數(shù)節(jié)流 * @param {Number} options.delay - 函數(shù)節(jié)流時(shí)間閾值 * @param {Boolean} options.unload - 圖片重載 * @param {Function} options.callback - 懶加載操作完成時(shí)的回掉函數(shù) */ Lazy.init = function (options) { options = options || {}; global = document.querySelector(options.root) || global; debounce = options.debounce !== false; delay = options.delay || delay; unload = options.unload || unload; callback = options.callback || callback; // 懶加載區(qū)域,寫的雖然有點(diǎn)長(zhǎng)但是不難理解 boxRect = { t: 0 - (options.offsetTop || options.offset || 0), r: (global.innerWidth || document.documentElement.clientWidth) + (options.offsetRight || options.offset || 0), b: (global.innerHeight || document.documentElement.clientHeight) + (options.offsetBottom || options.offset || 0), l: 0 - (options.offsetLeft || options.offset || 0) }; // 這里提前調(diào)用一次,因?yàn)槿绻鹍ebounce為true,load之后會(huì)有最低250ms的延遲 Lazy.render(); // 綁定事件 if (global.addEventListener) { global.addEventListener("load", onDebounceRender, false); global.addEventListener("scroll", onDebounceRender, false); } else { global.attachEvent("onload", onDebounceRender); global.attachEvent("onscroll", onDebounceRender); } };四. 核心方法實(shí)現(xiàn)
手摸手第四步。在第四步我們來(lái)完成Lazy.render函數(shù)的實(shí)現(xiàn)。這個(gè)函數(shù)也是本項(xiàng)目的核心方法,所有的懶加載實(shí)現(xiàn)都是在此處完成。思路不復(fù)雜,所以實(shí)現(xiàn)起來(lái)也比較簡(jiǎn)單。
/** * 懶加載實(shí)現(xiàn) * @param {HTMLElement} element */ Lazy.render = function (element) { // dom結(jié)構(gòu)約定:img標(biāo)簽要有[data-lazy]自定義屬性,需要設(shè)置背景的標(biāo)簽需要有[data-lazy-background]自定義屬性 var nodes = (element || document).querySelectorAll("[data-lazy], [data-lazy-background]"); var len = nodes.length; for (var i = 0; i < len; i++) { var node = nodes[i]; // 目標(biāo)元素在懶加載區(qū)域和不在懶加載區(qū)域 if (canLoadImg(node)) { // 懶加載圖片需要重載,懶加載之前先將占位圖片存儲(chǔ) if (unload && node.src && !node.getAttribute("data-lazy-placeholder")) { node.setAttribute("data-lazy-placeholder", node.src); } // 圖片的懶加載 var src = node.getAttribute("data-lazy"); if (src !== null && node.src !== src) { node.src = src; } // 背景的懶加載 var bgUrl = node.getAttribute("data-lazy-background"); if (bgUrl !== null && node.style.backgroundImage !== bgUrl) { node.style.backgroundImage = "url(" + bgUrl + ")"; } // 如果圖片不需要重載,懶加載完成移除[data-lazy]自定義屬性 if (!unload) { node.removeAttribute("data-lazy"); } // 懶加載完成移除[data-lazy-background]自定義屬性 node.removeAttribute("data-lazy-background"); // 懶加載完成觸發(fā)回掉 callback(node, "load"); } else { // 當(dāng)圖片不在懶加載區(qū)域時(shí)做重載處理 var placeholder = node.getAttribute("data-lazy-placeholder"); if (unload && placeholder !== null) { node.src = placeholder; // 移除[data-lazy-placeholder]自定義屬性 node.removeAttribute("data-lazy-placeholder"); // 重載完成觸發(fā)回掉 callback(node, "unload"); } } } // 如果沒(méi)有懶加載的元素,銷毀Lazy.init添加的事件 if (len === 0) { Lazy.destroy(); } };五. 解綁方法實(shí)現(xiàn)
手摸手第五步。這一步更簡(jiǎn)單,話不多說(shuō)直接看代碼。
/** * 不滿足懶加載條件時(shí)移除綁定的事件,重置定時(shí)器引用 */ Lazy.destroy = function () { if (document.removeEventListener) { global.removeEventListener("scroll", onDebounceRender); } else { global.detachEvent("onscroll", onDebounceRender); } clearTimeout(timer); };六. 結(jié)語(yǔ)
由于景科同學(xué)剛開始寫博文,語(yǔ)文老師走的又早(是真的早)?,文筆難免摳腳,不足之處還望各位看官同學(xué)多多包含。本項(xiàng)目是景科同學(xué)自寫自測(cè),雖然比較簡(jiǎn)單,但是不保證沒(méi)有隱藏的bug。所以如果看官同學(xué)發(fā)現(xiàn)還望留言指正,景科同學(xué)在此以示感謝。
本文完整代碼請(qǐng)點(diǎn)這里。
如果大神同學(xué)看到此處,更希望你能留下批評(píng)指正的意見(jiàn),這樣景科同學(xué)才能更快的進(jìn)步,下次如果你們不小心點(diǎn)開景科同學(xué)寫的文章才不會(huì)白花花的浪費(fèi)寶貴的時(shí)間,誰(shuí)說(shuō)不是呢?!
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/94064.html
摘要:靈活性和針對(duì)性。所以我覺(jué)得大部分組件還是自己封裝來(lái)的更為方便和靈活一些。動(dòng)手開干接下來(lái)我們一起手摸手教改造包裝一個(gè)插件,只要幾分鐘就可以封裝一個(gè)專屬于你的。 項(xiàng)目地址:vue-countTo配套完整后臺(tái)demo地址:vue-element-admin系類文章一:手摸手,帶你用vue擼后臺(tái) 系列一(基礎(chǔ)篇)系類文章二:手摸手,帶你用vue擼后臺(tái) 系列二(登錄權(quán)限篇)系類文章三:手摸手,帶...
摘要:社區(qū)的認(rèn)可目前已經(jīng)是相關(guān)最多的開源項(xiàng)目了,體現(xiàn)出了社區(qū)對(duì)其的認(rèn)可。監(jiān)聽(tīng)事件手動(dòng)維護(hù)列表這樣我們就簡(jiǎn)單的完成了拖拽排序。 完整項(xiàng)目地址:vue-element-admin 系類文章一:手摸手,帶你用vue擼后臺(tái) 系列一(基礎(chǔ)篇)系類文章二:手摸手,帶你用vue擼后臺(tái) 系列二(登錄權(quán)限篇)系類文章三:手摸手,帶你用vue擼后臺(tái) 系列三(實(shí)戰(zhàn)篇)系類文章四:手摸手,帶你用vue擼后臺(tái) 系列...
摘要:社區(qū)的認(rèn)可目前已經(jīng)是相關(guān)最多的開源項(xiàng)目了,體現(xiàn)出了社區(qū)對(duì)其的認(rèn)可。監(jiān)聽(tīng)事件手動(dòng)維護(hù)列表這樣我們就簡(jiǎn)單的完成了拖拽排序。 完整項(xiàng)目地址:vue-element-admin 系類文章一:手摸手,帶你用vue擼后臺(tái) 系列一(基礎(chǔ)篇)系類文章二:手摸手,帶你用vue擼后臺(tái) 系列二(登錄權(quán)限篇)系類文章三:手摸手,帶你用vue擼后臺(tái) 系列三(實(shí)戰(zhàn)篇)系類文章四:手摸手,帶你用vue擼后臺(tái) 系列...
摘要:詳細(xì)具體的使用可以見(jiàn)文章手摸手,帶你優(yōu)雅的使用。為了加速線上鏡像構(gòu)建的速度,我們利用源進(jìn)行加速并且將一些常見(jiàn)的依賴打入了基礎(chǔ)鏡像,避免每次都需要重新下載。 完整項(xiàng)目地址:vue-element-admin系類文章二:手摸手,帶你用vue擼后臺(tái) 系列二(登錄權(quán)限篇)系類文章三:手摸手,帶你用vue擼后臺(tái) 系列三(實(shí)戰(zhàn)篇)系類文章四:手摸手,帶你用vue擼后臺(tái) 系列四(vueAdmin 一...
摘要:同時(shí)增加了單元測(cè)試,使用了,增加了可視化配置權(quán)限,增加了自定義布局等等,優(yōu)化了原先的權(quán)限方案,支持不刷新頁(yè)面更新路由等等功能。雖然它的初衷是為了單元測(cè)試的,但正好滿足了我們的需求。它會(huì)重寫瀏覽器的對(duì)象,從而才能攔截所有請(qǐng)求,代理到本地。 前言 vue-element-admin 從 2017.04.17提交第一個(gè) commit 以來(lái),維護(hù)至今已經(jīng)有兩年多的時(shí)間了了,發(fā)布了四十多個(gè)版本,...
閱讀 1827·2023-04-26 02:11
閱讀 3147·2023-04-25 16:18
閱讀 3850·2021-09-06 15:00
閱讀 2796·2019-08-30 15:55
閱讀 2093·2019-08-30 13:20
閱讀 2186·2019-08-26 18:36
閱讀 3277·2019-08-26 11:40
閱讀 2726·2019-08-26 10:11