图片懒加载

参考:https://blog.csdn.net/weixin_43913643/article/details/132975686

参考:https://blog.csdn.net/qq_44947815/article/details/125286969

图片懒加载是前端性能优化中的一项重要技术。它的核心思想是在页面加载时,只显示用户视口内的图片,而视口外的图片则等到它们即将进入视口时再进行加载。这样做可以显著提升页面的初始化渲染速度和用户体验。

总的来说,图片懒加载不仅能够提升页面加载速度,还能够减少服务器的压力和节省用户的带宽,尤其适合图片资源较多或者页面内容较长的网站。随着前端技术的不断发展,懒加载技术也在不断进化,以适应更多的场景和需求。

vue-lazyload插件

参考:https://blog.csdn.net/2301_76635548/article/details/131007819

要求: 需要给图片设置高度

注意: 用户在浏览器开启缓存禁用 或者 后端接口设置了no-cache 会导致图片加载2次。参考

Vue3实现

  1. 安装vue-lazyload插件:

    npm install vue-lazyload --save-dev
    
  2. 全局main.js文件中引入并注册vue-lazyload:

    import VueLazyLoad from 'vue-lazyload'
    
    const app = createApp(App)
    app.use(VueLazyLoad, {
      // 可选配置项
      error: require('./assets/img/error.jpg'), // 加载失败时显示的图片
      loading: require('./assets/img/loading.gif'), // 加载中时显示的图片
      preLoad: 1.3, // 预加载高度的比例
      attempt: 3, // 尝试加载次数
    })
    
    
    
  3. 使用指令:vue-lazyload提供了一个自定义指令v-lazy,我们可以在img标签或者任何需要设置背景图片的标签上使用它。例如:

    <template>
      <div>
        <!-- 懒加载img标签 -->
        <img
          v-for="n in 4"
          :key="n"
          v-lazy="getImageUrl(`/src/view/performance-optimization/lazy-images/assets/${n}.png`)"
          :alt="`Image ${n}`" />
    
        <!-- 懒加载背景图片 -->
        <div
          style="width: 100%; height: 100vh; background-size: cover"
          v-lazy:background-image="
            getImageUrl(`/src/view/performance-optimization/lazy-images/assets/5.png`)
          "></div>
      </div>
    </template>
    
    <script setup lang="ts">
      import { getImageUrl } from '@common/utils/images'
    </script>
    
    <style scoped>
      img {
        width: 100%;
        height: 100vh;
      }
    </style>
    

    v-lazy指令接收一个字符串类型的值,表示图片的地址。如果是背景图片,需要在指令后加上:background-image修饰符

原理解析: vue-lazyload的核心原理是利用了IntersectionObserver API,这是一个用于检测元素是否与视口相交的API,它可以高效地监听元素的可见性变化,并触发回调函数。vue-lazyload在注册插件时,会创建一个全局的IntersectionObserver实例,并设置一些选项,如阈值(threshold)、根元素(root)等。然后,在绑定v-lazy指令时,会创建一个监听器(listener)对象,并将其添加到一个监听器队列(listenerQueue)中。每个监听器对象都包含了元素的相关信息,如状态(state)、图片地址(src)等。

接下来,vue-lazyload会遍历监听器队列,并调用IntersectionObserver实例的observe方法,将每个元素注册到观察者中。当元素与视口相交时,IntersectionObserver实例会触发回调函数,并传入一个entries参数,表示所有被观察的元素的状态信息。vue-lazyload会根据entries中的isIntersecting属性判断元素是否可见,如果是,则调用监听器对象的load方法,将元素的src或者style属性替换为真实的图片地址,并将该监听器对象从队列中移除。
——————————————

原文链接:https://blog.csdn.net/2301_76635548/article/details/131007819

IntersectionObserve / UseIntersectionObserver

API:IntersectionObserver - Web API 接口参考 | MDN

参考

原理:IntersectionObserver是浏览器原生提供的构造函数,使用它能省到大量的循环和判断。这个构造函数的作用是它能够观察可视窗口与目标元素产生的交叉区域。简单来说就是当用它观察我们的图片时,当图片出现或者消失在可视窗口,它都能知道并且会执行一个特殊的回调函数,就可以利用这个回调函数实现需要的操作。

new IntersectionObserver(([entry]) => {
  // entry 属性含义:https://developer.mozilla.org/zh-CN/docs/Web/API/IntersectionObserverEntry
  // time: 时间戳,单位毫秒,表示观察者观察到元素的时刻
  // target: 被观察的元素,是一个DOM节点对象
  // rootBounds: 容器元素的矩形区域的信息。getBoundingClientRect() 方法的返回值,如果没有容器元素(即直接相对于视口滚动),则返回null
  // boundingClientRect: 目标元素的矩形区域的信息
  // intersectionRect: 目标元素与视口(或容器元素)的交叉区域的信息
  // intersectionRatio: 目标元素的可见比例,即 intersectionRect 占 boundingClientRect 的比例,完全可见时为 1,完全不可见时小于等于 0
  // isIntersecting: 元素是否在可视区域内
  if (entry.isIntersecting) {
    setTimeout(() => {
      el.src = bind.value
    }, 2000)
    observer.unobserve(el)
  }
})
observer.observe(el)

技术思路:

  1. 检测图片位置:使用Intersection Observer API来判断图片是否进入视口。这是一种现代且高效的方式,它可以精确地知道元素何时出现在视口中。
  2. 设置标记:为需要懒加载的图片设置特定的标记,例如使用HTML5的自定义属性。
  3. 编写脚本:通过JavaScript编写脚本来控制图片的加载行为。当检测到图片进入视口时,脚本会触发图片的加载。
  4. 数据模拟:在实际应用中,还需要后端配合,实现图片的分页加载,以便在用户滚动页面时能够及时加载新的内容。

技术实现: 首先在HTML中为需要懒加载的图片添加了一个自定义属性data-src,用于存储图片的真实URL。然后,在JavaScript中,我们创建了一个Intersection Observer实例,并监听了所有带有data-src属性的图片元素。当这些图片元素进入视口时,我们会将它们的src属性设置为data-src属性的值,从而触发图片的加载。同时,为了避免重复加载已经加载过的图片,我们在加载完成后会取消对图片元素的观察。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>图片懒加载示例</title>
    <style>
        img {
            width: 100%;
            height: auto;
        }
    </style>
</head>
<body>
    <div class="container">
        <img data-src="image1.jpg" alt="Image 1">
        <img data-src="image2.jpg" alt="Image 2">
        <img data-src="image3.jpg" alt="Image 3">
        <!-- 更多图片 -->
    </div>
    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var observer = new IntersectionObserver(function(entries) {
                entries.forEach(function(entry) {
                    if (entry.isIntersecting) {
                        var img = entry.target;
                        img.src = img.dataset.src;
                        observer.unobserve(img);
                    }
                });
            });

            var images = document.querySelectorAll("img[data-src]");
            images.forEach(function(img) {
                observer.observe(img);
            });
        });
    </script>
</body>
</html>

Vue 局部自定义指令通过 IntersectionObserver实现:

<template>
  <div class="lazy-load-images-bg">
    <img
      v-for="n in 9"
      :key="n"
      v-lazy="getImageUrl(`/src/assets/images/full-screen/${n}.webp`)"
      :alt="`Image ${n}`" />
  </div>
</template>

<script setup lang="ts">
  import type { Directive } from 'vue'

  const getImageUrl = (url: string) => {
    return new URL(url, import.meta.url).href
  }

  // 图片懒加载:IntersectionObserver
  const vLazy: Directive<HTMLImageElement, string> = async (el, bind) => {
    const def = await import('@/assets/images/vue-logo.png')
    el.src = def.default
    const observer = new IntersectionObserver(([entry]) => {
      if (entry.isIntersecting) {
        el.src = bind.value
        observer.unobserve(el)
      }
    })
    observer.observe(el)
  }
</script>

<style scoped lang="less">
  .lazy-load-images-bg {
    width: 100%;
    height: 100%;
    overflow-y: auto;
  }
  /* 注意:图片要设定宽高 */
  img {
    display: block;
    width: 100%;
    height: 100%;
  }
</style>

Vue 全局自定义指令通过 useIntersectionObserver 实现:

lazyLoad.ts

import { useIntersectionObserver } from '@vueuse/core'

export const lazyLoadImage = {
  install(app: any) {
    app.directive('lazy-image', {
      mounted(el: any, binding: any) {
        el.src = '@/assets/images/default.png'

        const { stop } = useIntersectionObserver(el, ([{ isIntersecting }]) => {
          if (isIntersecting) {
            el.src = binding.value
            stop()
          }
        })
      },
    })
  },
}

main.ts

import { lazyLoadImage } from '@/directive/lazyLoad'

const app = createApp(App)
app.use(lazyLoadImage)
<template>
  <div class="lazy-load-images-bg">
    <img
      v-for="n in 9"
      :key="n"
      v-lazy-image="getImageUrl(`/src/assets/images/full-screen/${n}.webp`)"
      :alt="`Image ${n}`" />
  </div>
</template>

<!-- 使用 v-lazy-image 自定义指令 -->
<script setup lang="ts">
  const getImageUrl = (url: string) => {
    return new URL(url, import.meta.url).href
  }
</script>

<style scoped lang="less">
  .lazy-load-images-bg {
    width: 100%;
    height: 100%;
    overflow-y: auto;
  }
  /* 注意:图片要设定宽高 */
  img {
    display: block;
    width: 100%;
    height: 100%;
  }
</style>

scroll 监听 + scrollTop+offsetTop+innerHeight

要求: 需要给图片设置高度

Vue3实现:

<template>
  <div class="container" ref="lazyImagesContainerRef" @scroll.passive="lazyLoadThrottle">
    <img
      v-for="n in 12"
      :key="n"
      :data-src="getImageUrl(`/src/view/performance-optimization/lazy-images/assets/${n}.png`)"
      :alt="`Image ${n}`" />
  </div>
</template>

<script setup lang="ts">
  import { ref, onMounted } from 'vue'
  
  /**
   * 获取本地静态资源
   * @param url 本地资源路径。注意:只能是绝对路径
   * @returns
   */
  export const getImageUrl = (url: string) => {
    return new URL(url, import.meta.url).href
  }

  /**
   * 节流
   * @param delay 单位 毫秒,延迟时间
   * @returns
   */
  const throttle = (cb: any, delay: number) => {
    let timer: any

    return () => {
      if (timer) {
        return false
      }

      timer = setTimeout(() => {
        cb && cb()
        clearTimeout(timer)
        timer = null
      }, delay)
    }
  }

  const lazyImagesContainerRef = ref(null)
  const lazyLoad = () => {
    // 浏览器可视区的高度
    const visibleAreaHeight =
      window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight

    const bufferHeight = visibleAreaHeight / 2

    // 当前的滚动距离
    // 如果是body进行滚动
    // const scrollTop =
    //   document.documentElement.scrollTop || window.pageYOffset || document.body.scrollTop
    const lazyImagesContainer = lazyImagesContainerRef.value as any
    const scrollTop = lazyImagesContainer.scrollTop

    const images: HTMLImageElement[] = lazyImagesContainer.querySelectorAll('img[data-src]')
    images.forEach(image => {
      // 图片距离顶部的高度
      const imgScrollTop = image.offsetTop

      if (imgScrollTop - bufferHeight <= scrollTop + visibleAreaHeight) {
        image.src = image.getAttribute('data-src') || ''
        // 性能优化:已经加载的不会再进行加载
        image.removeAttribute('data-src')
      }
    })
  }

  // 性能优化:使用节流
  const lazyLoadThrottle = throttle(lazyLoad, 500)

  onMounted(() => {
    // 用于首屏加载
    lazyLoad()
  })
</script>

<style scoped>
  .container {
    width: 100%;
    height: 100%;
    overflow-y: auto;
  }
  img {
    width: 100%;
    height: 100vh;
  }
</style>

scroll 监听 + getBoundingClientRect()

API:Element.getBoundingClientRect() - Web API 接口参考 | MDN

const oBounding = image.getBoundingClientRect()

oBounding.top:元素上边到视窗上边的距离;
oBounding.right:元素右边到视窗左边的距离;
oBounding.bottom:元素下边到视窗上边的距离;
oBounding.left:元素左边到视窗左边的距离;
oBounding.width:元素自身的宽度
oBounding.height:元素自身的高度

在这里插入图片描述

要求: 需要给图片设置高度

注意: 挂载时 / 图片分页后 需要立刻调用lazyLoad函数,不然首屏不会加载

Vue3实现:

<template>
  <div class="container" ref="lazyImagesContainerRef" @scroll.passive="lazyLoadThrottle">
    <img
      v-for="n in 12"
      :key="n"
      :data-src="getImageUrl(`/src/view/performance-optimization/lazy-images/assets/${n}.png`)"
      :alt="`Image ${n}`" />
  </div>
</template>

<script setup lang="ts">
  import { ref, onMounted } from 'vue'
  
  /**
   * 获取本地静态资源
   * @param url 本地资源路径。注意:只能是绝对路径
   * @returns
   */
  export const getImageUrl = (url: string) => {
    return new URL(url, import.meta.url).href
  }

  /**
   * 节流
   * @param delay 单位 毫秒,延迟时间
   * @returns
   */
  const throttle = (cb: any, delay: number) => {
    let timer: any

    return () => {
      if (timer) {
        return false
      }

      timer = setTimeout(() => {
        cb && cb()
        clearTimeout(timer)
        timer = null
      }, delay)
    }
  }

  // 获取所有img标签
  const lazyImagesContainerRef = ref()
  const lazyLoad = () => {
    // 浏览器可视区的高度
    const visibleAreaHeight =
      window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight

    const bufferHeight = visibleAreaHeight / 2

    const images: HTMLImageElement[] =
      (lazyImagesContainerRef.value as any).querySelectorAll('img[data-src]') || []
    images.forEach(image => {
      const oBounding = image.getBoundingClientRect() // 返回一个矩形对象,包含上下左右的偏移值

      if (oBounding.top - bufferHeight <= visibleAreaHeight) {
        image.src = image.getAttribute('data-src') || ''
        // 性能优化:已经加载的不会再进行加载
        image.removeAttribute('data-src')
      }
    })
  }

  // 性能优化:使用节流
  const lazyLoadThrottle = throttle(lazyLoad, 500)

  onMounted(() => {
    // 用于首屏加载
    lazyLoad()
  })
</script>

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

HTML新增 loading属性:loading="lazy"

要求: 需要给图片设置高度

实现:

<template>
  <div>
    <img src="./assets/1.png" alt="`Image 1" loading="lazy" />
    <img src="./assets/2.png" alt="`Image 2" loading="lazy" />
    <img src="./assets/3.png" alt="`Image 3" loading="lazy" />
    <img src="./assets/4.png" alt="`Image 4" loading="lazy" />
    <img src="./assets/5.png" alt="`Image 5" loading="lazy" />
    <img src="./assets/6.png" alt="`Image 6" loading="lazy" />
    <img src="./assets/7.png" alt="`Image 7" loading="lazy" />
    <img src="./assets/8.png" alt="`Image 8" loading="lazy" />
  </div>
</template>

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

属性:

auto:浏览器默认的懒加载策略,和不增加这个属性的表现一样

lazy:在资源距当前视窗到了特定距离内后再开始加载

eager:立即加载,无论资源在页面中什么位置

兼容性:
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值