怎么监听图片或者背景图片渲染完成

日常开发中我们经常遇到一种场景,就是pc端网站登录页面有一个背景图片,但是设计提供的背景图一般最少都是2倍图,就是图片比较大,但是图片在没有访问过的时候会导致无法一次性渲染出来。这时候产品可能会提出自己的要求,要求图片必须一次性加载出来。。。。这时候该怎么办呢?显然使用onLoad是肯定无法实现上述效果的。

所以我们需要解决的关键问题在于如何监听图片元素是否真正渲染完成,于是乎就想到了采用MutationObserver+requestAnimationFrame的方案来实现。

具体思路如下

tsx 复制代码
import React, { useState, useEffect, useRef } from 'react'

type ImageRenderMonitorProps = {
  children?: React.ReactNode;
};

// 判断是 img 元素还是 div, 主要用来区分是img标签还是背景图片
const isImageElement = (element: HTMLElement): boolean => {
  return (
    element.tagName === 'IMG' ||
    window.getComputedStyle(element).backgroundImage !== 'none' ||
    document.defaultView?.getComputedStyle(element, null).backgroundImage !== 'none'
  )
}

const ImageRenderMonitor: React.FC<ImageRenderMonitorProps> = ({ children }) => {
  const containerRef = useRef<HTMLDivElement>(null)
  const observerRef = useRef<HTMLDivElement>(null)
  const pendingElements = useRef<Set<Element>(new Set())
  const [isShow, setIsShow] = useState<boolean>(false)

  // 获取图片地址
  const getBgUrl = (element: HTMLElement): string | null => {
    const bgImage = window.getComputedStyle(element).backgroundImage
    const match = bgImage.match(/url\(["']?(.*?)["']?\)/)

    return match ? match[1] : null
  }

  // 渲染完成,让图片显示出来
  const confirmRenderComplete = (element: HTMLElement) => {
    // 强制布局计算
    void element.offsetHeight

    element.style.opacity = '1'
    element.style.transition = 'opacity 0.3s ease-in-out'
  }

  // 加载背景图
  const preloadBackgroundImage = (element: HTMLElement): HTMLImageElement | null => {
    const bgUrl = getBgUrl(element)
    if (!bgUrl) return null

    const img = new Image()
    img.src = bgUrl
    return img
  }

  // 处理追加元素
  const handleNewElement = (element: HTMLElement) => {
    element.style.opacity = '0'

    pendingElements.current.add(element)
    
    const isImgTag = element.tagName === 'IMG'
    const img = isImgTag ? (element as HTMLElement) : preloadBackgroundImage(element)

    if (!img) return

    // 加载完成
    const onLoad = () => {
      requestAnimationFrame(() => {
        requestAnimationFrame(() => {
          confirmRenderComplete(element)
        })
      })
    }

    // 加载失败
    const onError = () => {
      pendingElements.current.delete(element)

      // 这里可以处理错误逻辑,比如显示默认图片或者错误文案之类的
    }

    img.addEventListener('load', onLoad)
    img.addEventListener('error', onError)
  }

  useEffect(() => {
    // 触发元素渲染
    setIsShow(true)
    // 初始化 MutationObserver

    const initMutationObserver = () => {
      if (!containerRef.current) return
      observerRef.current = new MutationObserver((mutations) => {
        mutations.forEach((mutation) => {
          mutation.addedNodes.forEach((node) => {
            if (node.nodeType === Node.ELEMENT_NODE) {
              const element = node as HTMLElement
              // 满足条件对元素进行处理
              if (isImageElement(element)) {
                handleNewElement(element)
              }
            }
          })
        })
      })
    }

    initMutationObserver()


    // 卸载监听
    return () => {
      observerRef?.current?.disconnect()
      pendingElements?.current?.forEach((element) => {
        if (element instanceof HTMLElement) {
          element.style.opacity = '1'
        }
      })
      pendingElements?.current?.clear()
    }
  }, [])

  return (
    <div ref={containerRef}
    style={{
      position: 'absolute',
      width: '100%',
      height: '100%'
    }}>
      {isShow && children}
    </div>
  )
}


export default ImageRender

具体使用如下

tsx 复制代码
import ImageRender from 'xxxx/ImageRender'
import bgUrl from 'xxx/bg.png'
// 方式1: 背景图片
<ImageRender>
  <div style={{backgroudImage: url(`${bgUrl}`)}}></div>
</ImageRender>

// 方式2: img元素
<ImageRender>
  <img src={bgUrl} width="100%" alt="" />
</ImageRender>

总结

上述代码中,涉及到了 MutationObserver 的使用和 opacity的使用,有兴趣的小伙伴可以自行想一下为什么要用到这两个属性或者方法。

当然上述只是我的一点思考或者说一种解决方案,供各位参考。相信各位小伙伴经过自己的努力肯定会有更优的解决方案。

相关推荐
excel3 分钟前
Web发展与Vue.js导读
前端
YAY_tyy5 分钟前
Three.js 开发实战教程(五):外部 3D 模型加载与优化实战
前端·javascript·3d·three.js
Zuckjet_3 小时前
开启 3D 之旅 - 你的第一个 WebGL 三角形
前端·javascript·3d·webgl
2401_863801463 小时前
探索 12 种 3D 文件格式:综合指南
前端·3d
珍宝商店5 小时前
前端老旧项目全面性能优化指南与面试攻略
前端·面试·性能优化
bitbitDown5 小时前
四年前端分享给你的高效开发工具库
前端·javascript·vue.js
gnip6 小时前
实现AI对话光标跟随效果
前端·javascript
脑花儿6 小时前
ABAP SMW0下载Excel模板并填充&&剪切板方式粘贴
java·前端·数据库
lumi.7 小时前
Vue.js 从入门到实践1:环境搭建、数据绑定与条件渲染
前端·javascript·vue.js
二十雨辰7 小时前
vue核心原理实现
前端·javascript·vue.js