react.js 替换 img src onerror

react.js Replace img src onerror

我有一个 React 组件,它是列表的详细视图。

如果图像不存在并且出现 404 错误,我正在尝试用默认图像替换该图像。

我通常会在 img 标签中使用 onerror 方法,但这似乎不起作用。

我不确定如何使用 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);
}

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;

您只需要定义 onError 处理程序,而不是更改将触发组件渲染方法的状态,最终组件将使用占位符重新渲染。

请不要同时使用 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;

我接受了@Skay 的回答并创建了一个可重用的图像组件。发帖以防对任何人有帮助:

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;

您可以使用不受控制的组件:

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

我就是这样做的。

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

这最适合我

<img 
  src={record.picture}
  onError={({ currentTarget }) => {
    currentTarget.onerror = null; // prevents looping
    currentTarget.src="image_path_here";
  }}
/>

这对我有用。

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

student是我的数组名,noimage是图片,没有图片的时候显示。

对于像我一样也想改变元素样式的人 and/or 改变 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
  }}
/>

希望对您有所帮助!

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
 }}
/>

这是我目前使用的。

@DepH 的回答很好,但如果您的错误源也未加载,它确实会产生无限循环。这帮助我避免了回调循环:

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

由于没有完美的答案,我发布我使用的片段。我正在使用回退到 fallbackSrc.

的可重用 Image 组件

由于后备图像可能会再次失败并触发重新渲染的无限循环,因此我添加了 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,
};

如果回退图像也失败,Arthur 的回答将导致无限回调。

为避免这种情况,首先在构造函数中将 imageLoadError 的状态设置为 true :

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

然后在 onError 函数中检查此状态值以避免无限回调,

代码将如下所示:-

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

如果符合您的要求,您可以使用 object。像下面这样的东西会工作得很好

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

查看此答案了解更多详情

运行 遇到了类似的问题,我能找到的最佳解决方案是 Georgii Oleinikov 的回答。 (不需要按照 Nitesh 运行jan 在他的回答中建议的那样创建新的 imageLoadError 状态)

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

e.target.onerror = null 不需要(也没有真正帮助),因为 if 条件足以防止无限循环(如果备份图像也无法加载)。

所以:

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

编辑:另一种方法是在 return 括号外设置一个标志并检查 if 语句中的标志。代码应如下所示:

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

这是一个使用钩子的答案:

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} />
}

如果你想处理 src 道具的变化,你可以传递 srckey 道具。 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='...' />

使用像这样的键可能会失败的唯一极端人为设计的边缘情况是兄弟组件。我认为如果它们具有相同的键,则只有一个兄弟节点会呈现。为了解决这个问题,您可以将图像包装在 <> Fragment.

<><Image key={srcProp} ... /></>
<><Image key={srcProp} ... /></>

我是这样写的

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

如果有人将图像 src 与 require 一起使用,则 onError 不起作用 -

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

然后 require 抛出一个错误,我尝试了多种方法并尝试捕获块解决方案作为 -

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

并用作

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

我用上面Arthurs的方法使e.target.onerror = null来停止死循环,但还是死循环了。所以,要停止无限循环,我必须使用下面的 method.I 必须找到实际的 属性 onError 并将其设为 null。

<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

之前的版本有bug;他们不认为 src 可以更改。所以我做了我的最终解决方案并且它:

  1. 支持打字
  2. src 更改时的支持案例
  3. 转发参考
  4. 不忽略 onError(意味着您可以像通常使用 <img /> 那样将 onError 传递给 ImageWithFallback

这里是:

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

即使这是一个老问题,如果您正在寻找一个干净的解决方案,您可以使用 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

正如其中一条评论中提到的,最好的解决方案是使用 react-image 库。当您在构建后尝试提供 React 网站的静态版本时,使用 onError 将失败。

这里是超级简单直接的例子,如何使用react-image,只需导入 Img 组件

import {Img} from 'react-image'

然后指定您尝试加载的 src 列表

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

如果第一个 url 未找到,将加载第二个,还有一些其他非常酷的功能,例如在加载图像时显示微调器或在 none 的情况下显示其他组件列出的图像可用

就这么简单

e.target.onerror = null如果错误图片也加载失败 jsx

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

试试这个自定义图像组件:

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;

2021 更新了使用 React 功能组件、Hook 和 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} />
}


打字稿版本:

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}/>
  },
)

使用 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} />
  },
)

借助@emil 上面的解决方案 我创建了这个小功能组件。它在第一次错误时使用后备 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

用法...

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

我使用 TypeScript 扩展了@Emils 解决方案并添加了

  • 加载时支持占位符
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}
            />
        );
    }
}

对于 SSR(服务器端渲染)...

所以,这里有一个解决方法(对我来说)!

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={...}
    />
  );
};

所以,魔术发生在两个 useEffect 钩子中。 (只使用一个是行不通的)。 基本上,由于 hasRendered dep,第二个 useEffect 确保第二次触发(或组件重新渲染)第一个挂钩(或组件重新渲染),然后强制设置图像 src在该挂钩中,然后触发客户端上的事件!