简体   繁体   English

react.js 替换 img src onerror

[英]react.js Replace img src onerror

I have a react component that is the detail view from a list.我有一个反应组件,它是列表中的详细视图。

I am trying to replace the image with a default image if the image does not exist and there is a 404 error.如果图像不存在并且出现 404 错误,我正在尝试将图像替换为默认图像。

I would normally use the onerror method in the img tag but that doesn't seem to be working.我通常会在 img 标签中使用 onerror 方法,但这似乎不起作用。

I am not sure how to do this with react.我不确定如何通过反应来做到这一点。

Here is my component:这是我的组件:

import React from 'react';
import {Link} from 'react-router';
import ContactStore from '../stores/ContactStore'
import ContactActions from '../actions/ContactActions';

class Contact extends React.Component {
  constructor(props) {
    super(props);
    this.state = ContactStore.getState();
    this.onChange = this.onChange.bind(this); 
 }

componentDidMount() {
  ContactStore.listen(this.onChange);
  ContactActions.getContact(this.props.params.id);
}

componentWillUnmount() {
  ContactStore.unlisten(this.onChange);
}

componentDidUpdate(prevProps) {
  if (prevProps.params.id !== this.props.params.id) {
    ContactActions.getContact(this.props.params.id);
  }
}

onChange(state) {
  this.setState(state);
}

render() {
  return (
    <div className='container'>
      <div className='list-group'>
        <div className='list-group-item animated fadeIn'>
          <h4>{this.state.contact.displayname}</h4>
          <img src={this.state.imageUrl} />
        </div>
      </div>
    </div>
  );
}
}

export default Contact;

这对我最有效

<img src={record.picture} onError={(e)=>{e.target.onerror = null; e.target.src="image_path_here"}}/>

Since there is no perfect answer, I am posting the snippet I use.由于没有完美的答案,我发布了我使用的代码段。 I am using reusable Image component that fallbacks to fallbackSrc .我正在使用回fallbackSrc可重用Image组件。

Since the fallback image could fail again and trigger infinite loop of re-rendering, I added errored state.由于后备图像可能再次失败并触发重新渲染的无限循环,因此我添加了errored状态。

 import React, { Component } from 'react'; import PropTypes from 'prop-types'; class Image extends Component { constructor(props) { super(props); this.state = { src: props.src, errored: false, }; } onError = () => { if (!this.state.errored) { this.setState({ src: this.props.fallbackSrc, errored: true, }); } } render() { const { src } = this.state; const { src: _1, fallbackSrc: _2, ...props } = this.props; return ( <img src={src} onError={this.onError} {...props} /> ); } } Image.propTypes = { src: PropTypes.string, fallbackSrc: PropTypes.string, };

You can use uncontrolled component:您可以使用不受控制的组件:

<img src={this.state.img} ref={img => this.img = img} onError={
    () => this.img.src = 'img/default.img'
}>

You need just define onError handler than change the state which will trigger component render method and eventually component will re-render with placeholder.您只需要定义 onError 处理程序而不是更改将触发组件渲染方法并最终组件将使用占位符重新渲染的状态。

Please, don't use jQuery and React together!请不要同时使用 jQuery 和 React!

import React from 'react';
import {Link} from 'react-router';
import ContactStore from '../stores/ContactStore'
import ContactActions from '../actions/ContactActions';

class Contact extends React.Component {
  constructor(props) {
    super(props);
    this.state = ContactStore.getState();
    this.onChange = this.onChange.bind(this); 
 }

componentDidMount() {
  ContactStore.listen(this.onChange);
  ContactActions.getContact(this.props.params.id);
}

componentWillUnmount() {
  ContactStore.unlisten(this.onChange);
}

componentDidUpdate(prevProps) {
  if (prevProps.params.id !== this.props.params.id) {
    ContactActions.getContact(this.props.params.id);
  }
}

onChange(state) {
  this.setState(state);
}

onError() {
  this.setState({
    imageUrl: "img/default.png"
  })
}

render() {
  return (
    <div className='container'>
      <div className='list-group'>
        <div className='list-group-item animated fadeIn'>
          <h4>{this.state.contact.displayname}</h4>
          <img onError={this.onError.bind(this)} src={this.state.imageUrl} />
        </div>
      </div>
    </div>
  );
}

export default Contact;

Arthur's answer will result in infinite callbacks if fallback image also fails.如果后备图像也失败,亚瑟的回答将导致无限回调。

To avoid that, first set a state in the constructor for imageLoadError as true :为了避免这种情况,首先在构造函数中为 imageLoadError 设置一个状态为 true :

constructor(props) {
    super(props);
    this.state = {
      imageLoadError: true,
    };
}

and then check for this state value in onError function to avoid infinite callbacks,然后在onError函数中检查这个状态值以避免无限回调,

the code will look like this :-代码将如下所示:-

<img
    src={"https://if_this_url_fails_go_to_onError"}
    onError={e => { 
        if(this.state.imageLoadError) { 
            this.setState({
                imageLoadError: false
            });
            e.target.src = 'fallbackImage.png';
        }
    }}
/>

2021 Updated Answer using React Functional Components, Hooks and TypeScript 2021 使用 React Functional Components、Hooks 和 TypeScript 更新答案

// ImageWithFallback.tsx
import React, { ImgHTMLAttributes, useState } from 'react'

interface Props extends ImgHTMLAttributes<any> {
  fallback: string
}

export default function ImageWithFallback({ fallback, src, ...props }: Props) {
  const [imgSrc, setImgSrc] = useState<string | undefined>(src)
  const onError = () => setImgSrc(fallback)

  return <img src={imgSrc ? imgSrc : fallback} onError={onError} {...props} />
}


@DepH's answer is nice, but it does produce and infinite loop if your error source also doesn't load. @DepH 的回答很好,但如果您的错误源也没有加载,它确实会产生无限循环。 This helped me avoid the callback loop:这帮助我避免了回调循环:

onError={(e)=>{ if (e.target.src !== "image_path_here") 
    { e.target.onerror = null; e.target.src="image_path_here"; } }}

Its So Simple它如此简单

e.target.onerror = null If Error Image Also Fails to Load jsx e.target.onerror = null如果错误图片也无法加载jsx

<img 
   src={imageSrc}
   onError={(e) => (e.target.onerror = null, e.target.src = imageErrorSrc)}/>

Ran into a similar problem and the best solution i could find was Georgii Oleinikov 's answer.遇到了类似的问题,我能找到的最佳解决方案是Georgii Oleinikov的答案。 (Doesn't require making new imageLoadError state as suggested by Nitesh Ranjan in his answer) (不需要按照Nitesh Ranjan在他的回答中的建议创建新的imageLoadError状态)

onError={(e)=>{ if (e.target.src !== "image_path_here"){
                    e.target.onerror = null;
                     e.target.src="image_path_here";}
                }
           }

e.target.onerror = null is not needed (and doesn't really help) because the if condition is enough to prevent the infinite loop(if backup image fails to load as well). e.target.onerror = null不是必需的(并且没有真正的帮助),因为 if 条件足以防止无限循环(如果备份图像也无法加载)。

So:所以:

onError={(e)=>{ if (e.target.src !== "image_path_here"){
                 e.target.src="image_path_here";}
               }
         }

EDIT: The other way around is to set a flag outside the return brackets and check for the flag in the if statement.编辑:另一种方法是在返回括号外设置一个标志并检查 if 语句中的标志。 Code should look something like this:代码应如下所示:

render(){
 let errorflag=true;
 return(
            <img alt='' src={imageUrl} 
                    onError={(e)=>{ if (errorflag){ errorflag=false; e.target.src=url; } }} />
            );
} 

Here's an answer using hooks:这是使用钩子的答案:

import React, { useState } from 'react'

/**
 * Returns an object that can 
 * be spread onto an img tag
 * @param {String} img
 * @param {String} fallback
 * @returns {Object} { src: String, onError: Func }
*/
function useFallbackImg(img, fallback) {
  const [src, setImg] = useState(img)

  function onError(e) {
    console.log('Missing img', img, e)
    // React bails out of hook renders if the state
    // is the same as the previous state, otherwise
    // fallback erroring out would cause an infinite loop
    setImg(fallback)
  }

  return { src, onError }
}

/**
 * Usage <Image src='someUrl' fallback='fallbackUrl' alt='something' />
 */
function Image({src, fallback, ...rest}) {

  const imgProps = useFallbackImg(src, fallback)

  return <img {...imgProps} {...rest} />
}

And if you are want to handle the src prop changing, you can pass a key prop of the src .如果你想处理src prop 的变化,你可以传递srckey prop。 https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#recommendation-fully-uncontrolled-component-with-a-key https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#recommendation-fully-uncontrolled-component-with-a-key

<Image key='someUrl' src='someUrl' fallback='fallbackUrl' alt='...' />

The only extreme contrived edge case where using a key like this might fail is with sibling components.使用这样的键可能会失败的唯一极端人为的边缘情况是使用同级组件。 I think only one sibling node will render if they have the same key.我认为如果它们具有相同的键,则只会呈现一个兄弟节点。 To get around this you could probably wrap the Image in a <> Fragment .为了解决这个问题,您可以将 Image 包装在<> Fragment

<><Image key={srcProp} ... /></>
<><Image key={srcProp} ... /></>
import OriginalImage from '../../originalImg.png'
import ReplacementImage from '../../replaceImg.png'

<img
 src= OriginalImage
 alt="example"
 onError={(e) => {
    e.target.src = ReplacementImage //replacement image imported above
    e.target.style = 'padding: 8px; margin: 16px' // inline styles in html format
 }}
/>

this is what I'm currently using.这是我目前正在使用的。

I took @Skay's answer and created a reusable Image component.我接受了@Skay 的回答并创建了一个可重用的 Image 组件。 Posting in case it helps anyone:发布以防它对任何人有帮助:

 import React, { PropTypes } from 'react'; const Image = ({src, fallbackSrc, ...other}) => { let element; const changeSrc = newSrc => { element.src = newSrc; }; return ( <img src={src} onError={() => changeSrc(fallbackSrc)} ref={el => element=el} {...other} /> ); }; Image.propTypes = { src: PropTypes.string, fallbackSrc: PropTypes.string }; export default Image;

I wrote like this.我是这样写的。

import React, { useState } from 'react';
import NoImageSVG from './noImage.svg';

const ImgWithFallback: React.FunctionComponent<{ src: string; alt: string; className: string }> = ({
  src,
  alt,
  className,
}) => {
  const [isUndefined, updateIsUndefined] = useState(false);

  const onError = () => {
    updateIsUndefined(true);
  };

  if (isUndefined) {
    return (
      <div className={className}>
        <NoImageSVG width='5rem' height='5rem' />
      </div>
    );
  }

  return <img src={src} alt={alt} className={className} onError={onError} />;
};

export default React.memo(ImgWithFallback, () => true);

For those like me who also wanted to change the styles of the element and/or change the img source, just do something like this:对于像我一样想要更改元素样式和/或更改 img 源的人,只需执行以下操作:

<img
  src={'original src url goes here'}
  alt="example"
  onError={(e) => {
     e.target.src = '/example/noimage.png' // some replacement image
     e.target.style = 'padding: 8px; margin: 16px' // inline styles in html format
  }}
/>

Hope it helps!希望能帮助到你!

I used the above method of Arthurs making e.target.onerror = null to stop the infinite loop , but still the infinite loop happened.我使用了上面Arthurs的方法让e.target.onerror = null停止无限循环,但仍然发生了无限循环。 So , to stop the infinite loop I had to use the below method.I had to find the actual property onError and make it null.所以,为了停止无限循环,我必须使用下面的方法。我必须找到实际的属性 onError 并将其设为空。

<img src={imageSource}
     onError={(e) => { 
              e.target[Object.keys(e.target).filter(prop=>prop.includes('EventHandler'))[0]].onError = null;
              e.target.src = 'images/avatar.png'; }}
 />

event.target properties event.target 属性

Even though this is an old question if you are looking of a clean solution you can use react-image-fallback library.即使这是一个老问题,如果您正在寻找一个干净的解决方案,您也可以使用react-image-fallback库。

<ReactImageFallback
                    src="my-image.png"
                    fallbackImage="my-backup.png"
                    initialImage="loader.gif"
                    alt="cool image should be here"
                    className="my-image" />

react-image-fallback反应图像回退

That's how I did it.我就是这样做的。

 class Pix extends React.Component{

          constructor(props){
            super(props);
           this.state={link: this.props.link};
           this.onError=this.onError.bind(this);
          }


          onError(){
              console.log("error: could not find picture");
              this.setState(function(){ return {link: "missing.png"}; });
             };

          render(){
          return <img onError={this.onError} src={this.state.link}/>;
          } 
    }

You can use object if that's ok with your requirement.如果您的要求没问题,您可以使用object Something like below will work perfectly fine像下面这样的东西将工作得很好

<object data={expected_image} type="image/jpg">
  <img src={DEFAULT} alt="404" />
</object>

Check this answer for more details https://stackoverflow.com/a/29111371/1334182检查此答案以获取更多详细信息https://stackoverflow.com/a/29111371/1334182

If anyone is using image src with require then onError doesn't work as -如果有人使用带有 require 的图像 src,则 onError 不起作用 -

<img src={require(`./../../assets/images/${props.imgName}.png`)} className="card-img" alt={props.name} />

then require throws an error, where I tried multiple ways and came to try and catch block solution as -然后 require 抛出一个错误,我尝试了多种方法并尝试捕获块解决方案 -

  let imgSrc;
  try {
    imgSrc = require(`./../../assets/images/${props.imgName}.png`);  
  } catch {
    imgSrc = require(`./../../assets/images/default.png`);
  }

and use as并用作

<img src={imgSrc} className="card-img" alt={props.name} />

Previous versions have the bug;以前的版本有这个bug; they don't count that src could be changed.他们不认为src可以改变。 So I made my ultimate solution and it:所以我做了我的最终解决方案,它:

  1. Supports typing支持打字
  2. Support case when src is changed更改src时的支持案例
  3. Forwards ref转发参考
  4. Doesn't ignore onError (means you can pass onError to ImageWithFallback like you usually do with <img /> )不忽略onError (意味着您可以像通常使用<img />那样将onError传递给ImageWithFallback

Here it is:这里是:

import React, { useState, useCallback, useEffect } from 'react';
import noImage from 'src/svg/no-image.svg';

export const ImageWithFallback = React.forwardRef(
  (
    {
      onError,
      ...props
    }: React.DetailedHTMLProps<
      React.ImgHTMLAttributes<HTMLImageElement>,
      HTMLImageElement
    >,
    ref: React.Ref<HTMLImageElement>,
  ) => {
    const [imageLoadFailed, setImageLoadFailed] = useState<boolean>(false);

    const handleError = useCallback(
      (e: React.SyntheticEvent<HTMLImageElement, Event>) => {
        if (imageLoadFailed) return;
        setImageLoadFailed(true); // to avoid infinite loop
        if (onError) {
          onError(e);
        }
      },
      [imageLoadFailed, setImageLoadFailed, onError],
    );

    useEffect(() => {
      setImageLoadFailed(false); // in case `src` is changed
    }, [props.src]);

    return (
      <img
        {...props}
        src={imageLoadFailed ? noImage : props.src}
        onError={handleError}
        ref={ref}
      />
    );
  },
);

As it was mentioned in one of the comments, the best solution is to use react-image library.正如其中一条评论中提到的,最好的解决方案是使用react-image库。 Using onError will fail when you try to serve static version of your react website after build.当您尝试在构建后提供 React 网站的静态版本时,使用onError将失败。

Here is super simple and straightforward example how to use react-image , just import Img component这是如何使用react-image 的超级简单直接的示例,只需导入 Img 组件

import {Img} from 'react-image'

And later specify a list of src that you try to load然后指定您尝试加载的 src 列表

<Img
   src={['images/image1.svg', 'images/default.svg']}
   alt="Some title"
/>   

If 1st url not found, the 2nd will be loaded, there are also some other pretty cool features like showing a spinner while image is loading or displaying some other component in case none of the listed images are available如果未找到第一个 url,则将加载第二个,还有一些其他非常酷的功能,例如在加载图像时显示微调器或在没有列出的图像可用时显示其他组件

Try this custom Image component:试试这个自定义图像组件:

import React, { useRef } from 'react';
import PropTypes from 'prop-types';

import defaultErrorImage from 'assets/images/default-placeholder-image.png';

const Image = ({ src, alt, className, onErrorImage }) => {
  const imageEl = useRef(null);
  return (
    <img
      src={src}
      alt={alt}
      className={className}
      onError={() => {
        imageEl.current.src = onErrorImage;
      }}
      ref={imageEl}
    />
  );
};

Image.defaultProps = {
  onErrorImage: defaultErrorImage,
};

Image.propTypes = {
  src: PropTypes.string.isRequired,
  alt: PropTypes.string.isRequired,
  className: PropTypes.string.isRequired,
  onErrorImage: PropTypes.string,
};

export default Image;

Typescript version:打字稿版本:

const Avatar = (): JSX.Element => {
    function imageErrorHandler(e: React.SyntheticEvent<HTMLImageElement, Event>) {
      const el = e.target as HTMLImageElement
      el.onerror = null
      el.src = '/fallback.png'
    }

    return <img src={'/smth.png'} onError={imageErrorHandler}/>
  },
)

With forwardRef and possible null src:使用 forwardRef 和可能的 null src:

import { forwardRef } from 'react'

type Props = Omit<React.ComponentPropsWithoutRef<'img'>, 'src'> & { src?: null | string }

const Avatar = forwardRef<HTMLImageElement, Props>(
  ({ src, ...rest }, ref): JSX.Element => {
    function imageErrorHandler(e: React.SyntheticEvent<HTMLImageElement, Event>) {
      const el = e.target as HTMLImageElement
      el.onerror = null
      el.src = '/fallback.png'
    }

    return <img src={src || '/alternative.png'} onError={imageErrorHandler} ref={ref} {...rest} />
  },
)

With the help of @emil's solution above I created this little functional component.在上面@emil 解决方案的帮助下,我创建了这个小功能组件。 It's using a fallback src on First error and removing the img on second error, from fallback src.它在第一个错误时使用后备 src,并从后备 src 中删除第二个错误时的 img。

import React, { useState } from 'react'

function ImageFallback({ src, fallbackSrc, ...props }) {

    const [state, setState] = useState({ src: src, errored: false })
   

    //update(next img) state onMount 
    useEffect(() => {
       setState({
           src: src,
           errored: false,
       })

    }, [src])

   //update (remove) state onUnMount
   useEffect(() => {
       return () => {
           setState({
               src: null,
               errored: false,
           })
       }
   }, [])

    const onError = () => {
        //1st error
        if (!state.errored) {
            setState({
                src: fallbackSrc,
                errored: true,
            });
        } else if (state.errored && state.src) {
            //2nd error
            //when error on fallbacksrc - remove src
            setState({
                src: null,
                errored: true,
            });
        }

    }

    return (
        state.src && <img
            src={state.src}
            onError={onError}
            {...props}
        />
    )
}

export default ImageFallback

Usage ...用法 ...

 <ImageFallback src={anySrc} fallbackSrc={anyFallbackSrc} className={classes.logo} alt='' />

I expanded @Emils solution using TypeScript and added我使用 TypeScript 扩展了 @Emils 解决方案并添加了

  • placeholder support while loading加载时占位符支持
import * as React from "react";

type Props = {
    src: string,
    fallbackSrc: string,
    placeholderColor?: string,
    className?: string,
}

type State = {
    src: string,
    errored: boolean,
    loaded: boolean
}

export default class Image extends React.Component<Props, State> {
    constructor(props: Props) {
        super(props);

        this.state = {
            src: props.src,
            errored: false,
            loaded: false
        };
    }

    onError = () => {
        if (!this.state.errored) {
            this.setState({
                src: this.props.fallbackSrc,
                errored: true,
            });
        }
    }

    onLoad = () => {
        if(!this.state.loaded){
            this.setState({loaded: true});
        }
    }

    render() {
        let style = {
            backgroundColor: this.props?.placeholderColor || "white"
        };

        if(this.state.loaded){
            style.backgroundColor = "transparent";
        }

        return (
            <img
                style={style}
                onLoad={this.onLoad}
                onError={this.onError}
                {...this.props}
                src={this.state.src}
            />
        );
    }
}

For SSR (Server Side Rendering)...对于 SSR(服务器端渲染)...

So, here's a workaround that works (for me)!所以,这是一个有效的解决方法(对我来说)!

const Img: FC<
  DetailedHTMLProps<ImgHTMLAttributes<HTMLImageElement>, HTMLImageElement>
> = ({ src, ...props }): JSX.Element => {
  const [hasRendered, setHasRendered] = useState(false);
  const imgRef = useRef<HTMLImageElement | null>(null);

  useEffect(() => {
    if (imgRef.current && hasRendered) {
      imgRef.current!.src = src || '';
    }
  }, [src, hasRendered]);

  useEffect(() => {
    setHasRendered(true);
  }, []);

  return (
    <img
      {...props}
      ref={imgRef as any}
      alt={props.alt || 'image'}
      aria-hidden={true}
      onError={...}
      onLoad={...}
    />
  );
};

So, the magic happens in the two useEffect hooks.所以,神奇发生在两个useEffect钩子中。 (Using just one didn't work). (仅使用一个不起作用)。 Basically, the second useEffect ensures the first hook is triggered (or component re-renders) a second time (after initial render), due to the hasRendered dep, which then forces the image src to be set in that hook which then triggers the events on the client!基本上,由于hasRendered dep,第二个useEffect确保第二次触发(或组件重新渲染)第一个钩子(在初始渲染之后),然后强制图像 src 设置在该钩子中,然后触发事件在客户端!

2022 Aug Updated Answer 2022 年 8 月 更新答案

el.target.onerror = null; //Not worked for me also not prevents looping.

The best solution is setting errcount attribute to the element.最好的解决方案是将 errcount 属性设置为元素。

Then check if attribute is set, no need to set src again.然后检查是否设置了属性,无需再次设置src。

also you can add logic to retry for 1,2,3...n times.您也可以添加逻辑以重试 1,2,3...n 次。

<img onError={this.addDefaultSrc} src={BaseURL + item.ImageUrl} />

addDefaultSrc(el) {
  var errcount = parseInt(el.target.getAttribute("errcount"));
  if (isNaN(errcount)) {
    el.target.setAttribute("errcount", "1");
    el.target.src = BaseURL + 'Images/no-image.jpg';
  }
  //else
  //el.target.src = "";
}

this works for me .这对我有用。

{<img className="images"
    src={`/images/${student.src ? student.src : "noimage.png" }`} alt=  
{student.firstname} />} 

student is the name of my array and noimage the image, when there is no image is display. student 是我的数组的名称, noimage 图像,当没有图像时显示。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM