图片懒加载的方案

就目前所知,实现图片懒加载的方法有 IntersectionObserver,getBoundingClientRect,图片的loading属性

IntersectionObserver

原理:视窗(祖先元素)与目标元素相交时触发注册事件

useEffect(() => {
  // 实现懒加载
  const observer = new IntersectionObserver(
    (entries) => {
      entries.forEach((entry) => {
        if (entry.isIntersecting) {
          const img = imageRef.current
          if (src !== '' && src !== undefined) {
            img.src = src
            // 从缓存中读出,加载动画为 false
            img.complete && setIsLoading(false)
          } else {
            // 如果没有 src,使用默认图片
            setIsError(true)
          }
          observer.disconnect()
        }
      })
    },
    // 设置 rootMargin 预加载部分图片资源
    { root: document.querySelector(`${root}`), rootMargin:'0px 0px 0px 0px' }
  )
  // 如果图片元素存在,就监听它
  observer.observe(imageRef.current)
  // 组件卸载时,取消监听
  return () => observer.disconnect()
  // eslint-disable-next-line react-hooks/exhaustive-deps
}, [])

优点:使用 Intersection Observer 进行图片懒加载的好处在于可以避免通过监听 scroll 事件等方式造成的性能问题,并且在一些特定场景下(如图片在列表中的情况),也能实现更精细的控制。

缺点:交叉监视器的异步执行基于 event-loop,处于动画帧回调函数 requestAnimationFrame 之后,又在 requestidlecallback 之前。可以明确这个 api 的执行跟事件循环相关。大部分设备中浏览器的刷新频率为 60FPS ,大概是 16.6ms 一次,也就是说如果我们拖动滚动条的速度快于这个更新频率 IntersectionObserver 确实是有可能不会执行到的,因此就会出现快速刷新白屏问题

getBoundingClientRect

获取了所有需要懒加载的图片元素,然后监听 scroll 事件,并在事件处理程序中检查每个图片是否在可视区域内。如果是,就将 data-src 属性的值赋给 src 属性,从而加载图片。

const images = document.querySelectorAll('img[data-src]');

function lazyLoad() {
  images.forEach(img => {
    const rect = img.getBoundingClientRect();
    if (rect.top < window.innerHeight && rect.bottom >= 0) {
      img.src = img.dataset.src;
      img.removeAttribute('data-src');
    }
  });
}

window.addEventListener('scroll', lazyLoad);

缺点:

  • 需要计算元素位置:使用 getBoundingClientRect 需要计算每个需要懒加载的元素在视口中的位置和大小。这个计算过程需要耗费一定的时间,特别是当需要懒加载的元素比较多的时候,会对页面的性能产生影响。
  • 可能出现图片抖动:如果一个图片需要懒加载,它的 src 属性为空,当使用 getBoundingClientRect 判断该图片进入可视区域后,会将其 src 属性设置为图片地址,此时图片开始加载。这个过程可能会导致图片抖动,即在图片加载前,该图片占据的空间是一个空白框,而在图片加载后,该空白框变成了真正的图片,这种效果会给用户带来不良的体验。
  • 可能出现重复加载:如果用户快速滚动页面,可能会导致多个图片在短时间内进入可视区域。这个时候,如果使用 getBoundingClientRect 判断这些图片需要懒加载并将它们的 src 属性设置为图片地址,可能会导致这些图片重复加载,这不仅浪费了带宽和时间,也会影响页面的性能。
  • 可能影响 SEO:如果使用懒加载技术,搜索引擎可能无法正确地抓取和索引图片,这可能会对网站的 SEO 产生负面影响。

图片设置 loading 属性

根据caniuse,目前 loading="lazy" 的兼容性如下,已覆盖主流的浏览器,但要注意该属性懒加载图片的不可控性。因此,为了确保图片的兼容性和用户体验,建议在使用 loading="lazy"属性懒加载图片的同时,也使用其他懒加载技术,如 Intersection Observer API。

不可控性(特点):

  1. Lazy loading 加载数量与屏幕高度有关,高度越小加载数量越少,但并不是线性关系
  2. Lazy loading 加载数量与网速有关,网速越慢,加载数量越多,但并不是线性关系
  3. 滚动即会触发图片懒加载,不会说滚动一屏后再去加载
  4. 窗口 resize 尺寸变化也会触发图片懒加载,当屏幕高度从小变大的时候
  5. 根据滚动位置不同,Lazy loading 会忽略头尾的图片请求

直接获取属性

getBoundingClientRect实现懒加载原理差不多,监听滚动和距离判定。参考以下属性:

innerWidth:返回窗口的视口宽度(即浏览器窗口中可见内容的宽度),以像素为单位,包括垂直滚动条。
innerHeight:返回窗口的视口高度(即浏览器窗口中可见内容的高度),以像素为单位,包括水平滚动条。
outerWidth:返回整个浏览器窗口的宽度,包括工具栏和滚动条,以像素为单位。
outerHeight:返回整个浏览器窗口的高度,包括工具栏和滚动条,以像素为单位。
offsetWidth:返回元素的整体宽度,包括元素的边框、内边距和内容宽度,但不包括外边距,以像素为单位。
offsetHeight:返回元素的整体高度,包括元素的边框、内边距和内容高度,但不包括外边距,以像素为单位。
scrollWidth:返回元素内容的实际宽度,包括溢出的部分,但不包括边框和内边距,以像素为单位。
scrollHeight:返回元素内容的实际高度,包括溢出的部分,但不包括边框和内边距,以像素为单位。
scrollLeft:返回元素的水平滚动条位置,以像素为单位。
scrollTop:返回元素的垂直滚动条位置,以像素为单位。
clientWidth: 是指元素的内部宽度,包括 padding 但不包括边框和滚动条的宽度。

# 一些区别
clientWidth 是指元素的内部宽度,包括 padding 但不包括边框和滚动条的宽度。换句话说,它是元素可见的内容区域的宽度。
offsetWidth 是指元素的整个宽度,包括 padding、边框和滚动条(如果有)的宽度。也就是说,它是元素在页面中占据的实际宽度。
scrollWidth 是指元素的滚动内容的宽度,包括未显示出来的部分。如果元素没有溢出,则 scrollWidth 等于 clientWidth。
innerWidth 是指浏览器窗口的内部宽度,即浏览器窗口的可视区域的宽度(不包括工具栏、滚动条等)。
outerWidth 是指浏览器窗口的外部宽度,即浏览器窗口的整个宽度(包括工具栏、滚动条等)。

另外:Next.js 使用 next/image 组件优化图片加载

Next.js 中可以使用 next/image 组件来进行图片加载,它是基于 React 和 HTML5 的 img 元素进行了优化和扩展,提供了许多额外的功能和性能优化。
以下是一些 next/image 组件的优化和特性:

  • 响应式图片:next/image 组件支持根据设备屏幕大小和像素密度自动选择最佳图片尺寸,从而提供更高的图像质量和更快的加载速度。
  • 自动优化:next/image 组件可以根据图像类型、格式和大小自动进行优化,以提高加载速度和节省带宽。
  • 优先级加载:next/image 组件可以在优先级高的图片加载完成之前延迟加载低优先级的图片,从而提高用户体验和性能。
  • 自动格式转换:next/image 组件可以根据浏览器支持的格式和设备像素密度自动转换图片格式,以提高图像质量和加载速度。
  • 预加载:next/image 组件可以在页面加载完成之前预加载图片,从而提高用户体验和性能。

参考

posted @ 2023-03-23 22:30  晨米酱  阅读(231)  评论(0)    收藏  举报