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

问题描述 投票:0回答:11

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

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;
javascript image reactjs loading state
11个回答
99
投票

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

JSBin 演示

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

39
投票

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

14
投票

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

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

4
投票

您可以更进一步,在更改图像时添加淡入过渡。下面的代码是我的

CrossFadeImage
组件。只需复制并使用它来代替普通的
img
组件即可。

CrossFadeImage
有 2 个图像,
top
bottom
bottom
堆叠在
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 ${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"
/>

现场演示

Edit demo app on CodeSandbox


2
投票

https://stackoverflow.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>
    );
  }
}```

2
投票

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

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

1
投票

顺风接受答案

const [isImageLoaded, setIsImageLoaded] = useState(false)     

{!isImageLoaded && <img width={30} src='/images/spinner.svg' />}

        <img
          className={`mx-4 ${!isImageLoaded && 'hidden'}`}
          width={30}
          src="imageUrl"
          onLoad={() => setIsImageLoaded(true)}
        />

0
投票

这是一个最小的 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>
  )
}

0
投票

我只想补充一件事。接受的答案很好。但是当 src 在 props 中发生变化时,它不会显示加载组件。要处理 props 更改,您可以在类组件中实现 componentDidUpdate 并在功能组件中实现 useEffect。

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

  componentDidUpdate(prevProps){
    if(prevProps.src!==this.props.src){
      this.setState({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>
    );
  }
}

或者,如果您想显示加载图像或错误图像,则可以使用 npm 包“simple-react-image”。只需使用安装即可

npm 我简单的反应图像

然后使用它。另外,您可以查看示例here

import React from 'react';
import { Image as Img } from 'simple-react-image';

class Foo extends React.Component {
  render(){
    return (
      <div>
        <Img
          errorImage="https://www.freeiconspng.com/thumbs/error-icon/error-icon-32.png" //image in case of error
          fallback="https://i.gifer.com/ZZ5H.gif"// image in case of loading
          src={this.props.src}
          onStateChange={(imageState)=>{
            this.setState({imageState});//can be loading,loaded,error
          }}
        />
      </div>
    );
  }
}

0
投票

这是 @Brigand 的修改版本,已经使用 React State Hook 进行了修改:

const imageWithLoading = ({ data }) => { const [loaded, setLoaded] = useState(false) return ( {loaded ? null : <p>Change this to loading component..</p> } <img src={data.img.path} style={loaded ? {} : { display: 'none' }} onLoad={() => setLoaded(true)} /> ) }
    

-1
投票
我的解决方案:

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
    
© www.soinside.com 2019 - 2024. All rights reserved.