API 文档
按引入方式分组:组件、钩子、类型,以及独立发布的入口。
<ImageView>
Section titled “<ImageView>”包裹一张缩略图,点击它打开预览器。它会根据自己所处的位置决定行为:
- 在
Group里面:作为触发器。图片集合、打开状态和界面都归Group管, 所以它下面的每个ImageView打开的是同一个预览器,彼此之间可以翻页。 - 单独使用时:没有可共享的对象,于是它自己补上
Group和默认界面。 这就是一行接入的写法。
因此从单图扩成多图是纯追加的——把现有的 ImageView 包进一个 Group 即可,
不需要改任何名字。
ImageItem 加上:
| 属性 | 类型 | |
|---|---|---|
children |
ReactElement |
用来打开预览器的元素。始终复用唯一的子元素;ImageView 不接受 asChild 属性。 |
index |
number |
在整组里的位置。不传时按注册顺序 —— 虚拟列表,或任何挂载顺序与视觉顺序可能不一致的场景,请显式传入。 |
disabled |
boolean |
|
container / labels / renderImage |
与 Group 相同 |
只有在外层没有 Group 时才会读取,因为那是它唯一自己拥有预览器的情况。放在 Group 里传会在开发环境告警。 |
它同时也是命名空间:各个部件都挂为静态属性(ImageView.Group、
ImageView.Content……),也都有独立的具名导出。要自己拼界面,从 primitives
入口取同一批部件 —— 见其它入口。
<ImageView.Group>
Section titled “<ImageView.Group>”| 属性 | 类型 | 默认值 | |
|---|---|---|---|
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。 |
<ImageView.Content>
Section titled “<ImageView.Content>”Portal 出去的 <dialog>。它不额外渲染任何东西 ——
传子节点来自己组合布局,或者干脆不写,让 Group 补上
DefaultContent。
使用主入口时,请让 Content 或 DefaultContent 成为 Group 的直接子节点;
只有这一层会参与是否补上预设的判断。primitives 入口的 Group 从不补预设。
Header、Toolbar、Footer —— 纯粹的定位容器,
都是支持 asChild 的 <div>,本身没有任何行为。
<ImageView.Stage>
Section titled “<ImageView.Stage>”手势承载面。Image、Prev/Next、Loading、Error 都应该放在它里面 ——
预设里工具栏的 position: absolute 正是相对 stage 定位的,
这样放在它下面的东西(说明文字、Thumbnails)不会把工具栏挤出画面。
<ImageView.Image>
Section titled “<ImageView.Image>”渲染当前图片以及紧邻的前后两张(手势进行中翻页不必等待挂载)。
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-boundary 和 disabled |
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 取自当前图片 |
FitToWindow 和 ActualSize 刻意做成两个按钮而不是分段控件 ——
分段控件的前提是总有一项处于选中状态,而缩放到中间值时两者都不是当前状态。
<ImageView.Thumbnails>
Section titled “<ImageView.Thumbnails>”需要显式启用 —— DefaultContent 不会自动渲染它。
| 属性 | 类型 | 默认值 |
|---|---|---|
mode |
'auto' | 'always' | 'never' |
'auto' |
auto 在只有一张图时不渲染,并且无论图片数量多少,
在视口宽度小于 640px 时通过 CSS 隐藏
(在那个尺寸下,缩略图轨要占掉手机约 15% 的高度,作用却有限)。
always 会同时覆盖这两条规则。
<ImageView.DefaultContent>
Section titled “<ImageView.DefaultContent>”没有提供自己的 Content 时,Group 自动渲染的就是它。
也可以显式使用,比如只想打开 counter/thumbnails
而不想自己把界面全部拼一遍:
| 属性 | 类型 | 默认值 |
|---|---|---|
counter |
boolean |
false |
thumbnails |
boolean | 'auto' | 'always' | 'never' |
false —— 传 true 等价于 'auto' |
renderImage |
与 Image 相同 |
— |
默认错误态刻意保持紧凑:一行错误文案和一个只显示图标的重试按钮。按钮的无障碍
名称以及其余面向用户的文案都来自 Group 上的 labels。
useViewer()
Section titled “useViewer()”返回实时的 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() 读取,用上面的方法驱动。
useLabels()
Section titled “useLabels()”返回合并后的 ViewerLabels:调用方传入的覆盖项已合并进默认值。
在自定义控件里用它,就能和内置控件从同一个 labels 属性取词。
ImageItem
Section titled “ImageItem”interface ImageItem { src: string alt?: string /** 由 Title 显示;未提供时回退到 alt。 */ name?: string /** 省掉一次测量往返,避免加载时布局跳动。 */ width?: number height?: number /** 默认等于 src。下载需要提供另一个文件时设置。 */ downloadUrl?: string}ViewerLabels
Section titled “ViewerLabels”这个库可能渲染出的所有文案,集中在一份配置里。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} />}Extension
Section titled “Extension”组合方式够不着的键盘行为才用它。任何需要渲染的东西都应该放进组件树; 指针手势继续由 Stage 里经过测试的状态机统一管理。
interface Extension { name: string onKeyDown?(event: KeyboardEvent, api: ViewerApi): boolean | void}返回 true 表示事件已被消费。通过 Group 的 extensions 属性传入数组。
react-img-view/primitives
Section titled “react-img-view/primitives”同名的同一批部件,但不带预设。Group 和 ImageView 都是主入口对应组件的
headless 版本:Group 不会自动注入 DefaultContent,ImageView 只是触发器,
必须放在 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> )}react-img-view/imperative
Section titled “react-img-view/imperative”从这个入口引入 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 会替换整个默认界面,因此 counter、thumbnails 和 renderImage
只在使用预设时生效。自定义 content 时,请直接在其中组合相应部件和图片渲染方式。
react-img-view/core
Section titled “react-img-view/core”手势数学和状态机,导出给需要在 React 之外复用这些能力的场景:
import { fitScale, tuning } from 'react-img-view/core'这个入口没有 React 依赖,也能独立 tree-shake;为什么保持这种边界,看
src/core/ 的源码。