React:在图片加载时显示加载图标。

28

我有一个React应用程序,在useEffect中调用我的 API ,该API返回要用作图像src的URL列表。

我正在使用react-loader-spinner显示加载动画组件,同时加载我的图像。

我在useState中有一个loading变量,以确定图像是否正在加载。

我无法弄清楚如何停止显示加载动画并在所有图像加载完成后显示它们。

这是我的代码:

Photos.jsx

import React, { useState, useEffect, Fragment } from 'react'
import Loader from 'react-loader-spinner';
import { getAllImages } from '../../services/media.service';
import Photo from '../common/Photo';

const Photos = () => {
  const [photos, setPhotos] = useState([]);
  const [loading, setLoading] = useState(false);

  useEffect(() => {
      setLoading(true);
      getAllImages()
        .then(results => {
          setPhotos(results.data)
          console.log(results.data)
        })
        .catch(err =>{
          console.log(err)
        })
  }, [])

  const handleLoading = () => {
    setLoading(false)
  }

  return ( 
    <Fragment>
      <div className="photos">
          { loading ? 
            <Fragment>
              <Loader
                height="100"    
                width="100"
              />
              <div>Loading Joe's life...</div>
            </Fragment>
            :
            photos.map((photo, index) => (
                index !== photos.length - 1 ? 
                <Photo src={photo.src} key={photo.id} /> :
                <Photo src={photo.src} key={photo.id} handleLoad={handleLoading}/>
            ))
          }
      </div>
    </Fragment>
   );
}

export default Photos;

Photo.jsx

import React from 'react'

import './Photo.css';

const Photo = (props) => {
  return ( 
    <div className="photo">
      <img src={props.src} alt={props.alt} onLoad={props.handleLoad}/>
      <div className="caption">
        Photo caption
      </div>
    </div>
   );
}

export default Photo;

我尝试在最后一个项目中使用onLoad,但它永远不会被调用,因为loading由于旋转器仍在显示而没有被设置回false。

希望能得到一些帮助。谢谢。


@Sagivb.g - 它从未被触及 - blueprintchris
这不是由于 Photo 组件,而是因为 loading 永远不会变成 false。 - blueprintchris
看起来你没有传递它。你正在调用一个没有返回表达式的函数,所以基本上你正在传递 undefined - Sagiv b.g
尝试在你的 useEffect 中设置 [loading],否则它只会在下一次渲染时重置,或者为什么不将默认值设置为 true,并在你的 useEffect 中删除 setLoading,因为这样做没有任何理由导致重新渲染。 - Keith
还有一些需要注意的地方,假设你地图中的最后一张图片将是最后被加载的图片,这有点天真 :) - Keith
显示剩余6条评论
3个回答

44

onLoad事件没有被调用的原因是,您从未将图像加载到DOM中。因此,不要有条件地渲染,而是有条件地设置display属性为none和block。

以下是一个简单的示例,演示如何等待所有图片加载完成。

可以安全地假设文件大小最大的图像最可能是最后加载的。

绝不是这样!图像加载所需的时间并不总是与其大小成正比,缓存或服务器负载也会影响加载速度。

const {useState, useEffect, useRef} = React;

const urls = [
  "https://placeimg.com/100/100/1" ,
  "https://placeimg.com/100/100/2" ,
  "https://placeimg.com/100/100/3"
];

function Test() {
  const [loading, setLoading] = useState(true);
  const counter = useRef(0);
  const imageLoaded = () => {
    counter.current += 1;
    if (counter.current >= urls.length) {
      setLoading(false);
    }
  }
  return <React.Fragment>
    <div style={{display: loading ? "block" : "none"}}>
       Loading images,
    </div>
    <div style={{display: loading ? "none" : "block"}}>
      {urls.map(url => 
        <img 
          key={url}
          src={url}
          onLoad={imageLoaded}/>)}
    </div>
  </React.Fragment>;
}

ReactDOM.render(<React.Fragment>
  <Test/>
</React.Fragment>, document.querySelector('#mount'));
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="mount"></div>


另外一个问题,可以使用条件渲染解决吗? - blueprintchris
@blueprintchris 你可以这样做,但需要更多的工作。你可以使用fetch API来加载图片。https://developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API - Keith
@blueprintchris 当然。请尝试:{ loading && <div>正在加载图片</div> } - Denys Rusov
1
你的回答帮助我实现了在加载图像时的骨架。谢谢。 - Luis Febro
1
美丽 - Alex Cory

4
只需创建这样的组件并在任何需要使用图像的地方加载即可:

import React from 'react'
import { useState } from 'react';

export default function MyImage({src, width, size}) {
    const [loading, setLoading] = useState(true);
    return (
    <div style={
        {
            display: "flex",
            justifyContent: "center",
            alignItems: "center",
            width: width?width:"100%",
        }
    } >
    <img src={src} style={
        {
            display: loading?"none":"block",
            width:"100%",
            animation: "fadeIn 0.5s",
        }
    } onLoad={(e)=>{setLoading(false)}}></img>
    <div className="spinner" style={{
        display: loading?"block":"none",
        fontSize: size?size:"24px"
    }} ></div>
</div>)}

如果你想要更多的UI效果,你只需要为spinner定义一个名为"spinner"的CSS类,并为图像定义一个名为"fadeIn"的动画。

用法:

<MyImage src="link/to/image" />

0

他们怎么样?谢谢你的帮助。我已将其作为Keith的解决方案,但它仍然无法调用onLoad函数。唯一有效且能触发onLoad的方法是使用img父标签上的visibility而不是display。我希望它能与display属性一起工作。

<div style={{visibility: loading ? "hidden" : "visible"}}>
  {urls.map(url => 
    <img 
      key={url}
      src={url}
      onLoad={imageLoaded}/>)}
</div>

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