如何检测何时加载通过道具提供的图像并更改 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 谢谢,像<div style= backgroundImage: 'url($url)' />
这样的背景图片呢?【参考方案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 张图片,top
和 bottom
。 bottom
叠加在top
上,用于显示需要动画的图片,这里是切换时会淡出的旧图,
在空闲状态下,top
显示当前图像,而bottom
是上一个图像但透明
CrossFadeImage
在检测到props.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 中的状态?的主要内容,如果未能解决你的问题,请参考以下文章