1. 項目實現介紹
vue
項目搭建參考《Webpack4 搭建 Vue 項目》
文檔使用 vuepress
, 官方文檔 https://vuepress.vuejs.org
發佈文檔 github pages
+ gh-page
項目地址 https://github.com/zxpsuper/vui-vue
文檔地址 https://zxpsuper.github.io/vui-vue
處於自我摸索階段,期待留下您的寶貴意見!
2. v-lazy 的基本實現
圖片懶加載的基本原理:
- 先用佔位圖代替目標圖片的
src
屬性值 - 當圖片的
offsetTop < innerHeight + scrollTop
時,即圖片出現在窗口內部,此時修改src
值爲data-src
的值 - 當然,這一切需要不斷地監聽滾動事件
先實現一個懶加載函數
var img = document.getElementsByTagName('img');
function lazyload() {
//監聽頁面滾動事件
var seeHeight = window.innerHeight; //可見區域高度
var scrollTop =
document.documentElement.scrollTop || document.body.scrollTop; //滾動條距離頂部高度
for (var i = 0; i < img.length; i++) {
if (img[i].getAttribute('data-image-show')) continue; // 如果已經加載完成,則不需走下面流程
if (img[i].offsetTop < seeHeight + scrollTop) {
console.log(img[i].offsetTop, seeHeight, scrollTop);
if (img[i].getAttribute('src') == Vue.$vuiLazyLoad.img) {
img[i].src = img[i].getAttribute('data-src');
img[i].setAttribute('data-image-show', 'true'); // 給個標識,表示已經加載完成
}
}
}
}
滾動監聽
滾動監聽,不斷滾動便會不斷觸發滾動監聽的函數,影響性能,因此在此需要加入一個防抖函數
// 防抖函數
function throttle(event, time) {
let timer = null;
return function(...args) {
if (!timer) {
timer = setTimeout(() => {
timer = null;
event.apply(this, args);
}, time);
}
};
}
添加監聽和去除監聽
window.removeEventListener('scroll', throttle(lazyload, 800));
window.addEventListener('scroll', throttle(lazyload, 800));
添加指令
這裏用到了自定義指令中的三個鉤子函數 bind,inserted,unbind
, 我們要讓指令中佔位圖可修改,因此寫成函數形式
const lazyload = function(Vue) {
var img = document.getElementsByTagName('img');
function lazyload() {
//監聽頁面滾動事件
var seeHeight = window.innerHeight; //可見區域高度
var scrollTop =
document.documentElement.scrollTop || document.body.scrollTop; //滾動條距離頂部高度
for (var i = 0; i < img.length; i++) {
if (img[i].getAttribute('data-image-show')) continue; // 如果已經加載完成,則不需走下面流程
if (img[i].offsetTop < seeHeight + scrollTop) {
console.log(img[i].offsetTop, seeHeight, scrollTop);
if (img[i].getAttribute('src') == Vue.$vuiLazyLoad.img) {
img[i].src = img[i].getAttribute('data-src');
img[i].setAttribute('data-image-show', 'true'); // 給個標識,表示已經加載完成
}
}
}
}
// vui中的默認配置,用戶可通過修改 Vue.$vuiLazyLoad.img 進行修改佔位圖
Vue.$vuiLazyLoad = {
img:
'https://github.com/zxpsuper/Demo/blob/master/images/avatar.jpg?raw=true',
imgLength: 0, // 懶加載的圖片數量,當數量爲 0 的時候移除滾動監聽
};
lazyload(); //頁面載入完畢加載可是區域內的圖片
window.removeEventListener('scroll', throttle(lazyload, 800));
window.addEventListener('scroll', throttle(lazyload, 800));
return {
name: 'lazy',
// 綁定
bind(el, binding) {
el.setAttribute('src', Vue.$vuiLazyLoad.img);
el.setAttribute('data-src', binding.value);
Vue.$vuiLazyLoad.imgLength++;
},
// 插入
inserted(el) {
// 暫時空
},
// 解綁
unbind() {
Vue.$vuiLazyLoad.imgLength--; // 每次解綁,自減
if (!Vue.$vuiLazyLoad.imgLength)
window.removeEventListener('scroll', throttle(lazyload, 800));
},
};
}
使用新特性 IntersectionObserver
IntersectionObserver接口(從屬於Intersection Observer API)爲開發者提供了一種可以異步監聽目標元素與其祖先或視窗(viewport)交叉狀態的手段。
觀察元素是否與視窗交叉,若是則修改 scr
爲 data-src
值,並解除觀察狀態,當然這一切的前提是你在圖片創建的時候觀察圖片本身,因此在圖片插入時的鉤子函數內
inserted(el) {
if (IntersectionObserver) lazyImageObserver.observe(el);
},
具體使用方法:
let lazyImageObserver
if (IntersectionObserver) {
lazyImageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach((entry, index) => {
let lazyImage = entry.target;
// 如果元素可見
if (entry.intersectionRatio > 0) {
if (lazyImage.getAttribute('src') == Vue.$vuiLazyLoad.img) {
lazyImage.src = lazyImage.getAtstribute('data-src');
}
lazyImageObserver.unobserve(lazyImage); // 解除觀察
}
});
});
}
當然我們優先使用 IntersectionObserver
, 若不支持則使用傳統方法
註冊指令
import Vue from 'vue'
Vue.directive('lazy', lazyLoad(Vue));
3. 完整代碼
const lazyLoad = function(Vue) {
var img = document.getElementsByTagName('img');
function lazyload() {
//監聽頁面滾動事件
var seeHeight = window.innerHeight; //可見區域高度
var scrollTop =
document.documentElement.scrollTop || document.body.scrollTop; //滾動條距離頂部高度
for (var i = 0; i < img.length; i++) {
if (img[i].getAttribute('data-image-show')) continue;
console.log(i);
if (img[i].offsetTop < seeHeight + scrollTop) {
console.log(img[i].offsetTop, seeHeight, scrollTop);
if (img[i].getAttribute('src') == Vue.$vuiLazyLoad.img) {
img[i].src = img[i].getAttribute('data-src');
img[i].setAttribute('data-image-show', 'true');
}
}
}
}
Vue.$vuiLazyLoad = {
img:
'https://github.com/zxpsuper/Demo/blob/master/images/avatar.jpg?raw=true',
imgLength: 0,
};
var lazyImageObserver;
if (IntersectionObserver) {
lazyImageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach((entry, index) => {
let lazyImage = entry.target;
// 如果元素可見
if (entry.intersectionRatio > 0) {
if (lazyImage.getAttribute('src') == Vue.$vuiLazyLoad.img) {
lazyImage.src = lazyImage.getAttribute('data-src');
}
lazyImageObserver.unobserve(lazyImage);
}
});
});
} else {
lazyload(); //頁面載入完畢加載可是區域內的圖片
window.removeEventListener('scroll', throttle(lazyload, 800));
window.addEventListener('scroll', throttle(lazyload, 800));
}
return {
name: 'lazy',
bind(el, binding) {
el.setAttribute('src', Vue.$vuiLazyLoad.img);
el.setAttribute('data-src', binding.value);
Vue.$vuiLazyLoad.imgLength++;
},
inserted(el) {
if (IntersectionObserver) lazyImageObserver.observe(el);
},
unbind() {
Vue.$vuiLazyLoad.imgLength--;
if (!Vue.$vuiLazyLoad.imgLength)
window.removeEventListener('scroll', throttle(lazyload, 800));
},
};
};
export default lazyLoad;
function throttle(event, time) {
let timer = null;
return function(...args) {
if (!timer) {
timer = setTimeout(() => {
timer = null;
event.apply(this, args);
}, time);
}
};
}
總結
本文是對vue自定義指令及懶加載原理的綜合實現,若有錯誤,望指出共同進步。
更多推薦
Canvas 進階(二)寫一個生成帶logo的二維碼npm插件
Canvas 進階(三)ts + canvas 重寫”辨色“小遊戲