• 作者:老汪软件技巧
  • 发表时间:2024-11-20 21:05
  • 浏览量:

前端开发中,图片懒加载是常见的优化措施之一。在使用 vue3 开发项目里,借助 Element Plus ,只需要给 添加上 loading="lazy" 或是 lazy 属性,就可以很方便地实现图片的懒加载。但如果离开了第三方框架,我们又该如何实现图片的懒加载呢?接下去就说说我的实现方案。

前期准备

我准备了几张图片放在项目的 public\imgs 目录中。页面组件代码如下,就是将几张图片简单地依次竖向排列:





此时打开页面效果如下,可以看到 6 张图片是同时请求的:

原生方案

原生

标签有个 loading 属性,默认值为 eager,意为立即加载图片,而不管该图片是否在可视视口之内。我们可以将 loading 的值改为 lazy,就可以实现图片的延迟加载,直到它和视口接近到一个计算得到的距离(由浏览器定义)。

现在给代码片段一中的

标签添加上 loading="lazy":

<img class="img" :src="item" alt="" loading="lazy" />

此时再次加载页面,可以看到初始时浏览器只请求了 4 张图片,只有当页面滚到一定位置时,才会去请求 5.jpg 和 6.jpg:

请注意,并非所有浏览器都支持这种原生方案,其可用性如下:

3.png

而且原生方案也无法在图片加载之前添加上一个默认的占位图片。

自定义实现

我们可以自定义一个 vue 指令 v-lazy ,结合滚动监听来实现我们自己的懒加载方案。

封装全局指令

为了方便维护,我们可以在项目的 src 目录下新建 directives 文件夹用于管理自定义指令,里面新建 index.ts 和具体指令的定义。比如 lazy 指令我就定义在 lazy.ts 中:

// src\directives\lazy.ts
import type { App } from 'vue'
export default function (app: App) {
  app.directive('lazy', {
    // 具体实现
  })
}

然后在 index.ts 中引入:

// src\directives\index.ts
import lazyDirective from './lazy'
import type { App } from 'vue'
export default function (app: App) {
  lazyDirective(app)
}

之后在 main.ts 引入 directives\index.ts 并将 app 传入执行即可:

// src\main.ts
import { createApp } from 'vue'
import App from './App.vue'
import lazyDirective from './directives'
const app = createApp(App)
lazyDirective(app)
app.mount('#app')

接下去就是对指令的具体实现了。

具体实现

图片懒加载vue_图片懒加载js_

先给代码片段一中的

标签添加上 v-lazy 并赋值 item,即各个图片的路径地址,同时可以删除 :src="item":

<img class="img" v-lazy="item" />

指令定义

在指令的 mounted 生命周期函数里, 将所有添加了 v-lazy 指令的图片元素,都对应地往一个准备好的数组 list 中加入一个对象,该对象有 2 个属性:

interface IListItem {
  el: any
  src: string
}
// 添加了 v-lazy 指令的图片信息,加载后就会被添加进 list
let list: IListItem[] = []
export default function (app: App) {
  app.directive('lazy', {
    mounted(el, binding) {
      const item = {
        el,
        src: binding.value
      }
      list.push(item)
      // 立即处理一次,主要作用于页面加载一开始就显示的图片
      handleImg(item)
    },
    unmounted(el) {
      list = list.filter(item => item.el !== el)
    }
  })
}

在 unmounted 中,即绑定了指令的图片元素的父组件卸载后,比如切换到别的页面时,就将该图片元素对应的对象从 list 中剔除。

位置判断

至于 handleImg() 方法,则是用于判断绑定了指令的图片是否出现在了视口范围内,从而需要加载:

function handleImg(item: IListItem) {
  // 默认占位图片
  item.el.src = '/imgs/0.jpg'
  const rect = item.el.getBoundingClientRect()
  // 图片位于视口下方
  const isBottom = rect.top >= viewHeight
  // 图片位于视口上方
  const isTop = rect.bottom <= 0
  // 图片位于视口内
  if (!isBottom && !isTop) {
    item.el.src = item.src
    // 处理后删除
    list = list.filter(imgItem => imgItem.src !== item.src)
  }
}

一开始时先让绑定了指令的图片的地址指向一张默认的占位图,即 0.jpg,图片为黑色背景,显示金色 2025 字样。

然后通过 getBoundingClientRect() 获取图片元素相对于视口的位置, 和 rect.bottom 的示意如下,图片引用至 mdn:

viewHeight 为视口高度,通过 window.innerHeight 获取:

// 获取视口高度
const viewHeight = window.innerHeight

如果判断结果为图片元素出现在了视口范围内,则将图片的 src 赋值为原本的图片地址,替换掉占位图。接着将该图片对应的对象从 list 中剔除,避免重复处理。

滚动监听

handleImg() 的调用则主要是在发生滚动的时候,为了防止过于频繁地触发,我使用了从 underscore 引入的防抖函数:

import _ from 'underscore'
// 监听滚动
window.addEventListener('scroll', _.debounce(handleImgList, 50))
// 处理图片数组
function handleImgList() {
  list.forEach(item => {
    handleImg(item)
  })
}

一旦页面发生滚动,则对 list 数组中的各个对象都执行一次 handleImg(),以判断对应的图片是否需要正确显示。

效果展示

现在,一开始时只会加载位于视口内的图片 1.jpg 和 2.jpg 以及占位图 0.jpg。当滚动页面时,再适时加载后续图片。图片加载过程中显示占位图,等所有图片都加载完毕,向上滚动也不会再有多余的处理:

点赞.png


上一条查看详情 +HarmonyOS Next密钥协商算法全攻略
下一条 查看详情 +没有了