最近项目上有一个需求,在显示图片的时候,需要传递自定义的头部就行认证。google了一番之后,发现没有现成的组件库可以使用【也可能是我没找到】,所以请求图片只能采用xhr方式来异步加载。下面就是在做这个组件库时的一些笔记,主要关注以下两个点:

图片的缩放处理

最开始想到的是使用CSS 属性background来显示图片,后来发现使用CSS的background-size实现按照比例缩放图片好像有点困难,具体如下:

因为要取到图片的原始尺寸,使用img标签显示也会有点问题。所以最终采用的是new Image()这个Web Api来创建的图片。具体代码如下:

export const getImage = (src: string) => (
  new Promise((resolve, reject) => {
    const image = new Image();
    image.onload = () => resolve(image);
    image.onerror = () => reject(new Error(NETWORK_ERROR));
    image.src = src;
    image.crossOrigin = '';
    return image;
  })
);

同时,图片缩放的部分代码如下:

if (ratio > 1) {
    if (imageWidth > wrapperWidth) {
      displayWidth = wrapperWidth;
      displayHeight = parseInt(`${(1 / ratio) * wrapperWidth}`, 10);
    }
  } else if (ratio === 1) {
    if (imageWidth > wrapperWidth) {
      displayWidth = wrapperWidth;
      displayHeight = wrapperWidth;
    } else {
      displayWidth = wrapperHeight;
      displayHeight = wrapperHeight;
    }
  } else if (imageHeight > wrapperHeight) {
    displayWidth = parseInt(`${ratio * wrapperHeight}`, 10);
    displayHeight = wrapperHeight;
  }

图片的覆盖问题

因为需要进行头部的认证,所以请求图片的方式统一使用了XHR的方式来进行请求,然后就会造成图片覆盖的问题。造成这个原因是,当出现了图片地址替换的时候,比如类似下面的代码:

const [src, setSrc] = useState(src1);
useEffect(() => { setTimeout(() => setSrc(src2)); }, [src]);

return (
  <div className="App">
    <Image width={50} height={100} src={src} errorMessage="something bad happen" />
  </div>
);

上述代码中的src2会后被加载,如果src1的加载速度比src2的加载速度快倒没有什么问题,但是反之,就会出现后加载的图片反而被先加载的图片进行覆盖。那么,怎么解决这个问题:

想到的办法是,当开始加载后一个图片时,首先进行判断是否存在上一个加载图片的请求,如果存在,则直接abort,类似于debounce的做法。具体的做法如下:

总结

看是简单的问题,做起来也会比较复杂,口说的没用,做起来才行。

最后,项目地址:https://github.com/Rynxiao/react-image,npm包地址:https://www.npmjs.com/package/rt-image,欢迎留言和star