如何检测何时加载通过道具提供的图像并更改 React 中的状态?

Posted

技术标签:

【中文标题】如何检测何时加载通过道具提供的图像并更改 React 中的状态?【英文标题】:How to detect when a image is loaded, that is provided via props, and change state in React? 【发布时间】:2017-08-24 05:22:42 【问题描述】:

我想在加载最终头像图像时加载不同的图像(假头像)。这个想法是检测道具图像何时加载并更改状态。可能吗?一些想法?谢谢!

class ImageUser extends React.Component 

constructor(props) 
    super(props);
    this.state = userImageLoaded: false;
    let imageSrc = "";

    if (!this.props.userImage) 
        imageSrc = this.props.noUserImage;
     else 
        imageSrc = this.props.userImage;
    

    this.loadingImage = <img className=styles.imageUser
                     src=this.props.loadingImage />;

    this.userImage =
        <img onLoad=this.setState(userImageLoaded: true)
             className=styles.imageUser src=imageSrc
             />;



render() 
    let image = "";
    if (this.state.userImageLoaded) 
        image = this.userImage;
     else 
        image = this.loadingImage;
    
    return (
        <div>
            image
        </div>
    );



export default ImageUser;

【问题讨论】:

【参考方案1】:

有几种方法可以做到这一点,但最简单的是显示隐藏的最终图像,然后在加载后将其翻转为可见。

JSBin Demo

class Foo extends React.Component 
  constructor()
    super();
    this.state = loaded: false;
  

  render()
    return (
      <div>
        this.state.loaded ? null :
          <div
            style=
              background: 'red',
              height: '400px',
              width: '400px',
            
          />
        
        <img
          style=this.state.loaded ?  : display: 'none'
          src=this.props.src
          onLoad=() => this.setState(loaded: true)
        />
      </div>
    );
  

【讨论】:

这正是我想要的。这是最简单的方法。等待组件还有另一个问题,但主题不同:) 我们如何创建这样的循环加载多个图像 @Ranjith 您将存储 id/src 值到布尔值的映射,指示它们是否已加载。 @Ranjith,您可以创建一个单独的“图像”组件,使其具有自己的状态。那么您就不必担心 key/id 映射了。 @FakeRainBrigand 谢谢,像&lt;div style= backgroundImage: 'url($url)' /&gt; 这样的背景图片呢?【参考方案2】:

与 Brigand 接受的答案相同,但带有 Hooks:

const Foo = ( src ) => 
  const [loaded, setLoaded] = useState(false);

  return (
    <div>
      loaded ? null : (
        <div
          style=
            background: 'red',
            height: '400px',
            width: '400px'
          
        />
      )
      <img
        style=loaded ?  :  display: 'none' 
        src=src
        onLoad=() => setLoaded(true)
      />
    </div>
  );
;

【讨论】:

【参考方案3】:

https://***.com/a/43115422/9536897 很有用,谢谢。

我想加强你并添加 背景图片

  constructor()
    super();
    this.state = loaded: false;
  

  render()
    return (
      <div>
        this.state.loaded ? null :
          <div
            style=
              background: 'red',
              height: '400px',
              width: '400px',
            
          />
        
        <img
          style= display: 'none' 
          src=this.props.src
          onLoad=() => this.setState(loaded: true)
        />
       <div 
         style= 
                  background: `url($this.props.src)`
                   ,display: this.state.loaded?'none':'block'
                
        />
      </div>
    );
  
```

【讨论】:

【参考方案4】:

检测图像何时加载的更好方法是创建对该元素的引用,然后将事件侦听器添加到该引用。您可以避免在元素中添加事件处理程序代码,并使代码更易于阅读,如下所示:

    class Foo extends React.Component 
        constructor()
            super();
            this.state = loaded: false;
            this.imageRef = React.createRef();
        

        componentDidMount() 
            this.imageRef.current.addEventListener('load', onImageLoad);
        

        onImageLoad = () =>  
            this.setState(loaded: true)
        

        render()
            return (
              <div>
                this.state.loaded ? null :
                  <div
                    style=
                      background: 'red',
                      height: '400px',
                      width: '400px',
                    
                  />
                
                <img
                  ref=this.imageRef
                  style= display: 'none' 
                  src=this.props.src
                />
                <div 
                  style=
                      background: `url($this.props.src)`
                      ,display: this.state.loaded?'none':'block'
                  
                />
              </div>
            );
        
    

【讨论】:

【参考方案5】:

使用对元素的引用但使用功能组件和带有 typescript 的钩子的想法相同:

import React from 'react';

export const Thumbnail = () => 
  const imgEl = React.useRef<htmlImageElement>(null);
  const [loaded, setLoaded] = React.useState(false);

  const onImageLoaded = () => setLoaded(true);

  React.useEffect(() => 
    const imgElCurrent = imgEl.current;

    if (imgElCurrent) 
      imgElCurrent.addEventListener('load', onImageLoaded);
      return () => imgElCurrent.removeEventListener('load', onImageLoaded);
    
  , [imgEl]);

  return (
    <>
      <p style=!loaded ?  display: 'block'  :  display: 'none' >
        Loading...
      </p>
      <img
        ref=imgEl
        src="https://via.placeholder.com/60"
        
        style=loaded ?  display: 'inline-block'  :  display: 'none' 
      />
    </>
  );
;

【讨论】:

【参考方案6】:

我的解决方案:

import React, FC,useState,useEffect from "react"

interface ILoadingImg 
    url:string,
    classOk?:string,
    classError?:string,
    classLoading?:string



const LoadingImg: FC<ILoadingImg> = (
                                         url,
                                         classOk,
                                         classError,
                                         classLoading
                                      ) => 


    const [isLoad,setIsLoad] = useState<boolean>(false)

    const [error,setError] = useState<string|undefined>(undefined)




    useEffect(() =>

        const image = new Image()

        image.onerror = () =>
            setError(`error loading $url`)
            setIsLoad( false)
        ;

        image.onload = function() 

         
                setIsLoad( true)
        

/*
//and you can get the image data


            imgData = 
                                src: this.src,
                                width:this.width,
                                height:this.height
                                

 */


        

        image.src = url




       return () =>  setIsLoad(false)

    ,[url])



    if(!isLoad)
        return <div className=classLoading>Loading...</div>
    

    if(error)
        return <div className=classError>error</div>
    


    return <img  src=url className=classOk  />



export default LoadingImg




【讨论】:

请在您的回答中说明您所做的更改以及您是如何解决问题的。【参考方案7】:

您可以更进一步,在更改图像时添加淡入过渡。下面的代码是我的CrossFadeImage 组件。只需复制并使用它,而不是普通的img 组件。

CrossFadeImage 有 2 张图片,topbottombottom叠加在top上,用于显示需要动画的图片,这里是切换时会淡出的旧图,

在空闲状态下,top 显示当前图像,而bottom 是上一个图像但透明

CrossFadeImage 在检测到props.src 变化时会做以下事情

重置两个 src 以取消任何当前正在运行的动画 将top的src设置为新图像,将bottom的src设置为将在下一帧淡出的当前图像 将bottom 设置为透明以启动过渡
import React from "react";

const usePrevious = <T extends any>(value: T) => 
  const ref = React.useRef<T>();
  React.useEffect(() => 
    ref.current = value;
  , [value]);
  return ref.current;
;
const useRequestAnimationFrame = (): [(cb: () => void) => void, Function] => 
  const handles = React.useRef<number[]>([]);
  const _raf = (cb: () => void) => 
    handles.current.push(requestAnimationFrame(cb));
  ;
  const _resetRaf = () => 
    handles.current.forEach((id) => cancelAnimationFrame(id));
    handles.current = [];
  ;

  return [_raf, _resetRaf];
;

type ImageProps = 
  src: string;
  alt?: string;
  transitionDuration?: number;
  curve?: string;
;

const CrossFadeImage = (props: ImageProps) => 
  const  src, alt, transitionDuration = 0.35, curve = "ease"  = props;
  const oldSrc = usePrevious(src);
  const [topSrc, setTopSrc] = React.useState<string>(src);
  const [bottomSrc, setBottomSrc] = React.useState<string>("");
  const [bottomOpacity, setBottomOpacity] = React.useState(0);
  const [display, setDisplay] = React.useState(false);
  const [raf, resetRaf] = useRequestAnimationFrame();

  React.useEffect(() => 
    if (src !== oldSrc) 
      resetRaf();
      setTopSrc("");
      setBottomSrc("");

      raf(() => 
        setTopSrc(src);
        setBottomSrc(oldSrc!);
        setBottomOpacity(99);

        raf(() => 
          setBottomOpacity(0);
        );
      );
    
  );

  return (
    <div
      className="imgContainer"
      style=
        position: "relative",
        height: "100%"
      
    >
      topSrc && (
        <img
          style=
            position: "absolute",
            opacity: display ? "100%" : 0,
            transition: `opacity $transitionDurations $curve`
          
          onLoad=() => setDisplay(true)
          src=topSrc
          alt=alt
        />
      )
      bottomSrc && (
        <img
          style=
            position: "absolute",
            opacity: bottomOpacity + "%",
            transition: `opacity $transitionDurations $curve`
          
          src=bottomSrc
          alt=alt
        />
      )
    </div>
  );
;

export default CrossFadeImage;

用法

<CrossFadeImage
  src=image
  
  transitionDuration=0.35
  curve="ease-in-out"
/>

现场演示

【讨论】:

【参考方案8】:

这是一个最小的 React 示例,它以 React 徽标开头并将其替换为上传的图像 -

import React from 'react'
import logo from './logo.svg'
import './App.css'


export default function App() 

  function loadImage(event) 
    const file = event.target.files && event.target.files[0]
    if (file) 
      const img = document.querySelector("#image")
      img.onload = () => window.URL.revokeObjectURL(img.src) // free memory
      img.src = window.URL.createObjectURL(file)
    
  

  return (
    <div className="App">
      <input type="file" id="inputfile" accept=".jpg" onChange=loadImage />
      <br/><br/>
      <img src=logo  id="image" width=600 />
    </div>
  )

【讨论】:

以上是关于如何检测何时加载通过道具提供的图像并更改 React 中的状态?的主要内容,如果未能解决你的问题,请参考以下文章

检测 iframe 何时开始加载新 URL

Android webview如何检测何时重新加载

如何检测表视图中的项目何时更改?

Android:如何通过通知下拉菜单检测设置何时更改?

如何检测变量何时更改值

通过 jQuery 检测图像加载