首页
/ antd Image 预览自定义工具栏实战:用 actionsRender 实现图片下载、翻转、旋转与缩放控制

antd Image 预览自定义工具栏实战:用 actionsRender 实现图片下载、翻转、旋转与缩放控制

2026-09-07 17:19:29作者:宣利权Counsellor

导读

antd 的 <Image> 组件在点击缩略图后会弹出全屏预览层(Preview),内置翻页、缩放、旋转、翻转与关闭等操作按钮。实际业务里我们往往不满足于默认工具栏——例如需要"下载当前预览的原图""下载翻转/旋转后的成图",或者按场景重新排布图标。本指南以仓库中 toolbarRender.md 这个官方 demo 为核心主体,逐步拆解其背后的 actionsRender 定制方案:读完你将掌握 actionsRender 的完整签名与回调 info 结构、内置行为 API(actions)的调用方式、受控下载图片的完整链路,以及旧版 toolbarRender 与新 API 之间的兼容关系,并能基于仓库源码定位每一条结论的真实出处。

一、demo 在讲什么:自定义工具栏 + 图片下载

该 demo 的定位描述很精炼:

可以自定义工具栏并添加下载原图或翻转旋转后图片的按钮。 You can customize the toolbar and add a button for downloading the original image or downloading the flipped and rotated image.

也就是说,它属于 antd Image 组件"图片预览定制"系列,在 Image API 文档 中对应标题 Custom toolbar render(自定义工具栏渲染),中文版索引页 index.zh-CN.md 则将其命名为"自定义工具栏"。示例的完整可运行实现位于同目录的 toolbarRender.tsx,两者组合构成了本指南全部技术信息的来源。

一句话概括 demo 的能力边界:

  • 使用 <Image.PreviewGroup> 组合多张图片,实现缩略图网格 + 统一预览;
  • 通过 preview={{ actionsRender: ... }} 完全替换预览层底部的默认工具栏;
  • 工具栏内自绘上一张/下一张、下载、水平/垂直翻转、左右旋转、放大/缩小、重置等按钮;
  • 预览切换时同步记录当前图片索引,供"下载当前图片"功能使用。

二、整体结构:PreviewGroup + preview.actionsRender

先看 demo 的核心 JSX 结构(省略样式细节后的骨架):

import { Image, Space } from 'antd';
import { DownloadOutlined, LeftOutlined, RightOutlined, SwapOutlined } from '@ant-design/icons';
// ...

const App: React.FC = () => {
  const [current, setCurrent] = React.useState(0);

  return (
    <Image.PreviewGroup
      preview={{
        actionsRender: (_, { transform, actions }) => (
          <Space size={12}>
            {/* 自定义按钮集合 */}
          </Space>
        ),
        onChange: (index) => {
          setCurrent(index);
        },
      }}
    >
      {imageList.map((item, index) => (
        <Image alt={`image-${index}`} key={item} src={item} width={200} />
      ))}
    </Image.PreviewGroup>
  );
};

这里有两层组件值得说明:

  1. <Image.PreviewGroup>:预览组容器。仓库中对应 PreviewGroup.tsx,它内部仍然把配置交给 @rc-component/imageRcImage.PreviewGroup 渲染,同时负责 antd 侧的样式前缀、classNames/styles 语义结构与预览配置合并。items 支持传入 string[] 或对象数组,也可以像 demo 一样用多个 <Image> 子节点声明(源码中 Image.PreviewGroup = PreviewGroup 的挂载见 index.tsx 第 319 行)。
  2. preview 配置对象:同一份配置被 usePreviewConfig.ts 解析归一化后再传给底层组件,actionsRender 就是其中最关键的自定义渲染入口。

需要留意,actionsRenderImage 单组件与 Image.PreviewGroup 两种场景下都可使用。若单图使用则写在 Imagepreview 属性里,类型对应 PreviewType;若组图预览则写在 PreviewGrouppreview 属性里,类型对应 PreviewGroupType(两种类型下 actionsRender 均定义,见 index.en-US.md API 表格)。区别在于组图模式下 info 会额外携带 currenttotal,可用于感知当前预览第几张。

三、actionsRender 签名与 info 对象全解析

3.1 函数签名

官方类型定义(见 index.en-US.md 的 Interface 章节):

type ActionsRender = (
  originalNode: React.ReactElement,
  info: ToolbarRenderInfoType,
) => React.ReactNode;
  • originalNode:默认工具栏的原始 React 元素。返回它则沿用默认;demo 完全不使用它,直接返回自己拼装的 <Space>,实现"整体替换"。
  • infoToolbarRenderInfoType,把当前预览状态与全部内置操作暴露给我们,是自定义工具栏真正的"控制中心"。

3.2 info:ToolbarRenderInfoType 字段说明

接口文档给出的完整结构为:

{
  icons: {
    flipYIcon: React.ReactNode;      // 垂直翻转图标
    flipXIcon: React.ReactNode;      // 水平翻转图标
    rotateLeftIcon: React.ReactNode; // 左旋图标
    rotateRightIcon: React.ReactNode;// 右旋图标
    zoomOutIcon: React.ReactNode;    // 缩小图标
    zoomInIcon: React.ReactNode;     // 放大图标
  };
  actions: {
    onActive?: (index: number) => void; // 切换至指定索引的图片(接口文档标注 5.21.0 起支持)
    onFlipY: () => void;                 // 垂直翻转
    onFlipX: () => void;                 // 水平翻转
    onRotateLeft: () => void;            // 向左旋转 90°
    onRotateRight: () => void;           // 向右旋转 90°
    onZoomOut: () => void;               // 缩小
    onZoomIn: () => void;                // 放大
    onReset: () => void;                 // 重置变换(接口文档标注 5.17.3 起支持)
    onClose: () => void;                 // 关闭预览
  };
  transform: TransformType; // 当前变换状态
  current: number;          // 当前图片索引(0 起)
  total: number;            // 图片总数
  image: ImgInfo;           // 当前图片信息
}

3.3 transform:当前变换状态

TransformType 结构(接口文档定义)为:

{
  x: number;        // 水平位移
  y: number;        // 垂直位移
  rotate: number;   // 旋转角度
  scale: number;    // 缩放倍数
  flipX: boolean;   // 是否水平翻转
  flipY: boolean;   // 是否垂直翻转
}

它是只读状态,供 UI 做条件渲染判断(比如 demo 里用 scale 判断放大/缩小按钮是否禁用)。真正改变它的动作由 actions 里的回调触发。另外,缩放边界与步进在 API 中有对应可配置项:

  • minScale(默认 1)与 maxScale(默认 50)控制缩放上下限;
  • scaleStep(默认 0.5)表示每次缩放的倍率步进为 1 + scaleStep

这些默认值可从 index.en-US.mdPreviewType/PreviewGroupType 表格查得,正好解释了 3.4 中 demo 的禁用逻辑为何使用 scale === 1scale === 50 两个边界判断。

3.4 image:图片元信息

{
  url: string;             // 预览图地址
  alt: string;             // 替代文本
  width: string | number;  // 宽
  height: string | number; // 高
}

ImgInfo 的作用在 imageRender/actionsRender 中非常实用(例如在渲染函数中展示当前图元信息,参见同目录 demo preview-imgInfo.tsx)。

四、逐按钮拆解 demo:翻页、翻转、旋转、缩放与重置

demo 把整套内置操作重绘为一行胶囊状的图标按钮组,每个图标背后都调用了 info.actions 对应方法:

demo 中的按钮 图标 调用的 actions 方法 备注
上一张 LeftOutlined onActive?.(-1) 首图时 disabled={current === 0}
下一张 RightOutlined onActive?.(1) 末图时 disabled={current === imageList.length - 1}
下载 DownloadOutlined 自定义 onDownload 4.2 单独讲解
垂直翻转 SwapOutlined rotate={90} onFlipY
水平翻转 SwapOutlined onFlipX
向左旋转 RotateLeftOutlined onRotateLeft
向右旋转 RotateRightOutlined onRotateRight
缩小 ZoomOutOutlined onZoomOut scale === 1 时禁用
放大 ZoomInOutlined onZoomIn scale === 50 时禁用
重置 UndoOutlined onReset 一键还原缩放/旋转/翻转/位移

注意 demo 中 actionsRender 的实参解构写法:

actionsRender: (
  _,
  {
    transform: { scale },
    actions: { onActive, onFlipY, onFlipX, onRotateLeft, onRotateRight, onZoomOut, onZoomIn, onReset },
  },
) => ...

它同时用到了 info.transform(读取只读状态)与 info.actions(触发用户交互),这正是 actionsRender 与"纯静态渲染"的根本差异——渲染函数被接入到了预览层的状态机中。

值得对比的是:antd 预览层默认工具栏本身也维护着同样一套图标。在 PreviewGroup.tsx 中可见默认图标映射 icons 常量:

export const icons = {
  rotateLeft: <RotateLeftOutlined />,
  rotateRight: <RotateRightOutlined />,
  zoomIn: <ZoomInOutlined />,
  zoomOut: <ZoomOutOutlined />,
  close: <CloseOutlined />,
  left: <LeftOutlined />,
  right: <RightOutlined />,
  flipX: <SwapOutlined />,
  flipY: <SwapOutlined rotate={90} />,
};

自定义 actionsRender 时若只想"微调",可以把 originalNodeinfo.icons 组合使用;若想完全重排(demo 的方案),则从 info.actions 取行为、自己选图标即可。RTL 场景下 PreviewGroup 还会根据 direction 自动交换左右箭头图标(源码第 90–97 行的 memoizedIcons),这是内部实现细节,自定义时需自行考虑 RTL 语义。

五、核心场景:把"当前图片"下载到本地

5.1 追踪当前索引

由于下载目标随预览翻页而变化,demo 用两块协作完成:

  1. 外层维护 const [current, setCurrent] = React.useState(0);
  2. preview 配置里注册 onChange: (index) => setCurrent(index);

PreviewGroupType.onChange 签名是 (current: number, prevCurrent: number) => void(见 index.en-US.md),即每次切换图片都会把新索引回传。demo 同时把两张测试图存进数组:

const imageList = [
  'https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg',
  'https://gw.alipayobjects.com/zos/antfincdn/aPkFc8Sj7n/method-draw-image.svg',
];

(以上为 demo 自带的远程演示素材地址,运行示例时可直接复用;部署到生产请替换为自己的图片域名。)

5.2 下载函数完整链路

demo 中的 onDownload 实现了一套浏览器端标准的"远端资源转本地下载"流程:

const onDownload = () => {
  const url = imageList[current];
  const suffix = url.slice(url.lastIndexOf('.'));      // 提取扩展名
  const filename = Date.now() + suffix;                // 生成文件名

  fetch(url)                                            // ① 拉取二进制
    .then((response) => response.blob())                // ② 转 Blob
    .then((blob) => {
      const blobUrl = URL.createObjectURL(new Blob([blob])); // ③ 生成对象 URL
      const link = document.createElement('a');
      link.href = blobUrl;
      link.download = filename;                          // ④ 指定下载文件名
      document.body.appendChild(link);
      link.click();                                      // ⑤ 触发下载
      URL.revokeObjectURL(blobUrl);                      // ⑥ 释放对象 URL
      link.remove();                                     // ⑦ 清理临时节点
    });
};

整个链路可归纳为:fetch 拉取图片 → response.blob() 转为二进制对象 → URL.createObjectURL 生成本地临时地址 → 动态创建带 download 属性的 <a> 并模拟点击 → 下载完成后 revokeObjectURL 回收资源并移除节点。

几点工程化提示:

  • 文件名时间戳化Date.now() + suffix 保证多次下载互不覆盖;demo 使用当前原图 URL 的扩展名作为后缀,SVG 场景亦可直接使用。
  • CORS 前提fetch 跨域读取图片字节要求目标资源允许跨域(响应头携带 CORS 许可),同源资源则无此限制。若直接 a.href = url 方式受跨域限制时可参考此 Blob 中转方案。
  • "下载翻转/旋转后图片"的实现边界:demo 源码中 onDownload 下载的是当前索引对应的原始图片,而预览层内的翻转/旋转仅作用于渲染状态。若要真正导出"变换后的成图",需要把 info.transform 中的 rotate/flipX/flipY/scale 应用到 <canvas>(绘制后 toBlob)或 SVG 后再走相同的下载链路——仓库示例本体并未内置该导出实现(源码注释中仅在说明此思路),因此描述为"可自定义添加下载原图或翻转旋转后图片的按钮"这一目标方向,落地时需自行实现变换合成逻辑。

5.3 受控开关下载按钮

自定义按钮属于完全受控的 UI,因此 demo 中下载按钮不带禁用态;而翻页/缩放按钮利用可用信息做了禁用判断,形成完整的可用性闭环:首图禁用"上一张"、末图禁用"下一张"、scale 触底禁用缩小、scale 触顶禁用放大。这套写法可直接移植到任意需要状态感知的工具栏实现中。

六、从 toolbarRender 到 actionsRender:废弃属性与兼容逻辑

早期版本的 API 名是 toolbarRender,如今已被 actionsRender 取代。仓库代码给出了完整的"废弃 + 兼容"证据链:

6.1 类型层面标记废弃

index.tsxDeprecatedPreviewConfig 中:

/** @deprecated Use `actionsRender` instead */
toolbarRender?: OriginPreviewConfig['actionsRender'];

并在 PreviewConfig 注释中同样标明 @deprecated。API 文档中 PreviewTypePreviewGroupType 两处也以删除线形式列出 toolbarRender 并注明 "please use 'actionsRender' instead"(见 index.en-US.md 第 94、131 行)。

6.2 运行时做"新老兼容"映射

usePreviewConfig.ts 是所有 preview 配置的归一化入口,它从原始配置中把两个属性同时解构出来:

const { ..., actionsRender, ..., toolbarRender, ... } = rawPreviewConfig;

// 返回时:
actionsRender: actionsRender ?? toolbarRender,

即:只要传了新的 actionsRender 就优先使用,没传则回退到旧的 toolbarRender,二者在运行时是同一渲染函数的两个名字。开发环境下它还通过 devUseWarning 触发迁移提示:

['toolbarRender', 'actionsRender'].forEach(([deprecatedName, newName]) => {
  warning.deprecated(!(deprecatedName in rawPreviewConfig), deprecatedName, newName);
});

6.3 测试侧的证据

对应的行为在测试中同样被固化:tests/deprecated.test.tsx 中有专门用例 it('toolbarRender', ...),断言渲染 <Image preview={{ toolbarRender: () => <div /> }} /> 时控制台输出警告:

Warning: [antd: Image] 'toolbarRender' is deprecated. Please use 'actionsRender' instead.

因此新项目请一律使用 actionsRender;旧代码可以继续工作,但应把 toolbarRender 平滑迁移为 actionsRender,二者参数签名(originalNodeinfo)保持一致。值得一提的是,迁移说明中还有一条配套提示:旧 toolbarRenderinfo 不包含 current/total(单图上下文),组图场景统一使用 actionsRender 即可获得完整信息。

七、工具栏外观定制:样式封装的正确姿势

demo 还演示了如何用 antd 官方样式方案(antd-style)给工具栏做外观定制:

import { createStyles } from 'antd-style';

const useStyles = createStyles((props) => {
  const { css, iconPrefixCls, cssVar } = props;
  return {
    wrapper: css`
      padding: 0 ${cssVar.paddingLG};
      color: ${cssVar.colorWhite};
      font-size: ${cssVar.fontSizeXL};
      background-color: rgba(0, 0, 0, 0.1);
      border-radius: 100px;
      .${iconPrefixCls} {
        padding: ${cssVar.paddingSM};
        cursor: pointer;
        &:hover { opacity: 0.3; }
        &[disabled] {
          opacity: 0.3;
          cursor: not-allowed;
        }
      }
    `,
  };
});

要点拆解:

  • cssVar 暴露了 antd 的设计令牌(如 paddingLGpaddingSMcolorWhitefontSizeXL),使自定义样式与全局主题保持一致的间距与色彩体系;
  • 通过 iconPrefixCls 精准命中 antd 图标类名,为图标统一设置内边距与悬停效果;
  • [disabled] 属性选择器同时兜底了"图标自带的 disabled 态"与 demo 里通过条件渲染赋值的禁用按钮,保证禁用时指针样式为 not-allowed 且透明度降低;
  • 外层使用 Space size={12} + 胶囊底色(border-radius: 100px),得到悬浮在预览底部的工具栏视觉。

需要说明:demo 对图标的禁用主要依赖图标组件自身支持 disabled 属性(demo 中 onZoomIn 图标点击放大的判断同时配合 onZoomOut 的边界条件实现),样式层仅负责把禁用态渲染得更清晰。若要完全控制预览容器层级,还可以结合 classNames/styles 语义(popup.actions 等)进一步深入定制,相关语义结构可查 index.en-US.md 的 Semantic DOM 章节。

八、可验证性与延伸阅读

本 demo 有自动化测试守护:快照测试会渲染 toolbarRender.tsx 并比对输出,相关快照记录在 snapshots/demo.test.ts.snap(用例名 renders components/image/demo/toolbarRender.tsx correctly)与 snapshots/demo-extend.test.ts.snaprenders ... extend context correctly)。这意味着官方会持续保证该示例可在当前版本正常渲染,示例代码本身即是"版本内事实"的最直接依据。

想继续深入,可以按需查阅以下仓库文件:

小结

围绕 toolbarRender.md 描述的能力,本文把 demo 拆成了四条可复用主线:actionsRender 接管工具栏渲染、用 info.actions 触发预览内置行为、用 info.transform 做状态感知(缩放边界禁用等)、用 fetch+Blob+<a download> 完成当前图片下载。同时澄清了 toolbarRenderactionsRender 的迁移路径与兼容实现(usePreviewConfig.tsactionsRender ?? toolbarRender 的合并逻辑及开发告警),避免读者在使用中踩到"新旧 API 混用"的坑。若你的下一个需求是"相册预览 + 导出当前变换成图",本文的 info 结构解析与下载链路即可作为起点,结合 Canvas 合成即可扩展出完整能力。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
915
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388