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

  • 图片的等比例缩放处理
  • 在请求图片的过程中,由于是异步加载,如果后加载的一个图片太小,而前一个图片过大,就会有图片显示不正确的问题

图片的缩放处理

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

  • 如果图片原始的尺寸小于外层容器的尺寸,我希望它居中显示
  • 如果图片原始尺寸大于外层尺寸
    • 如果ratio > 1 (imageWidth / imageHeight),图片应该按照宽度来进行缩放
    • 如果ratio = 1, 图片等比例缩放
    • 如果ratio < 1, 图片按照高度来缩放

因为要取到图片的原始尺寸,使用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的做法。具体的做法如下:

  • 声明一个图片请求的类,专门用来作图片请求

    export default class ImageRequest {
    xmlHttpRequest: XMLHttpRequest;
    url: string;
    headers: XMLHttpRequestHeaders; setHeaders() {
    if (this.headers) {
    const keys = Object.keys(this.headers);
    keys.forEach((key: string) => {
    this.xmlHttpRequest.setRequestHeader(key, this.headers[key]);
    });
    }
    } request(url: string, headers: XMLHttpRequestHeaders) {
    this.url = url;
    this.headers = headers; if (this.xmlHttpRequest) {
    this.xmlHttpRequest.abort();
    } this.xmlHttpRequest = new XMLHttpRequest();
    this.xmlHttpRequest.open('GET', this.url);
    this.xmlHttpRequest.responseType = 'blob';
    this.setHeaders();
    this.xmlHttpRequest.send(); return new Promise((resolve, reject) => {
    this.xmlHttpRequest.onload = () => {
    this.xmlHttpRequest = null;
    if (this.xmlHttpRequest.status === 200) {
    resolve(this.xmlHttpRequest.response);
    } else {
    reject(new Error(`${IMAGE_LOAD_ERROR}${this.xmlHttpRequest.statusText}`));
    }
    }; this.xmlHttpRequest.onerror = () => {
    reject(new Error(NETWORK_ERROR));
    };
    });
    }
    }

    在每个实例中维持一个XMLHttpRequest的引用,每当进行请求的时候,首先判断当前引用是否存在,如果存在,则直接abort,否则,则进行图片的请求。同时在组件中,需要创建一个实例

    // 记住,不能在组件外部声明实例,需要保存在每一个组件中,确保每一个组件都有一个新的请求实例
    // const imageRequest: ImageRequest = new ImageRequest(); const Image: React.FC<Props> = (props) => {
    const [request] = useState<ImageRequest>(new ImageRequest()); useEffect(() => {
    if (src) {
    setState(LOADING_STATE.LOADING);
    loadImage(request, src, headers).then((img: HTMLImageElement) => {
    const { displayWidth, displayHeight } = getDisplayImageSize(img, width, height);
    const displayImage = img;
    displayImage.width = displayWidth;
    displayImage.height = displayHeight;
    setState({ ...LOADING_STATE.SUCCESS, image: displayImage });
    }).catch(() => setState(LOADING_STATE.FAIL));
    }
    }, [loadImage, src]); // ...
    };

    注意,这里的ImageRequest实例只能保存在组件的state中,因为如果在组件开始使用const引入,如果一个页面中存在多个相同组件时,就会导致多个组件共享一个request实例中的xmlHttpRequest引用,就会出现前面的图片全部都会被abort掉的情况。

总结

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

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

做一个单纯的react-image显示组件的更多相关文章

  1. 使用React并做一个简单的to-do-list

    1. 前言 说到React,我从一年之前就开始试着了解并且看了相关的入门教程,而且还买过一本<React:引领未来的用户界面开发框架 >拜读.React的轻量组件化的思想及其virtual ...

  2. 4-13 Webpacker-React.js; 用React做一个下拉表格的功能: <详解>

    Rails5.1增加了Webpacker: Webpacker essentially is the decisions made by the Rails team and bundled up i ...

  3. 用vue的抽象组件来做一个防止img标签url为空或url地址出错的验证

    看了网上文章学习了下vue的抽象组件,感觉就跟react的高阶组件一样的使用场景,只是更加面向vue的底层编程 ,网上介绍的抽象组件一般有2种用法,1 用来加防抖和节流 2 用来控制按钮是否允许点击做 ...

  4. react实例之todo,做一个实时响应的列表操作

    react实例之todo, 做一个实时响应的列表操作 在所有的mvc框架中,最常见的例子不是hello world,而是todo,由于reactjs的简单性,在不引用flux和redux的情况下,我们 ...

  5. VUE2.0+VUE-Router做一个图片上传预览的组件

    之前发了一篇关于自己看待前端组件化的文章,但是由于学习和实践的业务逻辑差异,所以自己练习的一些demo逻辑比较简单,打算用vue重构现在公司做的项目,所以在一些小的功能页面上使用vue来做的,现在写的 ...

  6. 利用 React 高阶组件实现一个面包屑导航

    什么是 React 高阶组件 React 高阶组件就是以高阶函数的方式包裹需要修饰的 React 组件,并返回处理完成后的 React 组件.React 高阶组件在 React 生态中使用的非常频繁, ...

  7. 手把手做一个基于vue-cli的组件库(下篇)

    基于vue-cli4的ui组件库,上篇:如何做一个初步的组件.下篇:编写说明文档及页面优化.接上篇,开工. GitHub源码地址:https://github.com/sq-github/sq-ui ...

  8. 用struts2标签如何从数据库获取数据并在查询页面显示。最近做一个小项目,需要用到struts2标签从数据库查询数据,并且用迭代器iterator标签在查询页面显示,可是一开始,怎么也获取不到数据,想了许久,最后发现,是自己少定义了一个变量,也就是var变量。

    最近做一个小项目,需要用到struts2标签从数据库查询数据,并且用迭代器iterator标签在查询页面显示,可是一开始,怎么也获取不到数据,想了许久,最后发现,是自己少定义了一个变量,也就是var变 ...

  9. 用jQuery做一个三级菜单,鼠标移动到二级菜单的选项上,然后再迅速离开后,当鼠标再移动到该一级菜单或其他二级菜单选项,三级菜单也会显示。

    用jQuery做一个三级菜单,鼠标移动到二级菜单的选项上,然后再迅速离开后,当鼠标再移动到该一级菜单或其他二级菜单选项,三级菜单也会显示. 原因:在为一个元素绑定hover事件之后,用户把光标移入元素 ...

  10. Android UI组件----用相对布局RelativeLayout做一个登陆界面

    [声明] 欢迎转载,但请保留文章原始出处→_→ 生命壹号:http://www.cnblogs.com/smyhvae/ 文章来源:http://www.cnblogs.com/smyhvae/p/3 ...

随机推荐

  1. virtualbox Ubuntn配置多站点

    1.编辑站点文件: nano /etc/nginx/sites-available/default cd /etc/nginx/sites-available/  ls2. 把default的设置文件 ...

  2. 🔥httpsok-v1.11.0支持OSS证书自动部署

    httpsok-v1.11.0支持OSS证书自动部署 介绍 httpsok 是一个便捷的 HTTPS 证书自动续签工具,专为 Nginx .OpenResty 服务器设计.已服务众多中小企业,稳定.安 ...

  3. 用 C 语言开发一门编程语言 — 跨平台的可移植性

    目录 文章目录 目录 前文列表 实现跨平台的可移植性 使用预处理器指令 前文列表 <用 C 语言开发一门编程语言 - 交互式解析器l> 实现跨平台的可移植性 理想情况下,我希望我的代码可以 ...

  4. 在 Inno Setup 中检测 .NET 5 / .NET Core 运行环境是否存在

    为了将 .NET 5 / .NET Core 应用程序部署到客户机,我们可以编写 Inno Setup 代码来判断客户机是否安装了必要的运行环境..NET 官方仓库 中提供了一个名为 NetCoreC ...

  5. WPF多显示器问题 - WindowState

    标签 wpf multiple-monitors 一段时间以来,我一直试图让我的 WPF 应用程序跨越多个监视器,并且几乎可以正常工作.当我设置以下行时,问题似乎出现了: win1.WindowSta ...

  6. ALL IN AI | 第六届金蝶云·苍穹追光者开发大赛正式启动报名!

    2024年5月,第六届金蝶云·苍穹追光者开发大赛x第十三届"中国软件杯"金蝶赛道正式启动报名! 当下,人工智能正以其空前的速度.广度和深度,引领着新一轮科技革命和产业变革,重塑着经 ...

  7. zabbix第一天 zabbix安装,添加监控项

    1. zabbix 介绍 公司规模大,服务器众多,运维人员需要用到zabbix来监控整个服务器的运行状况,避免服务器故障后运维人员无法察觉. 清华zabbix源: https://mirrors.tu ...

  8. NOIP模拟95(多校28)

    T1 嗑瓜子 解题思路 \(f_{i,j}\) 表示操作 \(i\) 次,拿走了 \(j\) 个瓜子的概率,转移就比较直接了: \[f_{i+1,j+1}\leftarrow f_{i,j}\time ...

  9. js 求任意两数之间的和

    知识点:函数的传参,函数的返回值 函数的传参 函数的参数包括以下两种类型: 1.形参:在定义函数时,声明的参数变量仅在函数内部可见: 2.实参:在调用函数时,实际传入的值. 示例 我们在定义函数的时候 ...

  10. 8个实用的Java Streams API

    分享8个开箱即用的API,方便日常处理集合. 1. 快速过滤空值:Stream.ofNullable 该方法是在 Java 9 中引入的,有助于过滤集合中的所有空值,从而可能使我们避免空指针异常. 在 ...