图片加载占据了一个 web 应用的较多资源。
这篇文章中,我们实现一个 ImgWorker 组件,满足以下功能:
- image 可选择的 worker 加载,并且做好优雅降级
- decode 解码,并且做好优雅降级
- miniSrc 和 src 竞速加载
注意,在性能较低的机器,开启 worker 进程下载的开销大于直接使用主进程下载的开销; 使用 worker 需要主动传递 worker 属性为 true 开启
组件代码
先上代码,后续对关键代码做一些解释:
src/react-img-worker.js :
import * as React from 'react'; const blobUrl = `self.addEventListener('message', event => { const [url, type] = event.data; fetch(url, { method: 'GET', mode: 'no-cors', cache: 'default' }).then(response => { return response.blob(); }).then(_ => postMessage([url, type])).catch(console.error);})` type: 'application/javascript' ; interface IImgWorkerProps extends ReactDetailedHTMLProps< ReactImgHTMLAttributes<HTMLImageElement> HTMLImageElement > boxProps?: ReactDetailedHTMLProps< ReactImgHTMLAttributes<HTMLImageElement> HTMLImageElement >; miniSrc?: string; objectFit?: 'fill' | 'contain' | 'cover' | 'none' | 'scale-down'; renderLoading?: any; worker?: boolean; interface IImgWorkerState isLoading: boolean; src: string; Component< IImgWorkerProps IImgWorkerState> public static defaultProps = objectFit: 'cover' ; public div: any = null; public image: HTMLImageElement = ; public isLoadedSrcLock = false; public state = isLoading: true src: '' ; public worker: Worker = null as any; public { ; thisimagestylewidth = '100%'; thisimagestyleheight = '100%'; thisimagestyledisplay = 'none'; thisimagestyleobjectFit = thispropsobjectFit!; // 如果使用 worker 并且浏览器支持 worker if thispropsworker && typeof Worker !== 'undefined' thisworker = URL; thisworker; } public { thisdiv; this; } public { if nextPropsobjectFit !== thispropsobjectFit thisimagestyleobjectFit = nextPropsobjectFit!; let isPostMessage = false; if nextPropsminiSrc !== thispropsminiSrc isPostMessage = true; if nextPropssrc !== thispropssrc isPostMessage = true; // 如果 src 或 miniSrc 更新,重新请求 if isPostMessage thisisLoadedSrcLock = false; this; } public { if thisimage thisimageonload = null; thisimageonerror = null; if thisworker thisworker; } public { // 如果 src 已经被设置,拦截后续的更新 if thisisLoadedSrcLock return; if type === 'src' thisisLoadedSrcLock = true; thisimagesrc = url; thisimagedecoding = 'async'; thisimagedecode !== undefined ? thisimage : thisimageonload = thisonLoad; }; public { thisimagestyledisplay = 'block'; this; }; public { if propsminiSrc if thisworker thisworker; else this; if propssrc if thisworker thisworker; else this; }; public { const boxProps renderLoading: Loading src: _src ...rest = thisprops; const isLoading = thisstate; return <div = > Loading && isLoading && <Loading ="img-worker-loading" = /> </div> ; }
代码是 typescript 编写的,这是为了组件发版可以更简便的生成.d.ts 文件,内容很简单,其中关键在于两处:
- 创建一个 worker
thisworker = URL;
- 使用 decode
decode 的功能网上有许多文章,这里不展开
const image = ;imagesrc = url;imagedecoding = 'async';imagedecode !== undefined ? image : imageonload = thisonLoad;
使用
使用默认 image loader, ImgWorker 保留了 <img />
标签原有的所有 api
; ;
使用 worker loader:
; ;
支持 mini 图片,如果 设置了 miniSrc 会并行加载 miniSrc 和 src;若 miniSrc 优先加载完,显示 miniSrc;若 src 优先加载完会拦截此次后续的图片更新。
使用 worker + miniSrc + src:
; ;
直接使用 react-img-worker 库
为了方便使用,以上代码已发布至 npm,可以直接使用
$ yarn add react-img-worker
希望有对君有帮助:)