五种方式实现图片懒加载


近期,公司这边的项目遇到好多需要懒加载的操作,所以趁此机会学习并搜罗了许多实现懒加载的方式,并总结于下面~

其实实现懒加载的核心点在于 判断一个元素是否在可视区域

可视区域即我们浏览网页的设备肉眼可见的区域,如下图

image.png

在日常开发中,我们经常需要判断目标元素是否在视窗之内或者和视窗的距离小于一个值(例如 100 px),从而实现一些常用的功能,例如:

  • 图片的懒加载
  • 列表的无限滚动
  • 计算广告元素的曝光情况
  • 可点击链接的预加载

方式一 img的loading属性设为“lazy”

HTML元素延迟加载属性 —— loading属性值lazy允许浏览器选择性加载IMG元素,根据用户滚动操作至其元素附近执行加载,一定程度起到节流的作用。

业务场景

当前页面图片元素过多,避免不必要的流量浪费。

使用方法

<img src="deathghost.jpg" loading="lazy" alt="新码笔记" />                                                                  
其中元素属性loading就是今天所要了解的属性。
复制代码

loading的属性值

描述
eager 默认,图像立即加载。
lazy 图像延迟加载,只有鼠标滚动到该图片所在位置才会显示。

image.png


方式二 offsetTop - scrollTop < = innerHeight

offsetTop,元素的上外边框至包含元素的上内边框之间的像素距离,其他offset属性如下图所示:

image.png

下面再来了解下clientWidthclientHeight

  • clientWidth:元素内容区宽度加上左右内边距宽度,即clientWidth = content + padding
  • clientHeight:元素内容区高度加上上下内边距高度,即clientHeight = content + padding

这里可以看到client元素都不包括外边距

最后,关于scroll系列的属性如下:

  • scrollWidthscrollHeight 主要用于确定元素内容的实际大小
  • scrollLeftscrollTop 属性既可以确定元素当前滚动的状态,也可以设置元素的滚动位置
    • 垂直滚动 scrollTop > 0
    • 水平滚动 scrollLeft > 0
  • 将元素的 scrollLeftscrollTop 设置为 0,可以重置元素的滚动位置

注意

  • 上述属性都是只读的,每次访问都要重新开始

下面再看看如何实现判断:

公式如下

el.offsetTop - document.documentElement.scrollTop <= viewPortHeight
复制代码

代码实现

function isInViewPortOfOne (el) {
// viewPortHeight 兼容所有浏览器写法
const viewPortHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight
const offsetTop = el.offsetTop
const scrollTop = document.documentElement.scrollTop
const top = offsetTop - scrollTop
return top <= viewPortHeight
}
复制代码

方式三 getBoundingClientRect

该api返回值是一个 DOMRect对象,拥有left, top, right, bottom, x, y, width, 和 height属性

const target = document.querySelector('.target');
const clientRect = target.getBoundingClientRect();
console.log(clientRect);

// {
// bottom: 556.21875,
// height: 393.59375,
// left: 333,
// right: 1017,
// top: 162.625,
// width: 684
// }
复制代码

属性对应的关系图如下所示:

image.png

当页面发生滚动的时候,topleft属性值都会随之改变

如果一个元素在视窗之内的话,那么它一定满足下面四个条件:

  • top 大于等于 0
  • left 大于等于 0
  • bottom 小于等于视窗高度
  • right 小于等于视窗宽度

实现代码如下

function isInViewPort(element) {
const viewWidth = window.innerWidth || document.documentElement.clientWidth;
const viewHeight = window.innerHeight || document.documentElement.clientHeight;
const {
top,
right,
bottom,
left,
} = element.getBoundingClientRect();

return (
top >= 0 &&
left >= 0 &&
right <= viewWidth &&
bottom <= viewHeight
);
}
复制代码

方式四 Intersection Observer

Intersection Observer 即重叠观察者,从这个命名就可以看出它用于判断两个元素是否重叠,因为不用进行事件的监听,性能方面相比getBoundingClientRect会好很多

使用步骤主要分为两步:创建观察者和传入被观察者

创建观察者

const options = {
// 表示重叠面积占被观察者的比例,从 0 - 1 取值,
// 1 表示完全被包含
threshold: 1.0,
root:document.querySelector('#scrollArea') // 必须是目标元素的父级元素
};

const callback = (entries, observer) => { ....}

const observer = new IntersectionObserver(callback, options);
复制代码

通过new IntersectionObserver创建了观察者 observer,传入的参数 callback 在重叠比例超过 threshold 时会被执行`

关于callback回调函数常用属性如下:

// 上段代码中被省略的 callback
const callback = function(entries, observer) {
entries.forEach(entry => {
entry.time; // 触发的时间
entry.rootBounds; // 根元素的位置矩形,这种情况下为视窗位置
entry.boundingClientRect; // 被观察者的位置举行
entry.intersectionRect; // 重叠区域的位置矩形
entry.intersectionRatio; // 重叠区域占被观察者面积的比例(被观察者不是矩形时也按照矩形计算)
entry.target; // 被观察者
});
};
复制代码

传入被观察者

通过 observer.observe(target) 这一行代码即可简单的注册被观察者

const target = document.querySelector('.target');
observer.observe(target);
复制代码

举例

假如我们需要实现图片加载前loading的效果的话,我们就可以给img的src设为loading图片的路径,data-src设为图片真实路径,在上述callback函数,将data-src的值赋给src即可

优点

**Intersection Observer**的优点在于不需要对事件进行监听,而上面两个方法都需要对类似于scroll事件进行监听,如果渲染列表很长,有可能会造成页面卡顿,因为scroll事件伴随了大量的计算,会造成资源方面的浪费


方式五 vue-lazyload

[github.com/hilongjw/vu…] Github地址

这是在vue框架中可以安装的懒加载的插件,使用十分方便,可以通过v-lazy指令的形式写到对应的元素实现懒加载

<img v-lazy="img.thumbnail_pic_s">  // 用v-lazy替换src
复制代码

实现方式

  1. 安装
npm install vue-lazyload --save
复制代码
  1. 全局注册(main.js)

    // main.js 文件
    import VueLazyload from 'vue-lazyload'
    // Vue.use(VueLazyload) //无配置项
    // 配置项
    const loadimage = require('assets/img/common/loading.gif')
    // const errorimage = require('assets/img/common/error.gif')
    Vue.use(VueLazyload, {
    preLoad: 1.3, //预加载的宽高比
    loading: loadimage, //图片加载状态下显示的图片
    // error: errorimage, //图片加载失败时显示的图片
    attempt: 1, // 加载错误后最大尝试次数
    })
    // img元素上使用v-lazy="src";<img v-lazy="showImage"/>
    复制代码

    配置项的参数说明

    描述 默认 选项
    preLoad 表示lazyload的元素, 距离页面底部距离的百分比. 计算值为(preload - 1) 1.3 Number
    error 加载失败后图片地址 ‘data-src’ String
    loading 加载时图片地址 ‘data-src’ String
    attempt 图片加载失败后的重试次数 3 Number
    listenEvents 触发懒加载的事件 [‘scroll’, ‘wheel’, ‘mousewheel’, ‘resize’, ‘animationend’, ‘transitionend’, ‘touchmove’]
    adapter 注册img 的loading,loaded,error 三个状态的回调函数, 参数会暴露懒加载的img元素, 可以对其进行操作. { }
    filter img未加载之前, 解析到src 的时候注册的回调函数. 可以在加载图片之前,对src进行修改. 注册在filter下的所有的函数都会执行 { }
    lazyComponent 是否启用懒加载组件. 组件中的内容 只有在出现在preload的 位置中才会加载组件. 这个lazyloadComponent 组件有个缺点 就是,组件在加载前 是什么都不渲染的, 这样子的话,有可能会影响布局, 以及加载前到加载后的切换不好, 有点突兀和生硬. false
    dispatchEvent 触发dom事件 false Boolean
    throttleWait 等待时长 200 Number
    observer 是否启用IntersectionObserver, 这个api有兼容问题 false Boolean
    observerOptions IntersectionObserver选项 { rootMargin: ‘0px’, threshold: 0.1 }
    silent 不打印调试信息 true Boolean

因为src中的文件会被webpack编译,assets文件夹中的图片地址,会在编译过程中重命名。vue-lazyload是在main.js文件中引入,不会被webpack进行编译,因此vue-lazyload无法获得正确的图片地址,所以直接写相对地址就无法获取到图片正确地址

  1. 使用
  <div class="lazyLoad">
<ul>
<li v-for="img in arr">
<img v-lazy="img.thumbnail_pic_s">
</li>
</ul>
</div>
复制代码

这里有个坑需要注意 如设置了翻页功能,且每一页都是请求的数据进行渲染。 会发现其他的数据都变了,唯独图片还是原来的图片。 由于使用的数据是父组件传过来的,第一个想到父组件axios异步请求的数据导致子组件可能数据没有动态更新。但监听了下数据,发现确实是改变了 . 解决办法只要加个key就行, 如下代码

<ul>  
<li v-for="img in list">
<img v-lazy="img.src" :key="img.src" >
</li>
</ul>
复制代码

But, 上面的库只支持Vue2 在Vue3中main.js注册会报错,应该是开发者罢工了,没有跟着迭代

解决

本着不重复造轮子原则,github上一顿搜,找到了 vue3-lazy 这款代替品,看文档使用步骤跟之前类似,简单操作之后,可用,也提升了项目首页加载速度(虽然options少了很多,但还是可以用)

安装命令

npm install vue3-lazy -S
//或者
yarn add vue3-lazy
复制代码

main.ts 中初始化

import App from './App.vue'
import lazyPlugin from 'vue3-lazy'

createApp(App)
.use(lazyPlugin, {
loading: require('@/assets/images/default.png'), // 图片加载时默认图片
error: require('@/assets/images/error.png')// 图片加载失败时默认图片
})
.mount('#app')
复制代码

template中使用

<ul>
<li v-for="img in list">
<img v-lazy="img.src" >
</li>
</ul>
复制代码

参考地址:

github地址

参考资料

[blog.csdn.net/muzidigbig/…] Vue图片懒加载之Vue-Lazyload