NextJS:从缓存中加载的图像不会触发 onLoad 事件

NextJS: Images loaded from cache don't trigger the onLoad event

出于搜索引擎优化和社交媒体链接的原因,我目前正在将 ReactJS 客户端呈现的应用程序转换为 NextJS 应用程序。

其中一个转换的组件(基本上是一个等待加载完成然后淡入的图像)在 NextJS 环境中使用后无法按预期工作。

它的行为方式如下:

启用缓存:

使用 devtools 禁用缓存:

预期行为和之前仅使用 ReactJS 实现的行为:

当不使用 React 时,这个问题通常是由于有人在定义 onload 函数之前设置图像 src 引起的:

let img = new Image()
img.src = "img.jpg"
img.onload = () => console.log("Image loaded.")

应该是:

let img = new Image()
img.onload = () => console.log("Image loaded.")
img.src = "img.jpg"

这是在 NextJS 中导致相同问题的简化代码:

import React, { useState } from "react"

const Home = () => {
    const [loaded, setLoaded] = useState(false)

    const homeStyles = {
        width: "100%",
        height: "96vh",
        backgroundColor: "black"
    }

    const imgStyles = {
        width: "100%",
        height: "100%",
        objectFit: "cover",
        opacity: loaded ? 1 : 0
    }

    const handleLoad = () => {
        console.log("Loaded")
        setLoaded(true)
    }

    return (
        <div className="Home" style={homeStyles}>
            <img alt=""
                onLoad={handleLoad}
                src="https://images.unsplash.com/photo-1558981001-5864b3250a69?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1350&q=80"
                style={imgStyles}
            />
        </div>
    )
}

export default Home

由于某人的建议,我最终使用 ImageObject.complete 作为解决方法。

我使用 useRef 来引用图像并检查组件安装上是否有 image.current.complete === true

代码如下:

import React, { useEffect, useRef, useState } from "react"

const Home = () => {
    const [loaded, setLoaded] = useState(false)

    const image = useRef()

    const homeStyles = {
        width: "100%",
        height: "96vh",
        backgroundColor: "black"
    }

    const imgStyles = {
        width: "100%",
        height: "100%",
        objectFit: "cover",
        opacity: loaded ? 1 : 0
    }

    const handleLoad = () => setLoaded(true)

    useEffect(() => {
        if (image.current.complete) setLoaded(true)
    }, [])

    return (
        <div className="Home" style={homeStyles}>
            <img alt=""
                ref={image}
                onLoad={handleLoad}
                src="https://images.unsplash.com/photo-1558981001-5864b3250a69?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1350&q=80"
                style={imgStyles}
            />
        </div>
    )
}

export default Home

如果您使用的是来自 nextjs 10 的新 Image 组件,它不支持前向引用,您可以使用

修改解决方法
const [loaded, setLoaded] = useState(false)
const ref = useRef<HTMLDivElement>(null)
useEffect(() => {
  if ((ref.current?.firstChild?.firstChild as HTMLImageElement | undefined)?.complete) {
    setLoaded(true)
  }
}, [])

return <div ref={ref}>
         <Image src={src} 
           onLoad={() => setLoaded(true)}/>
       </div>

来自 Next.js v11.0.2-canary.4 onwards, one can directly use onLoadingComplete prop.

<Image src={src} onLoadingComplete={() => setLoaded(true)} />

这与其他选项相比如何?

  • 您可以继续使用很棒的 next/image 组件而不是未优化的 img 标签,无需任何额外代码或自己处理 refs。

我只想添加到 @mohamed-seif-khalid 的回答中,如果 ref 包含任何内容,我必须在 useEffect 中添加一个检查,因为我的渲染继续中断。

  useEffect(() => {
    if (image.current && image.current.complete) {
      handleImageLoad()
    }
  }, []);