1. 原生实现
使用原生JS实现图片懒加载,都是借助几种不同的API,来判断当前图片是否在可视区域内。
1.1 通过监听滚动的方式
-
图片顶部到文档顶部的距离 > 浏览器可视窗口高度 + 滚动条滚过的高度
,此时的图片就是不可见的 -
但如果用户直接滑到页面底部,那这个判断条件对所有的图片都为真
-
还要加上一条判断:
图片的高度 + 图片顶部到文档顶部的距离 > 滚动条滚过的高度
,以确保图片在可视区域内,而不只是被滑过 -
通过对应的API获取:
- 待加载图片的高度:
img.clientHeight
- 图片顶部到文档顶部的距离:
img.offsetTop
- 浏览器窗口滚动过的距离:
document.documentElement.scrollTop
或document.body.scrollTop
- 浏览器可视窗口高度:
document.documentElement.clientHeight
或window.innerHeight
// 获取到所有的 img 标签对应的元素,存到 imgs 数组中 const imgs = document.getElementsByTagName('img'); function lazyLoad(imgs) { console.log('懒加载被触发了!') // 浏览器可视窗口的高度; const windowHeight = window.innerHeight; // 可视窗口滚动过的距离; const scrollHeight = document.documentElement.scrollTop; for (let i = 0; i < imgs.length; i++) { // 根据我们先前讲解过的是否可视逻辑进行判断; // !imgs[i].src 是当该图片已加载好之后,无需重复加载 if (windowHeight + scrollHeight > imgs[i].offsetTop && !imgs[i].src && imgs[i].offsetTop + imgs[i].offsetHeight > scrollHeight) { // 使用data-xx的自定义属性可以通过dom元素的dataset.xx取得; imgs[i].src = imgs[i].dataset.src; } } }; // 进入页面时执行一次加载; lazyLoad(imgs); // 监听滚动事件,进行图片懒加载; window.onscroll = () => lazyLoad(imgs);
- 待加载图片的高度:
-
而这种方式实现懒加载需要进行防抖节流处理,更推荐采用节流的方式
- 防抖需要用户完全停止滚动一定时间才能进行加载
- 如果用户在查找指定图片而不松开控制滚动条的鼠标,很难保证完全不触发滚动事件
- 这样就会导致无法触发懒加载函数,图片展示不出来
-
而懒加载不能使用时间戳实现的节流,这会导致当快速滑到一个位置立刻停下时,无法进行图片的加载
- 计时器需要等待单位时间计时器结束后才能执行一次函数
- 时间戳只要判断当前时间间隔大于单位时间了就立即触发
- 时间戳版本的节流函数,执行的是单位时间内函数的第一次触发;而计时器版本的节流函数,执行的是单位时间内函数的后一次触发
function throttle(fn, delay, ...args) { // 使用剩余参数语法,接收任意数量的参数 let timer = null; // 定义一个定时器 return () => { let context = this; // 保存当前上下文 if (!timer) { // 如果没有定时器 timer = setTimeout(() => { // 设置一个延迟执行的定时器 fn.apply(context, args); // 执行函数,并传入参数 timer = null; // 清空定时器 }, delay); } } } // 监听滚动事件,加载后面的图片; window.onscroll = throttle(lazyLoad, 500, imgs);
1.2 getBoundingClientRect
-
该api返回值是一个
DOMRect
对象,拥有left
,top
,right
,bottom
,x
,y
,width
, 和height
属性 -
当页面发生滚动的时候,
top
与left
属性值都会随之改变 -
如果一个元素在视窗内,会满足四个条件:
- top >= 0
- left >= 0
- bottom <= 视窗高度
- right <= 视窗宽度
// Vue 组件中,为图片元素绑定监听滚动事件的方法 <template> <div> <img v-for="(image, index) in images" :src="image" ref="lazyImages" @load="onImageLoad(index)" /> </div> </template> //在组件的 methods 部分实现 onImageLoad 方法来检测图片是否进入可视区域 <script> export default { methods: { onImageLoad(index) { const lazyImages = this.$refs.lazyImages; const img = lazyImages[index]; const rect = img.getBoundingClientRect(); const viewWidth = window.innerWidth||document.documentElement.clientWidth; const viewHeight = window.innerHeight|| document.documentElement.clientHeight; if (rect.top <= viewWidth && rect.bottom <= viewHeight rect.top >= 0 && rect.left >= 0 && !img.src) { img.src = img.dataset.src; } }, }, }; </script>
1.3 Intersection Observer
-
IntersectionObserver
是专门为检测某个元素是否出现在可视窗口 -
以
IntersectionObserver
构造函数新建一个对象,接收两个参数callback
和options
-
当监听目标发生滚动变化时触发的回调函数,该回调函数接受一个参数
entries
, 它是IntersectionObserverEntry
的实例 -
简单来讲这个
entries
就存储着我们用observe()
添加给observer
实例的那些需要被监听的元素与其根元素容器在某一特定过渡时刻的交叉状态(默认为顶级文档的视窗) -
而每一个
entry
有一个target
属性,指向这个被监听的元素document.addEventListener("DOMContentLoaded", () => { if ("IntersectionObserver" in window) { const imgs = document.getElementsByTagName("img"); const imageObserve = new IntersectionObserver((entries) => { entries.forEach((entry) => { // console.log("滚动触发监听函数了!") // 通过该属性判断元素是否出现在视口内 if (entry.isIntersecting) { // entry.target能够取得那个dom元素 const img = entry.target; img.src = img.dataset.src; // 图片加载完成后解除监听 imageObserve.unobserve(img); } }); }); [...imgs].forEach((img) => { // 将所有的图片加入监听 imageObserve.observe(img); }); } else { alert("您的浏览器尚不支持IntersectionObserver,请尝试更新或者使用其他主流浏览器。"); } });
-
用户快速下滑到页面底部、直接从底部开始都不会触发上面图片的加载
2. 自定义指令
-
将懒加载逻辑封装成可重用的指令,在需要懒加载的图片元素上使用该指令
const lazyLoad = { // mounted 在绑定元素的父组件 // 及他自己的所有子节点都挂载完成后调用 mounted(el, binding) { // 如果有需要可以先设置src为 loading 图 // el.setAttribute('src', 'loading 图的路径'); const options = { rootMargin: '0px', threshold: 0.1, }; const observer = new IntersectionObserver((entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { // binding 是一个对象,binding.value 是传递给指令的值 el.setAttribute('src', binding.value); observer.unobserve(el); } }); }, options); observer.observe(el); }, }; export default lazyLoad;
3. 移动端的lazyload属性
- 原生的懒加载属性,只需在
img
标签上添加loading="lazy"
属性,浏览器会自动处理图片的懒加载 - 根据用户滚动操作至其元素附近执行加载,一定程度起到节流的作用
4. vue-lazyload
-
vue
框架中可以安装的懒加载的插件,通过v-lazy指令的形式写到对应的元素实现懒加载<img v-lazy="img.thumbnail_pic_s"> // 用v-lazy替换src
-
下载好依赖,并进行全局注册即可
-
src
中的文件会被webpack
编译,assets
文件夹中的图片地址,会在编译过程中重命名 -
vue-lazyload
是在main.js
文件中引入,不会被webpack
进行编译,因此vue-lazyload
无法获得正确的图片地址,直接写相对地址就无法获取到图片正确地址 -
PS:如果设置了翻页功能,且每一页都是请求的数据进行渲染,会发现其他的数据都变了,唯独图片还是原来的图片,解决办法只要加个key就行
<ul> <li v-for="img in list"> <img v-lazy="img.src" :key="img.src" > </li> </ul>
-
这个库只支持在
Vue2
,在Vue3
中main.js
注册会报错