在同构/通用React中,图片的onLoad事件-在图片加载完成后注册事件

43

在同构渲染页面中,图片可以在主script.js文件之前被下载。因此,在react注册onLoad事件之前,图像可能已经被加载,从而永远不会触发此事件。

script.js

constructor(props) {
    super(props);
    this.handleImageLoaded = this.handleImageLoaded.bind(this);
}

handleImageLoaded() {
    console.log('image loaded');
} 

render() {
    return (
        <img src='image.jpg' onLoad={this.handleImageLoaded} />
    );
}

场景1 - image.jpgscript.js

image.jpg is bigger than script.js

在这种情况下一切都正常。事件在图像最终加载之前注册,因此控制台中会显示image loaded消息。


场景2 - image.jpgscript.js

image.jpg is smaller than script.js

在这个场景中您可以看到本文开头描述的问题。 onLoad事件没有被触发。


问题

我应该怎么做才能在场景2中触发onLoad事件?


编辑:Soviut的回答实现

为了检测图像在呈现时是否已就绪,您应该检查纯JavaScript img对象上的complete属性:

constructor(props) {
    super(props);
    this.state = { loaded: false };
    this.handleImageLoaded = this.handleImageLoaded.bind(this);
    this.image = React.createRef();
}

componentDidMount() {
    const img = this.image.current;
    if (img && img.complete) {
        this.handleImageLoaded();
    }
}

handleImageLoaded() {
    if (!this.state.loaded) {
        console.log('image loaded');
        this.setState({ loaded: true });
    }
} 

render() {
    return (
        <img src='image.jpg' ref={this.image} onLoad={this.handleImageLoaded} />
    );
}

除非脚本被加载到DOM中,否则浏览器如何知道需要获取图像? - Pranesh Ravi
这是一个同构应用程序。我使用 node 在后端渲染此 HTML,并带有图像元素。 - Everettss
这个并没有在 React 加载之前处理图片的方式吗? - pailhead
1
@pailhead 不是的,如果图像比React事件更快,只有在componentDidMount上才会触发事件。 - Everettss
我也不明白为什么 onload 从未触发……甚至没想到这个可能性,即通过脚本的 jsx 渲染的图像,这个脚本应该首先加载,可能在图像本身之后加载,直到我记得我正在使用 gatsby 构建静态 HTML 文件。 - eballeste
@eballeste你也在使用Gatsby吗?你能否把ref传递给Gatsby Image组件中的内部<img>呢? - James
5个回答

27

在应用 onload 事件之前,您可以检查图像上的 complete 属性。

if (!img.complete) {
    // add onload listener here
}

看起来很有希望。你能否在我的 script.js 中更新答案,加入此语句 if (!img.complete) - Everettss
我宁愿保持这个例子简单。只需在if语句中包装this.handleImageLoaded = this.handleImageLoaded.bind(this);,当然首先要获取img标签。 - Soviut

25

使用钩子使得所有内容都更加整洁:


const useImageLoaded = () => {
  const [loaded, setLoaded] = useState(false)
  const ref = useRef()

  const onLoad = () => {
    setLoaded(true)
  }

  useEffect(() => {
    if (ref.current && ref.current.complete) {
      onLoad()
    }
  })

  return [ref, loaded, onLoad]
}

const SomeComponent = ({ src }) => {
  const [ref, loaded, onLoad] = useImageLoaded()

  return (
    <div>
      <img ref={ref} onLoad={onLoad} src={src} alt="" />
      {loaded && <h1>Loaded!</h1>}
    </div>
  )
}

14

另一种方法是使用ref,涵盖这两种情况:

<img
  ref={(input) => {
    // onLoad replacement for SSR
    if (!input) { return; }
    const img = input;

    const updateFunc = () => {
      this.setState({ loaded: true });
    };
    img.onload = updateFunc;
    if (img.complete) {
      updateFunc();
    }
  }}
  src={imgSrc}
  alt={imgAlt}
/>

请在函数末尾添加 img.onload = null,以避免在未挂载的组件上设置状态。 - Kingsley CA

10

即使src加载失败,img.complete也是true。

complete - 返回一个布尔值,如果浏览器已经完成图像的获取,无论成功与否,都为true。如果图像没有src值,它也会显示为true。

  1. 使用naturalWidth来确定img是否成功加载
    state = {
        isLoading: true,
        hasError: false,
    }

    myRef = React.createRef();

    componentDidMount() {
        const img = this.myRef.current;

        if (img && img.complete) {
            if (img.naturalWidth === 0) {
                this.handleOnError();
            } else {
                this.handleImageLoaded();
            }
        }
    }

    handleImageLoaded = () => {
        if (this.state.isLoading) {
            this.setState({ isLoading: false });
        }
    }

    handleOnError = () => {
        this.setState({ hasError: true });
    }

    render() {
        return (
            <img
                src={src}
                alt={alt}
                ref={this.myRef}
                onError={this.handleOnError}
                onLoad={this.handleOnLoad}
            />
        );
    }
  1. 另一种方法是在componentDidMount中添加“check”图片并为其设置eventHandler,让check图片处理eventListeners。
componentDidMount() {
   const testImg = new Image();
   testImg.onerror = this.handleOnError;
   testImg.onload = this.handleImageLoaded;
   testImg.src = this.props.src; // important to set eventlisteners before src
}


请注意,对于选项1(naturalWidth),在Firefox上存在有关svg的错误,这会导致自然宽度始终为0,从而触发handleOnError方法,即使它本来可以成功。经过2个小时的调试后,我不得不自己找出这个问题。 - s.r.

-3
<img
    src={this.props.imageUrl}
    onLoad={this.handleImageLoaded.bind(this)}
    onError={this.handleImageErrored.bind(this)}
/>

3
如果图像加载得比这个函数注册要快,我就不会工作。我已经在问题中解释了为什么这段代码无法工作。 - Everettss
@Everettss 我相信脚本将首先被执行,然后调用渲染方法,因此图像将在此之后添加到DOM中,并且onload始终会在render方法之后调用。因此,我认为您不需要更多的代码。 - Amit Dhaka
@Everettss 请阅读 此帖子 中 jfriend00 的评论。 - Amit Dhaka
@AmitDhaka 如果您在回答中包含解释而不仅仅是代码,那将会很好。我相信您的错误假设来自于误解同构渲染的工作原理。我正在我的博客上使用这个.complete方法 - 检查页面源代码,您将在任何React脚本准备就绪之前看到带有<img />标签的HTML。 - Everettss

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