共计 5164 个字符,预计需要花费 13 分钟才能阅读完成。
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 自定义指令及懒加载原理的综合实现,若有错误,望指出共同进步。
更多推荐
前端进阶小书(advanced_front_end)
前端每日一题(daily-question)
webpack4 搭建 Vue 应用(createVue)
Canvas 进阶(一)二维码的生成与扫码识别
Canvas 进阶(二)写一个生成带 logo 的二维码 npm 插件
Canvas 进阶(三)ts + canvas 重写”辨色“小游戏
Canvas 进阶(四)实现一个“刮刮乐”游戏