如何检测图像何时加载(通过 props 提供)并在 React 中更改状态?

How to detect when a image is loaded, that is provided via props, and change state in React?

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

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} alt="2"/>;

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

}

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

export default ImageUser;

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

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>
    );
  }
}

与 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>
  );
};

很有用,谢谢。

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

  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>
    );
  }
}```

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

    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>
            );
        }
    }

同样的想法,使用对元素的引用,但使用功能组件和带有打字稿的钩子:

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"
        alt="a placeholder"
        style={loaded ? { display: 'inline-block' } : { display: 'none' }}
      />
    </>
  );
};

我的解决方案:

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




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

CrossFadeImage 有 2 张图像,topbottombottom 堆叠在 top 上,用于显示需要动画的图像,在这种情况下切换时将是 faded-out 的旧图像,

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

CrossFadeImage 将在检测到 props.src 更改时执行以下操作

  • 重置两个 src 以取消任何当前 运行 动画
  • top的src设置为新图像,将bottom的src设置为faded-out下一帧
  • 的当前图像
  • bottom 设置为对 kick-off 过渡透明
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 ${transitionDuration}s ${curve}`
          }}
          onLoad={() => setDisplay(true)}
          src={topSrc}
          alt={alt}
        />
      )}
      {bottomSrc && (
        <img
          style={{
            position: "absolute",
            opacity: bottomOpacity + "%",
            transition: `opacity ${transitionDuration}s ${curve}`
          }}
          src={bottomSrc}
          alt={alt}
        />
      )}
    </div>
  );
};

export default CrossFadeImage;

用法

<CrossFadeImage
  src={image}
  alt="phonee"
  transitionDuration={0.35}
  curve="ease-in-out"
/>

现场演示

这是一个最小的 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} alt="upload" id="image" width={600} />
    </div>
  )
}