Browse by type
English | 简体中文
把任意 <code><img></code> 变成可原位展开的全屏 React 图片查看器。
即插即用,零配置,兼容 React 16.8 → 19。
<a href="https://www.npmjs.com/package/react-zmage"><img alt="npm version" src="https://img.shields.io/npm/v/react-zmage.svg?style=flat-square&color=cb3837"></a>
<a href="https://www.npmjs.com/package/react-zmage"><img alt="npm downloads/month" src="https://img.shields.io/npm/dm/react-zmage.svg?style=flat-square"></a>
<a href="https://bundlephobia.com/package/react-zmage"><img alt="minzipped size" src="https://img.shields.io/bundlephobia/minzip/react-zmage?style=flat-square"></a>
<a href="#react-兼容性"><img alt="React 16.8 — 19" src="https://img.shields.io/badge/react-16.8%E2%80%9319-61dafb?style=flat-square"></a>
<a href="https://github.com/Caldis/react-zmage/raw/1.9.0/LICENSE"><img alt="MIT license" src="https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square"></a>
<a href="https://zmage.caldis.me">在线 Demo</a> ·
<a href="https://zmage.caldis.me/media/react-zmage-demo.mp4">演示视频</a> ·
<a href="https://zmage.caldis.me/playground">参数调试台</a> ·
<a href="https://zmage.caldis.me/use-cases">使用场景</a> ·
<a href="#api">API</a> ·
<a href="https://github.com/Caldis/react-zmage/raw/1.9.0/AGENTS.md">AGENTS.md</a>
<img> 原位展开替换。原生属性(className、style、onClick 等)全部透传到底层 <img>,图片会从原位置展开进入全屏查看器。react-zmage/ssr 入口,import 阶段不会触碰 document。已在 Next.js 15 App Router、Vite SSR、Express renderToString 上验证。Zmage.browsing()),或包裹任意 HTML 子树自动给内部所有 <img> 接上查看器。npm install react-zmage # 或: pnpm add react-zmage / yarn add react-zmage
import Zmage from 'react-zmage'
import 'react-zmage/style.css'
<Zmage src="https://github.com/Caldis/react-zmage/raw/1.9.0/photo.jpg" alt="风景" />
Peer deps:react@>=16.8 <20、react-dom@>=16.8 <20。库内部用运行时 feature detection 自动选择 mount API(React 18+ 用 react-dom/client),消费方无需配置。
AI Agent 应先阅读 https://zmage.caldis.me/llms.txt,然后保持基础接入最小化。
react-zmage 通过三种调用方式暴露相同的配置接口。选哪种取决于你对页面 HTML 的控制程度。
何时使用: 你完全控制要渲染的 JSX 时。这是最干净的路径,优先选这个。
import Zmage from 'react-zmage'
import 'react-zmage/style.css'
export default function Gallery() {
return <Zmage src="https://github.com/Caldis/react-zmage/raw/1.9.0/photo.jpg" alt="风景" />
}
所有原生 HTML 属性(className、style、onClick、loading 等)都会按原样转发到内部 <img>。
Zmage.browsing()何时使用: 你没有合适的封面 <img>,或者不希望在组件树里多挂载节点。从事件处理器、第三方回调、异步流程等任意位置弹出查看器。
import Zmage from 'react-zmage'
function Trigger() {
return (
<button onClick={() => Zmage.browsing({ src: '/photo.jpg' })}>
打开查看器
</button>
)
}
Zmage.browsing(opts) 接受与 <Zmage> 完全相同的 props,并返回一个 () => void 的 destructor 函数(用于手动关闭)。
如果可能在服务端代码路径中执行,记得加
typeof window !== 'undefined'保护。react-zmage/ssr入口提供同样的 API,import 时不触碰document。
<Zmage.Wrapper>何时使用: 渲染出的 HTML 不在你的控制之内时 —— markdown 输出、CMS 富文本、dangerouslySetInnerHTML。把这棵子树整个包起来,内部所有 <img> 自动获得查看能力,无需修改原始内容。
<Zmage.Wrapper backdrop="#0a0a0a">
<article dangerouslySetInnerHTML={{ __html: htmlContent }} />
</Zmage.Wrapper>
包裹器会在 componentDidMount / componentDidUpdate 期间查找子节点中的 <img>。包裹器渲染之后再注入的图片,需等到包裹器重新渲染时才会被绑定。
包裹器模式下的参数范围:
src / alt 应放在子级 <img> 上。顶层 src / alt 会被点击的 DOM 节点覆盖。<Zmage.Wrapper> 上:preset、controller、hotKey、animate、gesture、backdrop、zIndex、portalTarget、radius、edge、loop、coverVisible、hideOnScroll、hideOnDblClick、loadingDelay 和生命周期回调。set。若被点击图片的 src 出现在 set 中,Wrapper 会打开匹配索引;defaultPage 只作为兜底。set 时,被点击图片按单图打开。data-zmage-caption 或最近的 figcaption 可作为查看器 caption。browsing 属于组件模式,不能控制 <Zmage.Wrapper>。TypeScript
import Zmage from 'react-zmage'
import type { BaseType } from 'react-zmage'
import { useRef } from 'react'
const config: BaseType = {
src: '/photo.jpg',
alt: '示例',
onBrowsing: (state) => console.log('browsing:', state),
}
const ref = useRef<HTMLImageElement>(null)
return <Zmage {...config} ref={ref} />
BaseType 是所有 props 的并集类型。子类型 ControllerSet、ControllerPlacement、ControllerRender、ControllerRenderState、ControllerRenderActions、ControllerRenderSlots、HotKey、Animate、AnimateCoverOptions、GestureSet、GestureSwipeOptions、GestureDragExitOptions、GestureWheelZoomOptions、GesturePinchZoomOptions、GestureDoubleTapZoomOptions、GestureTouchAction、Set、Preset、AnimateFlip 也都从 react-zmage 导出。
SSR / RSC(Next.js、Remix)
import Zmage from 'react-zmage/ssr'
import 'react-zmage/style.css'
API 完全一致,仅 import 路径不同。SSR 产物为 platform-neutral,避免在模块加载阶段引用浏览器 API。已在 Next.js 15 App Router (packages/sandbox-nextjs) 与 Express + Vite renderToString (apps/demo-ssr) 上验证。
所有 props 都由单一的
BaseType暴露。<Zmage>与Zmage.browsing()接受同样的参数对象。
| 配置项 | 类型 | 默认 | 说明 |
|---|---|---|---|
src |
string |
— | 图片 URL,等同于 <img> 的 src。 |
alt |
string |
'' |
图片标题,查看模式时显示在大图上方。 |
caption |
string \| { text: string; style?: CSSProperties; className?: string } |
'' |
大图下方的辅助文案。string 形式取默认胶囊样式;对象形式可通过 style / className 覆盖样式或主题化。多图模式下可由 set[i].caption 单独覆盖。 |
set |
Set[] |
[] |
多图集合,传入后启用浏览模式(左右键翻页)。在包裹器模式下,传 set 表示包裹区内图片属于同一个共享图库;若被点击图片的 src 出现在 set 中,会打开匹配索引。 |
defaultPage |
number |
0 |
set 非空时的初始页索引(从 0 开始)。在包裹器模式下它只作为兜底;被点击图片能匹配 set[i].src 时,以匹配索引为准。 |
| 配置项 | 类型 | 默认 | 说明 |
|---|---|---|---|
preset |
'desktop' \| 'mobile' \| 'auto' |
'auto' |
端预设。决定 controller / hotKey / animate / gesture 以及 preset 相关查看器间距的默认值集合。不传时使用 'auto'。'auto' 走 CSS media query (pointer: coarse) and (hover: none) 判定:满足则取 mobile 默认,否则 desktop;SSR / 无 matchMedia 环境 fallback 到 desktop。触屏设备上如需保留桌面行为,可显式传 preset="desktop"。 |
| 配置项 | 类型 | 默认 | 说明 |
|---|---|---|---|
controller |
boolean \| ControllerSet |
preset 决定 | 顶部工具栏按钮显隐。传 false 关闭整组,或传部分对象覆盖按钮、工具栏位置、覆盖层间距,或替换整个控制器渲染函数。 |
hotKey |
boolean \| HotKey |
preset 决定 | 键盘快捷键开关。 |
animate |
boolean \| Animate |
preset 决定 | 打开 / 关闭、封面几何与翻页动画。 |
gesture |
boolean \| GestureSet |
preset 决定 | 触摸与滚轮手势。传 false 关闭所有手势,或传部分对象覆盖 swipe / dragExit / wheelZoom / pinchZoom / doubleTapZoom / touchAction。 |
ControllerSetinterface ControllerSet {
pagination?: boolean | ReactNode // 多页指示器
zoom?: boolean | string | ReactNode // 缩放按钮
download?: boolean | string | ReactNode
close?: boolean | string | ReactNode
rotate?: boolean | string | ReactNode // 组合开关,覆盖 rotateLeft + rotateRight
rotateLeft?: boolean | string | ReactNode
rotateRight?: boolean | string | ReactNode
flip?: boolean | string | ReactNode // 组合开关,覆盖 flipLeft + flipRight
flipLeft?: boolean | string | ReactNode
flipRight?: boolean | string | ReactNode
// visual
backdrop?: string // 控制栏背景;默认回退到顶层 backdrop
color?: string // 控制栏图标色;默认 currentColor
placement?: ControllerPlacement // 默认 'top-right'
layout?: ControllerOverlayLayout // toolbar / flip / pagination / caption 的覆盖层安全偏移
render?: ControllerRender // 替换整个控制器 UI
}
type ControllerPlacement =
| 'top-right'
| 'top-left'
| 'bottom-right'
| 'bottom-left'
| 'top-center'
| 'bottom-center'
| 'left-center'
| 'right-center'
type ControllerLayoutInsetValue = number | string
type ControllerLayoutInset =
| ControllerLayoutInsetValue
| {
top?: ControllerLayoutInsetValue
right?: ControllerLayoutInsetValue
bottom?: ControllerLayoutInsetValue
left?: ControllerLayoutInsetValue
}
interface ControllerLayoutTarget {
inset?: ControllerLayoutInset
}
interface ControllerLayoutTargets {
toolbar?: ControllerLayoutTarget
flip?: ControllerLayoutTarget
pagination?: ControllerLayoutTarget
caption?: ControllerLayoutTarget
}
interface ControllerOverlayLayout extends ControllerLayoutTargets {
mobile?: ControllerLayoutTargets
}
type ControllerRender = (args: {
state: ControllerRenderState
actions: ControllerRenderActions
slots: ControllerRenderSlots
}) => ReactNode
interface ControllerRenderState {
show: boolean
zoom: boolean
page: number
total: number
canZoom: boolean
canPrev: boolean
canNext: boolean
canDownload: boolean
preset: 'desktop' | 'mobile'
placement: ControllerPlacement
current?: Set
}
interface ControllerRenderActions {
close: () => void
zoom: () => void
rotateLeft: () => void
rotateRight: () => void
prev: () => void
next: () => void
toPage: (page: number) => void
download: () => void
}
interface ControllerRenderSlots {
Toolbar: ReactNode
Pagination: ReactNode
FlipLeft: ReactNode
FlipRight: ReactNode
}
rotate与flip是组合开关 —— 启用 umbrella 时同时强制开启对应的两侧按钮,覆盖单侧标志。
backdrop与color可让工具栏和 modal 背景解耦。深色背景时建议一起设置,例如backdrop="#111"+controller={{ backdrop: 'rgba(0,0,0,0.4)', color: '#fff' }},避免工具栏图标不可见。单个按钮的颜色字符串仍然优先于controller.color。
placement只移动工具栏胶囊。侧边翻页按钮和分页指示器仍保持原位置。layout只调整工具栏、侧边翻页按钮、分页器和 caption 的覆盖层安全偏移,不改变图片动画几何。数值按 px 处理,字符串按 CSS 长度透传;标量inset按目标自身进入方向生效。desktop preset 默认设置pagination.inset=24和caption.inset=60;mobile preset 不预置layout,除非你显式传入。layout.mobile会在解析到 mobile 时叠加。render接收{ state, actions, slots }并替换整个控制器层;slots.Toolbar、slots.Pagination、slots.FlipLeft、slots.FlipRight可复用内置 UI。controller={false}会同时关闭内置 slots 和render。
render 返回任意 React 节点。返回 null 可隐藏控制器层;调用 actions 驱动查看器;读取 state 与页码、缩放、位置和能力状态同步:
| 路径 | 类型 |
|---|---|
state |
ControllerRenderState |
state.show |
boolean |
state.zoom |
boolean |
state.page |
number |
state.total |
number |
state.canZoom |
boolean |
state.canPrev |
boolean |
state.canNext |
boolean |
state.canDownload |
boolean |
state.preset |
'desktop' \| 'mobile' |
state.placement |
ControllerPlacement |
state.current |
Set \| undefined |
actions |
ControllerRenderActions |
actions.close |
() => void |
actions.zoom |
() => void |
actions.rotateLeft |
() => void |
actions.rotateRight |
() => void |
actions.prev |
() => void |
actions.next |
() => void |
actions.toPage |
(page: number) => void |
actions.download |
() => void |
slots |
ControllerRenderSlots |
slots.Toolbar |
ReactNode |
slots.Pagination |
ReactNode |
slots.FlipLeft |
ReactNode |
slots.FlipRight |
ReactNode |
return |
ReactNode |
<Zmage
src="https://github.com/Caldis/react-zmage/raw/1.9.0/photo.jpg"
set={[
{ src: 'photo.jpg', alt: '封面' },
{ src: 'detail.jpg', alt: '细节' },
]}
controller={{
placement: 'bottom-center',
render: ({ state, actions, slots }) => {
if (!state.show) return null
return (
<button type="button" disabled={!state.canPrev} onClick={actions.prev}>
上一张
</button>
<span>
{state.page + 1} / {state.total}
</span>
<button type="button" disabled={!state.canNext} onClick={actions.next}>
下一张
</button>
<button type="button" disabled={!state.zoom && !state.canZoom} onClick={actions.zoom}>
{state.zoom ? '适应' : '放大'}
</button>
{state.canDownload && (
<button type="button" onClick={actions.download}>
下载
</button>
)}
<button type="button" onClick={actions.close}>
关闭
</button>
{slots.Pagination}
)
},
}}
/>
| 字段 | desktop | mobile |
|---|---|---|
pagination |
✅ | ✅ |
rotate |
✅ | — |
zoom |
✅ | — |
download |
— | — |
close |
✅ | ✅ |
flip |
✅ | — |
placement |
top-right |
top-right |
radius |
8 |
0 |
edge |
16 |
0 |
controller.layout.pagination.inset |
24 |
— |
controller.layout.caption.inset |
60 |
— |
gesture.swipe |
— | ✅ |
gesture.dragExit |
— | ✅ |
gesture.wheelZoom |
✅ | — |
gesture.pinchZoom |
— | ✅ |
gesture.doubleTapZoom |
— | ✅ |
gesture.touchAction |
managed |
managed |
HotKeytype HotKeyValue = boolean | string | string[]
// true — 使用默认绑定
// false — 禁用,事件会继续传给外部监听器
// string — 描述符: 'Escape' / 'BracketLeft' / 'S' / 'Mod+S'
// (使用 e.code 名称,与键盘布局无关;
// Mod = macOS 下 ⌘,其他平台 Ctrl)
// string[] — 多个绑定,任意一个匹配即触发
interface HotKey {
close?: HotKeyValue // 默认 'Escape'
zoom?: HotKeyValue // 默认 'Space'
flip?: boolean // flipLeft / flipRight 的组合开关
flipLeft?: HotKeyValue // 默认 'ArrowLeft'
flipRight?: HotKeyValue // 默认 'ArrowRight'
rotate?: boolean // rotateLeft / rotateRight 的组合开关
rotateLeft?: HotKeyValue // 默认 'BracketLeft' ([)
rotateRight?: HotKeyValue // 默认 'BracketRight' (])
download?: HotKeyValue // 启用后默认 'Mod+S'
}
桌面端默认开启 close / zoom / flip / rotate;download 默认关闭,开启后会接管浏览器的 Cmd / Ctrl+S。移动端默认全关。
修饰键严格匹配:'Space' 不会匹配 Cmd+Space(macOS 输入法切换);未声明的修饰键不能被按下。单侧字符串描述符优先于组合开关,例如 { rotate: true, rotateLeft: 'KeyA' } 会把左旋绑定到 A,右旋仍保持 ]。
Animate```ts interface Animate { browsing?: boolean flip?: 'fade' | 'crossFade' | 'swipe' | 'zoom' | 'blur' | 'none' cover?: boolean | AnimateCoverOptions }
interface AnimateCoverOptions { objectFit?: boolean // 默认 true clip?: boolean // 默认 true radius?: bo
$ claude mcp add react-zmage \
-- python -m otcore.mcp_server <graph>