跳转到内容

快速开始

包裹任意元素即可 —— 缩略图可以是原生 <img>next/image<picture>, 或者你自己设计系统里的组件。ImageView 只负责挂上行为和 ref, 它不会替你渲染触发用的 <img>

import { ImageView } from 'react-img-view'
import zhCN from 'react-img-view/locales/zh-CN'
import 'react-img-view/styles.css'
function SingleImagePreview({ image }: { image: { thumb: string; full: string; name: string } }) {
return (
<ImageView src={image.full} alt={image.name} name={image.name} labels={zhCN}>
<img src={image.thumb} alt={image.name} />
</ImageView>
)
}
Woman holding a vintage camera
camera-portrait.webp

Group 会把它下面所有的 ImageView 收集成一组。点击任意一个都会打开预览器并定位到那张图, 方向键和滑动可以在其余图片之间切换。这里没有写 Content —— Group 会自动补上同一套默认界面。

import { ImageView } from 'react-img-view'
import zhCN from 'react-img-view/locales/zh-CN'
import 'react-img-view/styles.css'
function ImageList({ images }: { images: { src: string; name: string }[] }) {
return (
<ImageView.Group images={images} labels={zhCN}>
<div className="grid grid-cols-4 gap-3">
{images.map((image, i) => (
<ImageView key={image.src} index={i} {...image}>
<img src={image.src} alt={image.name} className="h-24 w-full object-cover" />
</ImageView>
))}
</div>
</ImageView.Group>
)
}
Woman holding a vintage camera
camera-portrait.webp
Steam rising across a green geothermal landscape
geothermal-landscape.webp
Woman writing in a journal in a sunlit forest
forest-journal.webp
Star-filled night sky above a forest
starry-night.webp

两者默认都关闭:图片不多时,左右箭头已经足够说明位置。 当图片数量较多、仅靠箭头说明不了情况时再打开:

<ImageView.Group images={images}>
{/* triggers */}
<ImageView.DefaultContent counter thumbnails />
</ImageView.Group>

如果不想自己维护 openindex,可以直接调用函数式 API:

import { ImagePreview } from 'react-img-view/imperative'
import zhCN from 'react-img-view/locales/zh-CN'
function ViewButton({ images }) {
return (
<button onClick={() => ImagePreview.open({ images, index: 2, labels: zhCN })}>
查看第 3 张
</button>
)
}

调用时传入图片数组和初始序号。再次调用会替换当前预览,并返回带有 close()go()next()prev() 的句柄。ImagePreview.close() 可以关闭 当前预览;关闭后会自动清理。

函数式调用没有对应的缩略图元素,因此会直接打开预览器,不播放缩略图到大图的过渡动画。

从应用里的任何地方打开预览器 —— 比如表格行里的「查看」按钮 —— 自己控制 openindex 就行。当外围组件本来就需要持有这两个值时,优先用这种形式:

const [open, setOpen] = useState(false);
const [index, setIndex] = useState(0);
<ImageView.Group
images={images}
open={open}
index={index}
onOpenChange={setOpen}
onIndexChange={setIndex}
>
{/* 以编程方式打开时,triggers 是可选的 */}
</ImageView.Group>
<Button onClick={() => { setIndex(2); setOpen(true); }}>
查看第 3 张
</Button>

DefaultContent 所使用的同一批部件,自己拼出界面。控件和布局区域支持 asChildImageView 始终直接复用自己的子元素;每个控件都会暴露 data-active / data-boundary / data-disabled 供你写样式。

使用主入口时,请让 ContentDefaultContent 成为 Group 的直接子节点; Group 正是通过这一层判断是否还需要补上默认界面。

import { ImageView } from 'react-img-view/primitives'
import zhCN from 'react-img-view/locales/zh-CN'
function CustomViewer({ images }) {
return (
<ImageView.Group images={images} labels={zhCN}>
{images.map((image, i) => (
<ImageView key={image.src} index={i} {...image}>
<img src={image.src} alt={image.name} />
</ImageView>
))}
<ImageView.Content className="riv-dialog">
<ImageView.Header className="riv-header">
<ImageView.Close>关闭</ImageView.Close>
<ImageView.Title />
<ImageView.Download>下载</ImageView.Download>
</ImageView.Header>
<ImageView.Stage className="riv-stage">
<ImageView.Image />
<ImageView.Prev></ImageView.Prev>
<ImageView.Next></ImageView.Next>
<ImageView.Loading>载入中…</ImageView.Loading>
<ImageView.Error>
{({ retry }) => (
<div>
<p>无法加载这张图片</p>
<button onClick={retry}>重试</button>
</div>
)}
</ImageView.Error>
<ImageView.Toolbar className="riv-toolbar">
<ImageView.ZoomOut />
<ImageView.ZoomIn />
<ImageView.RotateLeft />
<ImageView.RotateRight />
<ImageView.FitToWindow />
<ImageView.ActualSize />
</ImageView.Toolbar>
</ImageView.Stage>
</ImageView.Content>
</ImageView.Group>
)
}

useViewer()Group 内部任意位置都能用 —— 自定义控件、状态展示、埋点:

import { useViewer } from 'react-img-view/primitives'
function ZoomReadout() {
const viewer = useViewer()
return <span>{Math.round(viewer.scale * 100)}%</span>
}

useViewer() 的完整返回值和每个部件的属性,见 API 文档

按键 操作
/ 上一张 / 下一张
Home / End 第一张 / 最后一张
+ / - 放大 / 缩小
0 适应窗口
1 原始尺寸(1:1)
R / Shift+R 向右 / 向左旋转
Esc 关闭