图片懒加载vue-lazyload的核心原理

一、背景

图片是非常占用页面渲染时间的,尤其是一些图片比较多的页面,过多的图片可能会造成页面的卡顿降低流畅度影响用户体验,我们在实际开发中,对于处于视口外的图片,在用户没有滚动到位置的时候没必要渲染,此时我们就需要用到懒加载,让图片延后渲染。

vue中有一个插件vue-lazyload,它提供了一个“vue指令”可以完成上面需求,那他的原理是什么呢,最近我看了一下这个库的源代码,跟大家分享一下。

插件源码我就不贴了哈,为了通用性,它里面有很多处理通用性代码,我只拿最基本的代码出来。

全量代码我放在了文章结尾,各位同学可以手动复制看效果哦

二、效果

说明一下,为了效果明显,我把控制渲染的逻辑提前了一下

这样能明显的看到效果,实际开发中,应当把,加载新图片的逻辑放在看不到的位置进行提前加载。

三、一句话原理

获取视口的底部位置,并把所有img的src属性 复制给data-src,使用数组收集所有的img dom 对象。通过监听页面的scroll事件,使用getBoundingClientRect()方法,判断需要加载图片的img标签的top值是否在视口内,如果在视口内,则把当前img标签的data-src复制给src,进行加载。

名词解释

  • img html用来加载图片的一个标签
  • src img指向图片位置的属性
  • data-* 这个是一个自定义属性,可以在dom节点中自定义属性,后面可以通过元素的dataset属性获取,比如本文中用到的data-src,后面就可以使用 dom.dataset.src获取。
  • getBoundingClientRect() 获取,元素渲染后,在页面所处的位置

原理真的是非常的简单呢,涉及到的知识点也不多,接下来我会详细说一下原理,并跟大家一起实现一下这个图片懒加载。

四、实现原理

一、获取视口底部位置,在移动端可以使用 clientHeight 来获取到视口底部的像素值,pc端可以使用getBoundingClientRect() 获取

二、收集所有img标签转成数组并保存,当然实际开发中,我们对获取方式还要加一些其他条件,避免有些不需要懒加载的图片也被收集进来,这里我就简单点了。

三、写个渲染函数用来处理图片的渲染逻辑,主要逻辑是判断当前dom节点是否在视口内,需要注意的是有个问题:

  1. 开始的时候由于dom节点没有图片,宽度没有被撑开,导致所有节点都在视口内
  2. 由于问题"1"的存在,我们需要把排在前面的dom渲染完,才能判断下一dom是否在视口内
  3. 可以使用img的load事件的回调来处理。

五、代码实现与讲解

全量代码我放在了文章结尾,给位同学可以手动复制看效果哦,图片我没有提供,各位可以自己找一下哈。

一、基本结构和样式

html

<div id="mobile-viewport">
        <img data-src="./pic/1.jpeg">
        <img data-src="./pic/2.jpeg">
        <img data-src="./pic/3.jpeg">
        <img data-src="./pic/4.jpeg">
        <img data-src="./pic/5.jpeg">
        <img data-src="./pic/6.jpeg">
</div>

需要注意的是,这里用的不是src,是data-src

css

#mobile-viewport {
        height: 480px;
        width: 270px;
        border: 2px solid #000000;
        overflow: auto;

    }

    img {
        width: 100%;
    }

二、获取视口底部位置

 //模拟移动端视口 移动端使用 clientHeight
        let mobileViewportDom = document.getElementById('mobile-viewport')
        let mobileViewportRect = mobileViewportDom.getBoundingClientRect()
        //获得视口最下端的位置
        let {  bottom : mobileViewportRectBottom  } = mobileViewportRect

三、获取所有img节点

 //取到所有img
        let domArray = Array.from(document.getElementsByTagName('img'))  

四、渲染函数

 //定义渲染方法
        function renderPicture() {
            //如果 img 数组为空直接返回
            if (domArray.length === 0 ) {
                return
            }
            //获取当前长度,后面对比用
            let beginL = domArray.length
            //取数组第一个
            let imgDom = domArray[0]

            //给当前dom 添加load回调

            function isRenderPicture(){
                //回调中对比length是否发生改变
                if(beginL !== domArray.length){
                    // 如果发生改变,在调用一次看下一个是否也需要渲染
                    renderPicture(domArray)
                }
                imgDom.removeEventListener("load", isRenderPicture)
            }
            //给当前pic dom 添加加载回调
            imgDom.addEventListener('load', isRenderPicture)
            //获取当前img dom在页面中的位置
            let domRact = imgDom.getBoundingClientRect()
            //用img的最上点位置与视口的最下点对比
            if(domRact.top < mobileViewportRectBottom){
                //删除数组第一个,此处domArray的length发生改变
                domArray.shift()
                //复制scr属性
                imgDom.src = imgDom.dataset.src
            }

        }

有几个比较复杂的点:

1、在判断渲染点的时候,如果进行了渲染,我们会把已经渲染的dom节点移除domArray数组,所以在load事件中我们需要判断一下,两次domArray的长度是否相等,如果不等,证明渲染过,此时我们还要在调用一下渲染函数,判断下一个dom节点是否也需要渲染。

2、在添加load事件的时候,要在load的回调执行完移除这个监听,否则会非常的影响性能。

3、使用了shift(),函数应用了”队列“这个数据结构的特性,不熟悉的同学稍微看一下数据结构与算法吧。

六、总结

好的,到此我们这个功能就简单的实现了,这里给大家留两个思考题

一、代码中我是用了”domRact.top < mobileViewportRectBottom“ 来判断是否需要渲染,如何修改这个判断让图片渲染提前呢?

二、能不能利用浏览器的空闲时间,比如用户在浏览某个商品,把图片预载到内存中,在滚动的时候直接从内存加载呢?如何实现呢?

欢迎在评论区留言,一起探讨

录:全量代码

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>图片懒加载的核心原理</title>
</head>

<style>
    #mobile-viewport {
        height: 480px;
        width: 270px;
        border: 2px solid #000000;
        overflow: auto;

    }

    img {
        width: 100%;
    }
</style>

<body>
    <div id="mobile-viewport">
        <img data-src="./pic/1.jpeg">
        <img data-src="./pic/2.jpeg">
        <img data-src="./pic/3.jpeg">
        <img data-src="./pic/4.jpeg">
        <img data-src="./pic/5.jpeg">
        <img data-src="./pic/6.jpeg">
    </div>
    <script src="../utils/lodsh.js"></script>
    <script>
        //模拟移动端视口 移动端使用 clientHeight
        let mobileViewportDom = document.getElementById('mobile-viewport')
        let mobileViewportRect = mobileViewportDom.getBoundingClientRect()
        //获得视口最下端的位置
        let {  bottom : mobileViewportRectBottom  } = mobileViewportRect
        //取到所有img
        let domArray = Array.from(document.getElementsByTagName('img'))  
        
        
        //定义渲染方法
        function renderPicture() {
            //如果 img 数组为空直接返回
            if (domArray.length === 0 ) {
                return
            }
            //获取当前长度,后面对比用
            let beginL = domArray.length
            //取数组第一个
            let imgDom = domArray[0]

            //给当前dom 添加load回调

            function isRenderPicture(){
                //回调中对比length是否发生改变
                if(beginL !== domArray.length){
                    // 如果发生改变,在调用一次看下一个是否也需要渲染
                    renderPicture(domArray)
                }
                imgDom.removeEventListener("load", isRenderPicture)
            }
            //给当前pic dom 添加加载回调
            imgDom.addEventListener('load', isRenderPicture)
            //获取当前img dom在页面中的位置
            let domRact = imgDom.getBoundingClientRect()
            //用img的最上点位置与视口的最下点对比
            if(domRact.top < mobileViewportRectBottom){
                //删除数组第一个,此处domArray的length发生改变
                domArray.shift()
                //复制scr属性
                imgDom.src = imgDom.dataset.src
            }

        }
        //添加滚动事件
        mobileViewportDom.addEventListener('scroll', _.debounce(renderPicture,200))

        renderPicture(domArray)

    </script>
</body>

</html>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值