ImageViewer 图片查看器
- 组件说明:全屏图片查看器(lightbox),在半透明遮罩上居中展示图片,底部浮动工具栏承载导航、缩放、删除、导出与关闭操作。
- 交互特征:内置多图导航(上一张 / 下一张,到边界禁用,支持
←/→键)、范围内放大 / 缩小(支持+/=/-键)、可选删除、导出(缺省回退浏览器下载),以及通过收起按钮 /Esc/ 点击遮罩关闭。 - 实现约定:无
ui/primitive,直接在 design 层基于 Radix Dialog primitive 实现,复用其 portal / focus trap /Esc/ 滚动锁定 / a11y,视觉完全由 design 层接管。 - Figma 规范
基础用法
传入 images 数组并将触发元素作为 children。上一张 / 下一张在列表内导航,到边界时自动禁用。
结果
Loading...
实时编辑器
const images = [ { src: 'https://picsum.photos/seed/plaud-a/900/600', alt: 'Sample A' }, { src: 'https://picsum.photos/seed/plaud-b/900/600', alt: 'Sample B' }, { src: 'https://picsum.photos/seed/plaud-c/900/600', alt: 'Sample C' }, ] render( <div style={{ padding: '40px 0' }}> <ImageViewer images={images}> <Button>打开查看器</Button> </ImageViewer> </div>, )
单图
单图时传入只含一个元素的数组。上一张 / 下一张按钮(及其分隔线)直接隐藏,缩放、导出、关闭仍可用。
结果
Loading...
实时编辑器
const images = ['https://picsum.photos/seed/plaud-single/1200/800'] render( <div style={{ padding: '40px 0' }}> <ImageViewer images={images}> <Button variant="secondary">打开单张图片</Button> </ImageViewer> </div>, )
删除与导出
传入 onDelete 后显示删除按钮。onExport 会覆盖默认下载(缺省时先把图片 fetch 成 blob 再保存,跨域 CDN 地址也能真正下载而非直接跳转新页面)。
结果
Loading...
实时编辑器
const images = [ { src: 'https://picsum.photos/seed/plaud-d1/900/600', alt: 'Photo 1' }, { src: 'https://picsum.photos/seed/plaud-d2/900/600', alt: 'Photo 2' }, ] render( <div style={{ padding: '40px 0' }}> <ImageViewer images={images} onDelete={(index) => alert('删除索引: ' + index)} onExport={(image, index) => alert('导出 ' + index + ': ' + image.src)} > <Button>打开(含删除与导出)</Button> </ImageViewer> </div>, )
受控模式
外部控制 open 与 index,由自身状态驱动查看器。
结果
Loading...
实时编辑器
const images = [ { src: 'https://picsum.photos/seed/plaud-c1/900/600', alt: 'Image 1' }, { src: 'https://picsum.photos/seed/plaud-c2/900/600', alt: 'Image 2' }, { src: 'https://picsum.photos/seed/plaud-c3/900/600', alt: 'Image 3' }, ] const Demo = () => { const [open, setOpen] = useState(false) const [index, setIndex] = useState(0) return ( <div style={{ display: 'flex', gap: 12, alignItems: 'center', padding: '40px 0' }}> <Button onClick={() => setOpen(true)}>打开受控</Button> <span style={{ fontSize: 14, color: '#999' }}>index: {index}</span> <ImageViewer images={images} open={open} onOpenChange={setOpen} index={index} onIndexChange={setIndex} /> </div> ) } render(<Demo />)
命令式 API
ImageViewer.open() 用于命令式打开查看器——适合相册缩略图、表格行操作、快捷键等没有 JSX 触发器的场景。需要在应用根部挂载 DesignProvider 或 OverlayHost。返回带 close / update / afterClosed 的控制器。
import { ImageViewer } from '@plaud/design'
const controller = ImageViewer.open({
images: ['/a.png', '/b.png', '/c.png'],
defaultIndex: 0,
onDelete: (index) => {
// 在调用方自己的列表里删除后,再同步给查看器
controller.update({ images: nextImages })
},
onExport: (image) => download(image.src),
})
// 任意位置关闭
controller.close()
await controller.afterClosed
ImageViewer.open() 接受与声明式组件相同的入参,但不含 children / open / defaultOpen(显隐由 overlay host 接管)。
Token
| 区域 | Token |
|---|---|
| 遮罩 | Overlays/Default#00000066 |
| 工具栏背景 | Foregrounds/Tooltip#3d3d3d |
| 工具栏图标 | Foregrounds/White#ffffff |
| 禁用图标 | Labels/Disabled#808080 |
| 几何 | 值 | Token |
|---|---|---|
| 工具栏圆角 | 5px | Radius_5 |
| 工具栏内边距 / 按钮间距 / 按钮内边距 | 4px | Spacing_4 |
| 工具栏距底部 | 24px | Spacing_24 |
| 图标容器 | 20×20px | — |
| 工具栏按钮 hover(fallback) | white/10 | — |
| 分隔线(fallback) | white/20,高 28px | — |
| 图片最大尺寸(fallback) | max-h-[80vh] / max-w-[min(900px,90vw)] | — |
Props
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
images | (string | { src: string; alt?: string })[] | — | 图片列表;单图传只含一个元素的数组 |
children | ReactNode | — | 触发元素(非受控模式) |
open | boolean | — | 受控显隐 |
defaultOpen | boolean | false | 非受控初始显隐 |
onOpenChange | (open: boolean) => void | — | 显隐变化回调 |
index | number | — | 受控当前索引 |
defaultIndex | number | 0 | 非受控初始索引 |
onIndexChange | (index: number) => void | — | 索引变化回调 |
onDelete | (index: number) => void | — | 删除回调;传入后才显示删除按钮 |
onExport | (image: { src; alt? }, index: number) => void | — | 导出回调;缺省时回退到 blob fetch + 下载 |
minZoom | number | 0.5 | 最小缩放倍率 |
maxZoom | number | 3 | 最大缩放倍率 |
zoomStep | number | 0.25 | 单次缩放步进 |
className | string | — | 内容层 className |
使用约束
- 查看器是基于 Radix Dialog primitive 的全屏遮罩弹层;非受控模式下
children作为触发器。 - 命令式
ImageViewer.open()需要在应用根部挂载DesignProvider或OverlayHost。 - 删除时组件不会自行修改
images,请在调用方的onDelete回调中更新列表。 - 切换当前图片或重新打开查看器时,缩放会复位为
1。