跳到主要内容

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>,
)

受控模式

外部控制 openindex,由自身状态驱动查看器。

结果
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 触发器的场景。需要在应用根部挂载 DesignProviderOverlayHost。返回带 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
工具栏圆角5pxRadius_5
工具栏内边距 / 按钮间距 / 按钮内边距4pxSpacing_4
工具栏距底部24pxSpacing_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 })[]图片列表;单图传只含一个元素的数组
childrenReactNode触发元素(非受控模式)
openboolean受控显隐
defaultOpenbooleanfalse非受控初始显隐
onOpenChange(open: boolean) => void显隐变化回调
indexnumber受控当前索引
defaultIndexnumber0非受控初始索引
onIndexChange(index: number) => void索引变化回调
onDelete(index: number) => void删除回调;传入后才显示删除按钮
onExport(image: { src; alt? }, index: number) => void导出回调;缺省时回退到 blob fetch + 下载
minZoomnumber0.5最小缩放倍率
maxZoomnumber3最大缩放倍率
zoomStepnumber0.25单次缩放步进
classNamestring内容层 className

使用约束

  • 查看器是基于 Radix Dialog primitive 的全屏遮罩弹层;非受控模式下 children 作为触发器。
  • 命令式 ImageViewer.open() 需要在应用根部挂载 DesignProviderOverlayHost
  • 删除时组件不会自行修改 images,请在调用方的 onDelete 回调中更新列表。
  • 切换当前图片或重新打开查看器时,缩放会复位为 1