20行代码,封装一个 React 图片懒加载组件

简介: 20行代码,封装一个 React 图片懒加载组件

图片懒加载是我们在做性能优化时非常重要的手段。我们常常需要图片在进入页面可视区域时,才让加载图片的行为发生。


1.如何判断图片进入视口


我们可以使用传统的方式,监听页面的 scroll 事件,然后调用目标函数的 getBoundingClientRect 方法,得到目标元素在网页中的位置信息。


但是我并不喜欢监听 scroll 事件。因为他会大量的执行,并且 getBoundingClientRect 是一个同步方法,都在主线程上运行,当其频繁执行时可能会导致性能出现问题。


我们可以使用另外一种方式来做到同样的效果。他就是 IntersectionObserver

var observer = new IntersectionObserver(callback[, option])

IntersectionObserver 提供了一种异步观察目标元素与其祖先元素或者顶级文档 viewport 交叉状态的方法。其祖先元素或者视口,被称为根 root。当目标元素与根元素在视图上产生交集时,回调函数就会执行。


我们也可以在 options 中,自定义配置 root 元素

let options = {
  root: document.querySelector("#scrollArea"),
  rootMargin: "0px",
  threshold: 1.0,
};
let observer = new IntersectionObserver(callback, options);

options 接受三个参数


root


自定义目标元素的根节点。该节点必须是目标元素的祖先元素。如果未指定,默认为视口。


rootMargin


根元素周围的边距。其值可以类似于 CSS 的 margin 属性,例如 10px 20px 30px 40px,以此表示上、右、下、左。这些值夜可以是百分比。在计算交叉点之前,这组值用于增大或者缩小根元素边框的每一侧,默认为 0.


threshold


一个数字或者一组数字。表示目标可见度达到多少百分比时,回调函数就应该执行。例如,如果我希望交叉部分每超过目标元素 25% 就执行,那么我就传入 [0, 0.25, 0.5, 0.75, 1]. 默认值为 0


创建的实例有 4 个方法可以让我们使用。


  • observer.disconnect() 停止监听
  • observer.observe(element) 开始监听目标元素
  • observer.takRecords() 返回所有目标元素的信息对象数组
  • observer.unobserve(element)停止监听目标元素


回调函数执行时,接收一个参数,该参数为回调函数提供目标对象的位置信息,一共有六个属性

{
  // 回调执行的时间
  time: 3893.92,
  // 被观察的目标对象
  target: element
  // 根元素位置信息
  rootBounds: ClientRect {
    bottom: 920,
    height: 1024,
    left: 0,
    right: 1024,
    top: 0,
    width: 920
  },
  // 目标元素位置信息
  boundingClientRect: ClientRect {
     // ...
  },
  // 交叉区域矩形的位置大小信息
  intersectionRect: ClientRect {
    // ...
  },
  // 元素可见度比例
  intersectionRatio: 0.54,
}

该参数返回一个数组包含一个或者多个元素的位置信息。


2.图片懒加载原理


在浏览器中,展示一张图片,我们使用的是 img 标签。img 标签有一个必须传入的属性 src,当我们不传入 src 时,图片无法加载,一旦传入 src,那么图片就会立即开始加载


因此,我们需要做的事情就是,当图片没有出现在可视区域时,不传入正确的 src 属性,当通过上述的方法判断图片已经出现在可视区域,我们就传入正确的 src,此时图片会立即加载


3.代码实现


首先,我们封装的新组件,一定要继承原有 img 标签的所有能力。先定义一个 Props 类型声明,目前我们并不需要扩展其他的属性,暂时先这样,未来会根据需求的变动逐渐新增新的属性值。

interface LazyLoadProps extends DetailedHTMLProps<ImgHTMLAttributes<HTMLImageElement>, HTMLImageElement> {
}

然后我们我要的事情是,除了 src 属性,我们要把其他属性全部直接传给 img 标签,具体方式如下

export default function LazyLoad(props: LazyLoadProps) {
  const {src = '', ...other} = props
  return (
    <img {...other} />
  )
}

然后我们需要定义一个 ref 属性,用于获取 img 标签的元素对象

export default function LazyLoad(props: LazyLoadProps) {
  const {src = '', ...other} = props
  const img = useRef(null)
  return (
    <img ref={img} {...other} />
  )
}

准备工作做好之后,我们最后只需要借助 useEffect 声明 IntersectionObserver 实例然后监听图片元素即可

export default function LazyLoad(props: LazyLoadProps) {
  const {src = '', ...other} = props
  const [URL, setURL] = useState('')
  const img = useRef(null)
  useEffect(() => {
    var io = new IntersectionObserver((entries) => {
      // @ts-ignore
      if (entries[0].intersectionRatio > 0) {
        setURL(src)
        img.current && io.unobserve(img.current)
      }
    }, {})   
    if (img.current) {
      io.observe(img.current)
    }
  }, [])
  return (
    <img ref={img} src={URL} {...other} />
  )
}

这样,一个满足基本要求的图片懒加载组件就封装好了。


4.扩展思考


在我们做首屏优化的时候,为了能够达到最快的速度渲染页面,图片的加载往往也需要延后,但是又不能延后太多。因此此时的问题是,图片已经出现在可视区域了,我们又应该如何做才能做到懒加载呢?


在实践中可能还会遇到的需求变动是,给图片添加一个占位符。然后占位符元素与图片元素的切换不是立即发生的,而是要等到我们确保图片已经全部加载完成之后才发生的,否则的话,可能会出现图片只加载了一小半的视图情况。这又应该如何实现


继续优化。我们希望占位符元素与图片元素的切换没那么生硬,而是结合动画渐入渐出,又该如何实现。


继续优化,我们希望支持传入 aspectFill 等属性,确保图片的缩放比例,不能因为宽高的设置导致图片比例变形,又该如何实现


这些思考就留给大家在实践中去尝试验证了。本文就不在详细介绍。

相关文章
|
2月前
|
缓存 前端开发 数据安全/隐私保护
如何使用组合组件和高阶组件实现复杂的 React 应用程序?
如何使用组合组件和高阶组件实现复杂的 React 应用程序?
171 68
|
2月前
|
缓存 前端开发 Java
在 React 中,组合组件和高阶组件在性能方面有何区别?
在 React 中,组合组件和高阶组件在性能方面有何区别?
154 67
|
2月前
|
前端开发 JavaScript 安全
除了高阶组件和render props,还有哪些在 React 中实现代码复用的方法?
除了高阶组件和render props,还有哪些在 React 中实现代码复用的方法?
162 62
|
4月前
|
移动开发 前端开发 JavaScript
React音频播放列表组件:常见问题、易错点与解决方案
本文介绍了在React中实现音频播放列表时常见的挑战及解决方案。通过基础实现、常见问题分析和最佳实践,帮助开发者避免状态管理、生命周期控制和事件处理中的陷阱。关键点包括使用`useRef`操作音频元素、`useState`同步播放状态、全局状态管理防止多音频同时播放、以及通过`useEffect`清理资源。还提供了代码示例和跨浏览器兼容性处理方法,确保高效实现功能并减少调试时间。
165 30
|
4月前
|
移动开发 前端开发 UED
React 音频音量控制组件 Audio Volume Control
在现代Web应用中,音频播放功能不可或缺。React以其声明式编程和组件化开发模式,非常适合构建复杂的音频音量控制组件。本文介绍了如何使用HTML5 `&lt;audio&gt;`元素与React结合,实现直观的音量控制系统,并解决了常见问题如音量范围不合理、初始音量设置及性能优化等,帮助开发者打造优秀的音频播放器。
156 27
|
4月前
|
编解码 前端开发 开发者
React 图片组件样式自定义:常见问题与解决方案
在 React 开发中,图片组件的样式自定义常因细节问题导致布局错乱、性能损耗或交互异常。本文系统梳理常见问题及解决方案,涵盖基础样式应用、响应式设计、加载状态与性能优化等,结合代码案例帮助开发者高效实现图片组件的样式控制。重点解决图片尺寸不匹配、边框阴影不一致、移动端显示模糊、加载失败处理及懒加载等问题,并总结易错点和最佳实践,助力开发者提升开发效率和用户体验。
144 22
|
4月前
|
移动开发 前端开发 UED
React 音频进度条组件 Audio Progress Bar
在现代Web开发中,音频播放功能不可或缺。使用React构建音频进度条组件,不仅能实现播放控制和拖动跳转,还能确保代码的可维护性和复用性。本文介绍了如何利用HTML5 `&lt;audio&gt;`标签的基础功能、解决获取音频时长和当前时间的问题、动态更新进度条样式,并避免常见错误如忘记移除事件监听器和忽略跨浏览器兼容性。通过这些方法,开发者可以打造高质量的音频播放器,提升用户体验。
134 6
|
5月前
|
移动开发 前端开发 API
React 音频播放器组件 Audio Player
本文介绍如何使用React创建音频播放器组件,涵盖核心功能如播放/暂停、进度条、音量控制和时间显示。通过HTML5 `&lt;audio&gt;` 元素和React的声明式状态管理,实现交互式音频播放。常见问题包括控件不响应、进度条无法更新和音量控制失灵,并提供解决方案。此外,还讨论了浏览器兼容性、异步错误处理和性能优化等易错点及避免方法。
457 123
|
4月前
|
前端开发 JavaScript
除了使用Route组件,React Router还有其他方式处理404错误页面吗
除了使用Route组件,React Router还有其他方式处理404错误页面吗
126 58
|
4月前
|
前端开发
React 中高阶组件的原理是什么?
React 中高阶组件的原理是什么?
152 57