跳转到内容

API 文档

按引入方式分组:组件、钩子、类型,以及独立发布的入口。

包裹一张缩略图,点击它打开预览器。它会根据自己所处的位置决定行为:

  • Group 里面:作为触发器。图片集合、打开状态和界面都归 Group 管, 所以它下面的每个 ImageView 打开的是同一个预览器,彼此之间可以翻页。
  • 单独使用时:没有可共享的对象,于是它自己补上 Group 和默认界面。 这就是一行接入的写法。

因此从单图扩成多图是纯追加的——把现有的 ImageView 包进一个 Group 即可, 不需要改任何名字。

ImageItem 加上:

属性 类型
children ReactElement 用来打开预览器的元素。始终复用唯一的子元素;ImageView 不接受 asChild 属性。
index number 在整组里的位置。不传时按注册顺序 —— 虚拟列表,或任何挂载顺序与视觉顺序可能不一致的场景,请显式传入。
disabled boolean
container / labels / renderImage Group 相同 只有在外层没有 Group 时才会读取,因为那是它唯一自己拥有预览器的情况。放在 Group 里传会在开发环境告警。

它同时也是命名空间:各个部件都挂为静态属性(ImageView.GroupImageView.Content……),也都有独立的具名导出。要自己拼界面,从 primitives 入口取同一批部件 —— 见其它入口

属性 类型 默认值
images ImageItem[] 各 trigger 的注册顺序 ImageItem 数组。不传则改为把 ImageView 作为子节点列出。
open / defaultOpen boolean defaultOpen = false 受控 / 非受控。
onOpenChange (open: boolean) => void
index / defaultIndex number defaultIndex = 0
onIndexChange (index: number) => void
container HTMLElement | null document.body Portal 目标 —— 什么时候该指向一个带主题的祖先元素,见安装
extensions Extension[] [] 见下方 Extensions
labels Partial<ViewerLabels> 稳定的英文默认值 所有面向用户的文案。见 ViewerLabels

Portal 出去的 <dialog>。它不额外渲染任何东西 —— 传子节点来自己组合布局,或者干脆不写,让 Group 补上 DefaultContent

使用主入口时,请让 ContentDefaultContent 成为 Group 的直接子节点; 只有这一层会参与是否补上预设的判断。primitives 入口的 Group 从不补预设。

HeaderToolbarFooter —— 纯粹的定位容器, 都是支持 asChild<div>,本身没有任何行为。

手势承载面。ImagePrev/NextLoadingError 都应该放在它里面 —— 预设里工具栏的 position: absolute 正是相对 stage 定位的, 这样放在它下面的东西(说明文字、Thumbnails)不会把工具栏挤出画面。

渲染当前图片以及紧邻的前后两张(手势进行中翻页不必等待挂载)。 overscan?: number 控制当前张两侧各保留多少张处于挂载状态(默认 1)。

renderImage 可以替换大图的渲染方式,不必把所有原生图片属性都加进 ImageItem。它会收到当前图片、序号、是否为当前页,以及库组装好的 imageProps

<ImageView.Image
renderImage={({ item, imageProps }) => (
<picture>
<source srcSet={`${item.src}.webp`} type="image/webp" />
<img referrerPolicy="no-referrer" {...imageProps} />
</picture>
)}
/>

最终 DOM 里必须有一个展开了 imageProps<img>:它包含图片地址、 尺寸样式、加载/错误处理,以及变换和重试所需的回调。想保留预设界面时,可把同一个 函数传给 <ImageView.DefaultContent renderImage={...} />

部件 渲染时机
<ImageView.Loading> status === 'loading'
<ImageView.Error> status === 'error'。children 可以是渲染函数:{({ retry }) => …}
<ImageView.Title> 始终渲染 —— 当前图片的 name,回退到 alt
<ImageView.Counter> 一旦渲染就始终显示 —— 默认不在 DefaultContent 里。形如 "3 / 8",也可以传渲染函数 {({ index, total }) => …}

下面每个控件都是 <button>Download<a>), 接受 asChild 和标准的 button/anchor 属性, 并在相关时暴露 data-active / data-boundary / disabled

组件 data-image-view-control 行为
Close close api.close()
Prev / Next prev / next api.prev() / api.next();在两端会带 data-boundarydisabled
ZoomIn / ZoomOut zoom-in / zoom-out api.zoomBy(1.4) / api.zoomBy(1/1.4);到达缩放上下限时 disabled
RotateLeft / RotateRight rotate-left / rotate-right api.rotate(∓90)
FitToWindow fit api.fit()scale === fitScale 时带 data-active
ActualSize actual-size api.actualSize()scale === 1 时带 data-active
Download download 一个 <a>href/download 取自当前图片

FitToWindowActualSize 刻意做成两个按钮而不是分段控件 —— 分段控件的前提是总有一项处于选中状态,而缩放到中间值时两者都不是当前状态。

需要显式启用 —— DefaultContent 不会自动渲染它。

属性 类型 默认值
mode 'auto' | 'always' | 'never' 'auto'

auto 在只有一张图时不渲染,并且无论图片数量多少, 在视口宽度小于 640px 时通过 CSS 隐藏 (在那个尺寸下,缩略图轨要占掉手机约 15% 的高度,作用却有限)。 always 会同时覆盖这两条规则。

没有提供自己的 Content 时,Group 自动渲染的就是它。 也可以显式使用,比如只想打开 counter/thumbnails 而不想自己把界面全部拼一遍:

属性 类型 默认值
counter boolean false
thumbnails boolean | 'auto' | 'always' | 'never' false —— 传 true 等价于 'auto'
renderImage Image 相同

默认错误态刻意保持紧凑:一行错误文案和一个只显示图标的重试按钮。按钮的无障碍 名称以及其余面向用户的文案都来自 Group 上的 labels

返回实时的 ViewerApi —— 在 Group 内部的任何组件里调用都是安全的。

interface ViewerApi {
readonly index: number
readonly total: number
readonly open: boolean
readonly transform: Transform // { scale, x, y, rotation }
readonly scale: number
readonly rotation: number
readonly fitScale: number
readonly canZoomIn: boolean
readonly canZoomOut: boolean
readonly canPrev: boolean
readonly canNext: boolean
readonly status: 'idle' | 'loading' | 'ready' | 'error'
zoomTo(scale: number, options?: { origin?: { x: number; y: number }; immediate?: boolean }): void
zoomBy(factor: number): void
fit(): void
actualSize(): void
rotate(degrees: number): void
go(index: number): void
next(): void
prev(): void
close(): void
retry(): void
}

scale/rotation/平移位置刻意没有做成受控属性 —— 它们在手势过程中每一帧都在变,做成受控会把 60fps 的渲染循环 强加给所有使用者。用 useViewer() 读取,用上面的方法驱动。

返回合并后的 ViewerLabels:调用方传入的覆盖项已合并进默认值。 在自定义控件里用它,就能和内置控件从同一个 labels 属性取词。

interface ImageItem {
src: string
alt?: string
/** 由 Title 显示;未提供时回退到 alt。 */
name?: string
/** 省掉一次测量往返,避免加载时布局跳动。 */
width?: number
height?: number
/** 默认等于 src。下载需要提供另一个文件时设置。 */
downloadUrl?: string
}

这个库可能渲染出的所有文案,集中在一份配置里。errorTitle 是默认界面里 唯一还会显示成文字的一项,其余都是图标按钮上的 aria-label——不翻译它们 属于无障碍缺陷而不是外观问题:无论页面的 lang 声明成什么,读屏软件都会 照字面朗读。

不传 labels 时,Group 始终使用稳定的英文默认值,保证服务端输出与 hydration 完全一致,并把语言选择交给应用。把 labels 传给 Group(或 <ImageView>)即可覆盖;它按字段合并,没覆盖的字段继续使用英文默认值。

interface ViewerLabels {
viewer: string // dialog 上的 aria-label
close: string
prev: string
next: string
zoomIn: string
zoomOut: string
rotateLeft: string
rotateRight: string
fitToWindow: string
actualSize: string
download: string
retry: string
thumbnails: string // 缩略图轨上的 aria-label
thumbnailAt(index: number): string
errorTitle: string // 唯一会显示成文字的一项
loading: string
}

主入口和 primitives 入口导出 en。简体中文改为按需入口,不会增加默认包体积:

import zhCN from 'react-img-view/locales/zh-CN'
function ChineseViewer({ images }) {
return <ImageView.Group images={images} labels={zhCN} />
}

组合方式够不着的键盘行为才用它。任何需要渲染的东西都应该放进组件树; 指针手势继续由 Stage 里经过测试的状态机统一管理。

interface Extension {
name: string
onKeyDown?(event: KeyboardEvent, api: ViewerApi): boolean | void
}

返回 true 表示事件已被消费。通过 Groupextensions 属性传入数组。

同名的同一批部件,但不带预设。GroupImageView 都是主入口对应组件的 headless 版本:Group 不会自动注入 DefaultContentImageView 只是触发器, 必须放在 Group 里,不会自己撑起一个预览器。那个降级正是把预设拉进来的东西, 所以它不在这个入口。

引入和组合方式与主入口完全一致 —— 同样的名字、同样的形状,只有引入路径不同:

import { ImageView } from 'react-img-view/primitives'
function Gallery({ images }) {
return (
<ImageView.Group images={images}>
{images.map((image, i) => (
<ImageView key={image.src} index={i} {...image}>
<img src={image.src} alt={image.name} />
</ImageView>
))}
</ImageView.Group>
)
}

从这个入口引入 ImagePreview,就能用函数调用打开预览器。它不会增加常规入口的 体积:

import { ImagePreview } from 'react-img-view/imperative'
const viewer = ImagePreview.open({ images, index: 0 })
viewer.go(2)
viewer.next()
viewer.prev()
viewer.close()

open() 接收包含 images: ImageItem[] 和可选初始序号的配置对象。再次调用会替换当前预览; 传入空数组时不会打开,序号越界时会自动调整到有效范围。无论通过句柄、预设 关闭按钮还是 Escape 关闭,预览器都会自动清理。请只在点击事件、Effect 等浏览器环境中调用。

open() 参数 类型 默认值
images ImageItem[] 要打开的图片。
index number 0 初始序号,越界时自动调整。
content ReactNode 预设 ImageView.Content 等部件组合的自定义界面。
counter boolean false 是否显示预设计数器。
thumbnails boolean | 'auto' | 'always' | 'never' false 是否显示预设缩略图轨。
renderImage 函数 ImageView.Image 相同的图片渲染函数。
container / extensions / labels Group 相同 转发给内部托管的 Group

返回的句柄只控制这次调用创建的预览;之后再次 open() 时,旧句柄不会误操作新预览。 ImagePreview.close() 关闭当前函数式预览。自定义 content 内仍然可以使用 useViewer() 读取状态、控制缩放和旋转。函数式调用没有对应的缩略图位置,因此会 直接打开,不播放缩略图到大图的过渡动画。

传入 content 会替换整个默认界面,因此 counterthumbnailsrenderImage 只在使用预设时生效。自定义 content 时,请直接在其中组合相应部件和图片渲染方式。

手势数学和状态机,导出给需要在 React 之外复用这些能力的场景:

import { fitScale, tuning } from 'react-img-view/core'

这个入口没有 React 依赖,也能独立 tree-shake;为什么保持这种边界,看 src/core/ 的源码。