前言
项目遇到一个需要优化的点。就是公司的网要么都比较差,在点击 Image 查看预览时,会加载很久,并且没有loading态。而且来回切换时,每次都重新请求,没有缓存。我们的图片是存在七牛的,而七牛并没有缓存,所以前端通过 base64 来实现缓存
思路
请先看原本的代码:
tsx
import { IImgDTO } from "@/service/base/v1/organization/get-page";
import { IObj } from "@/utils/interface";
import { classNames } from "@/utils/tools";
import { GetProps, Image, Skeleton } from "antd";
import { useEffect, useRef, useState } from "react";
type PreviewGroupType = GetProps<typeof Image.PreviewGroup>;
export interface ITXImagesRenderProps extends PreviewGroupType {
imgs: IImgDTO[];
showCount?: number;
width?: number;
height?: number;
className?: string;
/** @param 仅展示第一张,剩余的预览查看 */
albumFlag?: boolean;
/** @param 更多图标的类名 */
moreClassName?: string;
}
export const TXImagesRender = function TXImagesRender_(
props: ITXImagesRenderProps
) {
const {
imgs = [],
showCount = 2,
className = "",
width = 32,
height = width,
albumFlag = false,
moreClassName = "",
...reset
} = props;
const [visible, setVisible] = useState(false);
const [current, setCurrent] = useState(0);
if (!imgs.length) {
return "-";
}
let showImgs = imgs;
let num = 0;
if (showImgs.length > showCount) {
num = showImgs.length - showCount + 1;
showImgs = imgs.slice(0, showCount - 1);
}
if (albumFlag) {
num = 0;
showImgs = imgs.slice(0, 1);
}
return (
<Image.PreviewGroup
{...reset}
items={imgs?.map?.((r) => r.fullPath)}
preview={{
visible,
onVisibleChange: setVisible,
current,
onChange: (index) => setCurrent(index),
}}
>
<div className={classNames({ "flex gap-1": true, [className]: true })}>
{showImgs.map((r, rIndx) => {
return (
<Image
key={rIndx}
width={width}
height={height}
src={r.fullThumbnailPath}
onClick={() => {
setCurrent(rIndx);
}}
/>
);
})}
<div
className={classNames({
"bg-gray-400/30 cursor-pointer flex items-center justify-center rounded":
true,
hidden: !num,
[moreClassName]: true,
})}
onClick={() => {
setCurrent(showCount - 1);
setVisible(true);
}}
style={{
height: `${height}px`,
width: `${width}px`,
}}
>
+{num}
</div>
</div>
</Image.PreviewGroup>
);
};
原本的代码在 preview 点开时没有 loading,网差的时候体验就不好。虽然antd Image在左右切换时会缓存,但在首次点开时依旧需要请求图片。
因此改动后,通过base64手动缓存图片,再加loading态,来实现优化方案
新的代码:
tsx
//...
export const TXImagesRender = function TXImagesRender_(
props: ITXImagesRenderProps
) {
const {
imgs = [],
showCount = 2,
className = "",
width = 32,
height = width,
albumFlag = false,
moreClassName = "",
...reset
} = props;
//...
const [loading, setLoading] = useState<boolean>(false);
const imgRef = useRef<HTMLImageElement | null>(null);
const [base64Obj, setBase64Obj] = useState<IObj>({});
const handleImageLoaded = (url: string) => {
if (base64Obj[url]) {
setLoading(false)
return
}
setLoading(false);
try {
const img = imgRef.current;
if (!img) return;
const canvas = document.createElement("canvas");
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;
const ctx = canvas.getContext("2d");
if (!ctx) return;
ctx.drawImage(img, 0, 0);
const dataURL = canvas.toDataURL("image/png");
setBase64Obj((prev) => ({
...prev,
[url]: dataURL,
}));
} catch (err) {
console.log(err);
console.warn("无法生成Base64(可能因跨域图片)", err);
}
};
useEffect(() => {
if (visible) {
setLoading(true);
}
}, [visible, current])
return (
<Image.PreviewGroup
preview={{
imageRender: (node, info) => (
<>
{loading && <Skeleton.Image active />}
<img
className={classNames({ "!hidden": loading })}
src={base64Obj[info.image.url] ?? info.image.url}
ref={imgRef}
onLoad={() => handleImageLoaded(info.image.url)}
crossOrigin="anonymous"
/>
</>
),
}}
>
//...
</Image.PreviewGroup>
);
};
新代码加了两个逻辑,在预览点开时,加载loading态,当onLoad时间图片加载完成时,取消loading态。还需要判断一个东西,如果base64缓存已经存在了,则不需要loading态了,直接使用缓存的即可