antd Image base64缓存 + loading 态优化方案

前言

项目遇到一个需要优化的点。就是公司的网要么都比较差,在点击 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态了,直接使用缓存的即可

相关推荐
子兮曰19 小时前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰19 小时前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
前端小万20 小时前
写公众号赚了 3000 块后,我做了一款叫 "一键成稿" 的软件
前端·微信小程序
爱勇宝20 小时前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
三十而立洋20 小时前
Cookie 详解:从产生到安全,一次讲透
前端·javascript
卡布鲁21 小时前
把一个 Vite + Vue3 应用塞进 qiankun (React + Umi3) 主站:十个坑的复盘
前端·javascript·react.js
汉堡大王95271 天前
Jev:不是聊天机器人, 而是一个智能 if 语句
前端·人工智能·后端
梦想很大很大1 天前
从运行事实到回归证据:Workrun 的 Telemetry 与 Evaluation 实践
前端·人工智能·后端
计算机魔术师1 天前
Meta Muse agent 接入 Shopify 的 Shop Pay 实现代理式购物
前端
沙蒿同学1 天前
我用 Go 搭了一条 AI Agent 流水线:从 1 张商品图到一整套淘宝详情页
前端·javascript·后端