20.图片懒加载的3种方法

方案: ①首屏加载时候img标签src赋为空值,这样就不会去渲染看不见的图片而浪费时间; ②当用户滑动到图片的可视区域后,替换src的路径,改为正式路径,则开始渲染图片;
好处:一是首屏加载快,二是节省流量。在图片没有到达可视区域的时候不会加载,因为不可能每一个用户会把页面从上到下滚动完。

 

方式一:传统方法通过监听滚动条来实现

<template>
    <div>
        <img v-for="(item,i) in lazyImgs" style="width: 180px;height:240px;margin-top:40px;display: inline-block;" :data-src="item"  :key="i" src=""  alt="">
    </div>
</template>
<script>
    export default{
        data(){
            return{
                lazyImgs:[]
            }
        },
        async mounted() {
            window.addEventListener("scroll", (e) => {
                // 这里做一个 节流 操作
                if (this.timer) return;
                this.timer = setTimeout(() => {
                    this.query("img[data-src]").forEach((img) => {
                        const rect = img.getBoundingClientRect();
                        if (rect.top < window.innerHeight) {
                            img.src = img.dataset.src;
                            // 我们是通过img[data-src]查找所有img标签的,渲染后就删除data-src可减少forEach循环的计算成本
                            img.removeAttribute("data-src"); 
                        }
                    });
                    clearTimeout(this.timer);
                // 这里一定要把定时器置为 null
                this.timer = null
                }, 300);
            });
        },
        methods: {
            query(selector) {
                return Array.from(document.querySelectorAll(selector));
            },
        }
    }
</script>

 

方式二:利用监听器实现(封装组件)

注意:用到的api:IntersectionObserver。就是一个监听器,学名叫交叉观测器,可以监听任何元素,当元素进入可视区域内,便会触发回调函数
弊端:IntersectionObserver方法可能没有兼容全浏览器,如果要实现兼容全浏览器,需要引入对应的插件实现
步骤一:新建一个文件lazyImg.vue
<template>
  <div class="img-box" v-lazy="vm" :data-src="src">
    <slot v-if="slotShow"></slot>
    <slot name="err" v-if="errFlag"></slot>
  </div>
</template>
<script>
export default {
  props: {src: {type: String,default: ""}},
  data() {return {slotShow: true,errFlag: false,vm: null,}},
  created() {this.vm = this;},
  directives: {
    lazy: {
      inserted(el, { value }) {
        const imgSrc = el.dataset.src;
        const observer = new IntersectionObserver(([{ isIntersecting }]) => {
          if (isIntersecting) {
            // 动态创建 img 添加到父元素内(若不动态加载,图片会从上到下依次加载,上面加载完成再显示下面的,在滚动的时候就会感觉有卡顿感)
            let img = document.createElement("img");
            img.src = imgSrc;
            img.style.width = "100%";
            img.style.height = "100%";
            // 添加图片加载完成事件:加载完成,让加载前的样式隐藏
            img.onload = function () {value.slotShow = false;};
            (img.onerror = function () {
              value.errFlag = true; // 加载失败显示的样式
              value.slotShow = false; //隐藏加载前的样式
            }),
            el.appendChild(img);
            observer.unobserve(el);
          }});
        observer.observe(el)}}
  },
  methods: {
      loadImg() {this.slotShow = false},
      error(e) {
          if(!e.srcElement.dataset.flag||!this.errFlag) return false
          // 这里我们就不给设置失败后的图片了,留给使用者自行设置样式
          // e.srcElement.src = this.errorImg
          this.errFlag = false
          this.slotShow = false}}
};
</script>
<style lang="less" scoped>
.img-box {display: flex;position: relative;overflow: hidden;}
img {width: 100%;height: 100%;object-fit: cover;}
</style>

步骤二:组件中使用

<template>
  <div>
    //图片懒加载最好设置图片高度,因为不管你是监听滚动条的方式,还是利用监听器api实现,都跟元素可视区域有关系,而高度就影响是否在可视区域内
    <lazyImg :index="i" v-for="(item,i) in lazyImgs" :key="i" :src="item">
      //图片加载之前默认在图片元素上方展示的样式
      <div class="slot-txt">加载中</div>
      <template #err>
        //图片加载失败后在上面展示的样式
        <div class="slot-txt">加载失败</div>
      </template>
    </lazyImg>
  </div>
</template>
<script>
import lazyImg from './components/lazyImg'
export default {
  components: {
    lazyImg
  },
  data: {
    return {
      lazyImgs: ['@assets/imgs/img1.png','@assets/imgs/img2.png','@assets/imgs/img2.png']
    }
  }
}
</script>
<style lang="scss" scoped>
.slot-txt {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  background: #f9ccd4;
  z-index: 100;
}
</style>

 

方式三:利用监听器实现(全局方法调用)

步骤一:main.js:设置全局自定义指令,命名为lazy

// 定义懒加载图片或者文件等,自定义指令
Vue.directive('lazy', (el, binding) => {
  let oldSrc = el.src //保存旧的src,方便后期渲染时候赋值src真实路径
  el.src = "" //将渲染的src赋为空,则不会渲染图片出来
  // 调用方法得到该elDOM元素是否处于可视区域
  let observer = new IntersectionObserver(([{ isIntersecting }]) => { 
    if (isIntersecting) { //回调是否处于可视区域,true or false
      el.src = oldSrc //如果处于可视区域额,将最开始保存的真实路径赋予DOM元素渲染
      observer.unobserve(el) // 只需要监听一次即可,第二次滑动到可视区域时候不在监听
    }
  })
  observer.observe(el) // 调用方法
})

步骤二:组件中使用

<template>
  <div>
    //v-lazy
    <div><img v-lazy src="@assets/imgs/img1.png" alt="img" /></div>
    <div><img v-lazy src="@assets/imgs/img2.png" alt="img" /></div>
  </div>
</template>

 

posted @ 2023-07-06 15:39  cjl2019  阅读(908)  评论(1编辑  收藏  举报