1.1.5 • Published 5 years ago
react-img-worker v1.1.5
图片加载占据了一个 web 应用的较多资源。
这篇文章中,我们实现一个 ImgWorker 组件,满足以下功能:
- image 可选择的 worker 加载,并且做好优雅降级
- decode 解码,并且做好优雅降级
- miniSrc 和 src 竞速加载
注意,在性能较低的机器,开启 worker 进程下载的开销大于直接使用主进程下载的开销; 使用 worker 需要主动传递 worker 属性为 true 开启
组件代码
先上代码,后续对关键代码做一些解释:
src/react-img-worker.js :
import * as React from 'react';
const blobUrl = new Blob(
[
`
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 React.DetailedHTMLProps<
React.ImgHTMLAttributes<HTMLImageElement>,
HTMLImageElement
> {
boxProps?: React.DetailedHTMLProps<
React.ImgHTMLAttributes<HTMLImageElement>,
HTMLImageElement
>;
miniSrc?: string;
objectFit?: 'fill' | 'contain' | 'cover' | 'none' | 'scale-down';
renderLoading?: any;
worker?: boolean;
}
interface IImgWorkerState {
isLoading: boolean;
src: string;
}
export class ImgWorker extends React.Component<
IImgWorkerProps,
IImgWorkerState
> {
public static defaultProps = {
objectFit: 'cover',
};
public div: any = null;
public image: HTMLImageElement = new Image();
public isLoadedSrcLock = false;
public state = {
isLoading: true,
src: '',
};
public worker: Worker = null as any;
public constructor(props: IImgWorkerProps) {
super(props);
this.image.style.width = '100%';
this.image.style.height = '100%';
this.image.style.display = 'none';
this.image.style.objectFit = this.props.objectFit!;
// 如果使用 worker 并且浏览器支持 worker
if (this.props.worker && typeof Worker !== 'undefined') {
this.worker = new Worker(URL.createObjectURL(blobUrl));
this.worker.addEventListener('message', event => {
const [url, type] = event.data;
this.loadImage(url, type);
});
}
}
public componentDidMount() {
this.div.appendChild(this.image);
this.postMessage(this.props);
}
public componentWillReceiveProps(nextProps: IImgWorkerProps) {
if (nextProps.objectFit !== this.props.objectFit) {
this.image.style.objectFit = nextProps.objectFit!;
}
let isPostMessage = false;
if (nextProps.miniSrc !== this.props.miniSrc) {
isPostMessage = true;
}
if (nextProps.src !== this.props.src) {
isPostMessage = true;
}
// 如果 src 或 miniSrc 更新,重新请求
if (isPostMessage) {
this.isLoadedSrcLock = false;
this.postMessage(nextProps);
}
}
public componentWillUnmount() {
if (this.image) {
this.image.onload = null;
this.image.onerror = null;
}
if (this.worker) {
this.worker.terminate();
}
}
public loadImage = (url: string, type: string) => {
// 如果 src 已经被设置,拦截后续的更新
if (this.isLoadedSrcLock) {
return;
}
if (type === 'src') {
this.isLoadedSrcLock = true;
}
this.image.src = url;
this.image.decoding = 'async';
this.image.decode !== undefined
? this.image
.decode()
.then(this.onLoad)
.catch(this.onLoad)
: (this.image.onload = this.onLoad);
};
public onLoad = () => {
this.image.style.display = 'block';
this.setState({
src: this.image.src,
isLoading: false,
});
};
public postMessage = (props: IImgWorkerProps) => {
if (props.miniSrc) {
if (this.worker) {
this.worker.postMessage([props.miniSrc, 'miniSrc']);
} else {
this.loadImage(props.miniSrc, 'miniSrc');
}
}
if (props.src) {
if (this.worker) {
this.worker.postMessage(this.worker.postMessage([props.src, 'src']));
} else {
this.loadImage(props.src, 'miniSrc');
}
}
};
public render() {
const { boxProps, renderLoading: Loading, src: _src, ...rest } = this.props;
const { isLoading } = this.state;
return (
<div ref={r => (this.div = r)} {...rest}>
{Loading && isLoading && (
<Loading key="img-worker-loading" isLoaing={isLoading} />
)}
</div>
);
}
}
代码是 typescript 编写的,这是为了组件发版可以更简便的生成.d.ts 文件,内容很简单,其中关键在于两处:
- 创建一个 worker
this.worker = new Worker(URL.createObjectURL(blobUrl));
- 使用 decode
decode 的功能网上有许多文章,这里不展开
const image = new Image();
image.src = url;
image.decoding = 'async';
image.decode !== undefined
? image
.decode()
.then(this.onLoad)
.catch(this.onLoad)
: (image.onload = this.onLoad);
使用
使用默认 image loader, ImgWorker 保留了 <img />
标签原有的所有 api
import { ImgWorker } from './react-img-worker';
export default () => {
return <ImgWorker src="http://example.png" />;
};
使用 worker loader:
import { ImgWorker } from './react-img-worker';
export default () => {
return <ImgWorker worker src="http://example.png" />;
};
支持 mini 图片,如果 设置了 miniSrc 会并行加载 miniSrc 和 src;若 miniSrc 优先加载完,显示 miniSrc;若 src 优先加载完会拦截此次后续的图片更新。
使用 worker + miniSrc + src:
import { ImgWorker } from './react-img-worker';
export default () => {
return (
<ImgWorker
worker
miniSrc="http://example.mini.png"
src="http://example.png"
/>
);
};
直接使用 react-img-worker 库
为了方便使用,以上代码已发布至 npm,可以直接使用
$ yarn add react-img-worker
希望有对君有帮助:)