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

14

我目前正在将一个使用ReactJS进行客户端渲染的应用程序转换为NextJS应用程序,以提高搜索引擎优化和社交媒体链接的效果。

转换后的其中一个组件实际上是一张图片,等待它加载完成后淡入显示,但在NextJS环境中使用时没有达到预期效果。

它的行为如下:

缓存已启用:

  • 第一次加载图像时,onLoad事件会触发并显示图像。
  • 第二次加载图像时,由于从缓存加载图像时不会触发onLoad事件,因此图像仍然隐藏。

使用devtools禁用缓存:

  • onLoad事件始终有效,因为图像从未从缓存中加载。

预期行为及以前仅使用ReactJS实现的行为:

  • 无论图像是否从缓存中加载,都应触发onLoad事件。

当不使用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

有没有任何想法可以指引我朝正确的方向前进? - Mohamed Seif Khalid
4个回答

20

感谢某人提供的建议,我最终使用了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

4
我知道仅仅表达感激之情而没有其他内容是违反规定的,但非常感谢你。你真是我的救星。<3 - John Miller

3
从 Next.js v11.0.2-canary.4 开始,用户可以直接使用 onLoadingComplete 属性。
<Image src={src} onLoadingComplete={() => setLoaded(true)} />

这个选项和其他选项相比如何?

  • 您可以继续使用令人惊叹的next/image组件,而无需任何额外代码或自己处理引用,而不是使用未经优化的img标记。

1
我只想补充一下 @mohamed-seif-khalid 的回答,我不得不在 useEffect 中添加一个检查,以确保 ref 包含任何内容,因为我的渲染仍然会出现问题。
  useEffect(() => {
    if (image.current && image.current.complete) {
      handleImageLoad()
    }
  }, []);

对于视频,可以使用const isLoaded = videoRef.current.readyState === 4;来完成相同的操作。 - Ryan Soderberg

1
如果您正在使用 nextjs 10 中的新 Image 组件,该组件不支持 forward ref,您可以通过修改以下解决方法来解决问题。
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>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接