自己动手撸一个图片懒加载

472 阅读4分钟

懒加载实现思路

(本文以vue为例,其他框架类同,学习记录)

图片懒加载使用场景

在我们制作的移动端应用中(包括WEB,app,小程序),时常会出现一种长列表的图文信息

而在我们写完成了之后,就会考虑如何去优化这个列表

在这个时候,我脑子里第一个想到的就是图片懒加载

因为在列表渲染过程中会发出很多个图片请求,尽管我们使用起来可能没什么感觉,但是实现一个懒加载可能会少发出很多的图片请求,致使服务器的压力变小

在工作中,我们总是会拿到很多的数据来渲染列表,通常我们的写法会是这样的

    <template>
        <div>
    	    <div v-for="item in list" :key="item.id">
        	<div class="name">{{item.name}}</div>
        	<div class="img">
        	   <img :src="item.img" />
        	</div>
    	    </div>
    	</div>
    </template>

而这种写法会使得只要list的数据出现了,同时也会发起多个图片请求

为了规避同时发起如此多的图片请求,所以才有了懒加载的出现

懒加载实现思路

在用户的滚动条滚动到该元素的位置时,我们才开始加载图片

所以,我们知道了,该使用监听滚动条的事件,来判断是否加载图片

于是我们知道了如下写法:

<script type="text/javascript">
   exprot defalut {
    data() {
      return {
        scroll: ''
      }
    },
    methods: {
      menu() {
        this.scroll = document.documentElement.scrollTop || document.body.scrollTop;
        console.log(this.scroll)
      }
    },
    mounted() {
      window.addEventListener('scroll', this.menu)
    },
  }
 </script>

那么,现在我们实现了监听滚动事件,如何判断什么时候加载图片呢

毫无疑问,我们来判断当前img元素是否出现在视野当中,也就是对屏幕底部的距离如果大于0就开始加载图片

于是,我们写成这样

<template>
    <div>
        <div v-for="item in list" :key="item.id">
	    <div class="name">{{item.name}}</div>
	    <div>
	    <!-- 通过每个item的show来判断图片的路径是否正确达到控制图片加载的效果 -->
		<img :src="item.show?item.img:''" />
	    </div>
	</div>
    </div>
</template>
<script>
export default {
	data() {
	    return {
		list: [
        		{
        		    id:1,
        		    img:'../1.png',
        		    name:'图片1',
        		    show:false
        		},
        		{
        		    id:2,
        		    img:'../2.png',
        		    name:'图片2',
        		    show:false
        		}
	        ]
	   }
	},
	methods: {
	    scroll() {
        	    //  图片
        	    let list = document.querySelectorAll(".img")
        	      // 判断图片是否已经被加载,若未被加载,则进入判断
        	     list.forEach((item,index)=>{
        		    if(!this.list[index].show){
        		    // 找出当前图片相对于视口的位置
        	            let y = item.getBoundingClientRect().y
        	            if (y > 0) {
        		    // 更加当前item的show属性刷新视图,设置为true,则图片路径为正确路径开始发起图片请求
        			this.list[index].show = true
        		    }
        		}
        	    })
            },
	}
	mounted() {
          window.addEventListener('scroll', this.scroll)
        },
}
</script>

到这里为止,我们发现,图片懒加载其实已经成功了,但是由于不停地监听滚动事件,所以我们的性能提升并没有我们想象之中的那么明显

幸运的是,浏览器给我们提供了IntersectionObserver 对象,用于推断某些节点是否可以被用户看见

于是,我们的代码变成了以下写法

tempalte部分

<template>
    <div>
        <div v-for="item in list" :key="item.id">
	    <div class="name">{{item.name}}</div>
	    <div>
	    <!-- 通过data-src属性填入实际图片路径阻止img加载真实图片,用加载图片代替 --!>
		<img :data-src="item.img" src="../loading-img.png" />
	    </div>
	</div>
    </div>
</template>

js部分


mounted() {
    //页面加载完成后,讲所有img挂载上IntersectionObserver
    const imgs = document.querySelectorAll('img')
    const io = new IntersectionObserver(entries => {
      //多张图片时,我们需要用forEach来给循环
      entries.forEach(entry => {
        //判断每张图片是否出现在屏幕中
        if (entry && entry.isIntersecting) {
          //将元素上的data-src复制给元素的路径,开启图片加载
          entry.target.src = entry.target.dataset.src
          //同时停止观察该元素
          io.unobserve(entry.target)
        }
      })
    })
    imgs.forEach(item => {
      io.observe(item)
    })
},

到目前为止,我们就以我目前知道最优的方法实现了一个vue的懒加载,其实其他框架也是同类逻辑,值得一提的是,微信内置的浏览器和小程序端也支持IntersectionObserver对象

小弟技术很菜,有讲的不对的轻喷

哈哈,欢迎各位大佬给出建议