// 主入口 (浏览器 / 现代 bundler)
import Zmage from 'react-zmage'
import 'react-zmage/style.css'
// SSR / RSC 入口 (Next.js App Router / Remix Server Components)
import Zmage from 'react-zmage/ssr'
// 三种使用方式
<Zmage src="..." /> // 1. 替换 <img>
Zmage.browsing({ src: '...' }) // 2. 命令式调用; 返回 destructor
<Zmage.wrapper>{htmlWithImgTags}</Zmage.wrapper> // 3. 自动包裹 children 中的 <img>| 默认导出 | Zmage — forwardRef 组件,ref 转发到内部 <img> |
| 静态方法 | Zmage.browsing(props) | Zmage.wrapper |
| 主类型 | BaseType (从 react-zmage 导出,含全部 props 联合) |
| 样式入口 | react-zmage/style.css (必须显式 import) |
| SSR 入口 | react-zmage/ssr |
| Peer Deps | react: >=16.8.0 <20, react-dom: >=16.8.0 <20 |
本地:
git clone https://github.com/Caldis/react-zmage
cd react-zmage
pnpm install
pnpm devpnpm add react-zmage
# 或
npm i react-zmage
# 或
yarn add react-zmage需要预先安装 react / react-dom(peer dependency):
pnpm add react react-dom| React | 状态 | 实现 |
|---|---|---|
| 16.8 ~ 17.x | ✅ 完全支持 | 走 ReactDOM.render |
| 18.x | ✅ 完全支持 | 自动检测并使用 createRoot |
| 19.x | ✅ 完全支持 | 必须使用 createRoot(已自动适配) |
库内部用运行时 feature detection 选择 mount API,无需消费方做任何配置。具体见 Zmage.callee.tsx 的 resolveMountAdapter。
import Zmage from 'react-zmage'
import 'react-zmage/style.css'
export default function Gallery() {
return <Zmage src="/photo.jpg" alt="风景" />
}把
<img>替换成<Zmage>即可。点击图片进入查看器。
不依赖封面图,直接弹出查看器:
import Zmage from 'react-zmage'
function Trigger() {
return (
<button onClick={() => Zmage.browsing({ src: '/photo.jpg' })}>
打开查看器
</button>
)
}Zmage.browsing 接受与 <Zmage> 完全相同的 props,并返回一个 () => void 的 destructor 函数(手动关闭用)。
适合渲染 markdown / 富文本输出,自动给所有 <img> 加查看功能:
<Zmage.wrapper>
<article dangerouslySetInnerHTML={{ __html: htmlContent }} />
</Zmage.wrapper>完整类型支持,支持泛型 ref 转发:
import Zmage from 'react-zmage'
import type { BaseType } from 'react-zmage'
import 'react-zmage/style.css'
import { useRef } from 'react'
export function App() {
const imgRef = useRef<HTMLImageElement>(null)
const config: BaseType = {
src: '/photo.jpg',
alt: '示例',
onBrowsing: (state) => console.log('browsing:', state),
}
return <Zmage {...config} ref={imgRef} />
}Next.js App Router / Remix 等 Server Components 环境,使用 SSR 友好入口:
import Zmage from 'react-zmage/ssr'
import 'react-zmage/style.css'API 完全一致,仅产物为 platform-neutral,避免 SSR 阶段引用浏览器 API。
类型签名约定:表中的
类型列均为 TypeScript 字面量。?表示可选。
最少了解这 5 个就能用:
| 配置项 | 类型 | 默认 | 说明 |
|---|---|---|---|
src |
string |
"" |
图片 URL,等同于 <img> 的 src |
alt |
string |
"" |
图片占位文字,等同于 <img> 的 alt |
txt |
string |
"" |
图片描述文本(除标题外的二级文案,可选) |
set |
Set[] |
[] |
多图集合,传入后启用浏览模式(左右翻页) |
defaultPage |
number |
0 |
多图模式下的初始页码 |
| 配置项 | 类型 | 默认 | 说明 |
|---|---|---|---|
preset |
'desktop' | 'mobile' |
'desktop' |
端预设。决定 controller / hotKey / animate 的默认值集合(详见预设表) |
⚠️ 旧值'auto'已废弃,会 fallback 到'desktop'并打 warning。
| 配置项 | 类型 | 默认 | 说明 |
|---|---|---|---|
browsing |
boolean |
(uncontrolled) | 显式控制查看器开关。设置后由父组件全权管理状态,需配合 onBrowsing 接收变更。不传则组件自治。 |
| 配置项 | 类型 | 默认 | 说明 |
|---|---|---|---|
controller |
boolean | ControllerSet |
preset 决定 | 顶部工具栏按钮显隐 |
hotKey |
boolean | HotKey |
preset 决定 | 键盘快捷键开关 |
animate |
boolean | Animate |
preset 决定 | 动画行为 |
interface ControllerSet {
pagination?: boolean | ReactNode // 多页指示器
zoom?: boolean | string | ReactNode // 缩放按钮
download?: boolean | string | ReactNode // 下载按钮
close?: boolean | string | ReactNode // 关闭按钮
rotate?: boolean | string | ReactNode // 旋转 (左+右组合按钮)
rotateLeft?: boolean | string | ReactNode // 仅左旋
rotateRight?: boolean | string | ReactNode // 仅右旋
flip?: boolean | string | ReactNode // 翻页 (左+右)
flipLeft?: boolean | string | ReactNode // 仅上一页
flipRight?: boolean | string | ReactNode // 仅下一页
}| 字段 | desktop | mobile |
|---|---|---|
pagination |
true |
true |
rotate |
true |
false |
zoom |
true |
false |
download |
false |
false |
close |
true |
true |
flip |
true |
false |
interface HotKey {
close?: boolean // ESC 关闭
zoom?: boolean // 空格缩放
flip?: boolean // 左右键翻页
}桌面端默认全开;移动端默认全关。
interface Animate {
browsing?: boolean // 进入/退出动画 (暂不可配, 仅占位)
flip?: 'fade' | 'crossFade' | 'swipe' | 'zoom' // 翻页动画
}
⚠️ 当set.length < 3时,flip强制为'fade'(避免单/双图边图为空时的视觉破绽)。默认值:desktop =
'fade',mobile ='swipe'。
| 配置项 | 类型 | 默认 | 说明 |
|---|---|---|---|
hideOnScroll |
boolean |
true |
桌面端:滚动时是否自动关闭查看器(移动端无效) |
coverVisible |
boolean |
false |
桌面端:放大期间是否保留封面图占位(默认会隐藏避免动画穿帮) |
backdrop |
string |
"#FFFFFF" |
查看器背景色(接受任何合法 CSS color / gradient) |
zIndex |
number |
1000 |
Portal 容器的 z-index |
radius |
number |
desktop:0 / mobile:0 |
查看模式下图片圆角 (px) |
edge |
number |
desktop:0 / mobile:0 |
图片距屏幕边缘的留白 (px) |
loop |
boolean |
true |
多图模式:尾页是否循环回首页 |
| 配置项 | 签名 | 触发时机 |
|---|---|---|
onBrowsing |
(isBrowsing: boolean) => void |
进入/退出查看模式 |
onZooming |
(isZooming: boolean) => void |
放大/缩小切换 |
onSwitching |
(page: number) => void |
翻页时回传新页码 |
onRotating |
(deg: number) => void |
旋转时回传当前角度 |
所有 props 通过单一交叉类型 BaseType 暴露:
export type BaseType =
& BaseParams // src / alt / txt / set / defaultPage
& PresetParams // preset
& FunctionalParams // controller / hotKey / animate
& InterfaceAndInteractionParams // hideOnScroll / coverVisible / backdrop / zIndex / radius / edge / loop
& LifeCycleParams // onBrowsing / onZooming / onSwitching / onRotating
& ControlledParams // browsing
& HTMLAttributes<HTMLImageElement> // 透传给底层 <img> 的全部原生属性 (className, style, onClick, ...)子类型完整定义见 packages/core/src/types/global.ts。
<Zmage
src="/cover.jpg"
set={[
{ src: '/01.jpg', alt: '页 1', style: { borderRadius: 30 }, className: 'custom' },
{ src: '/02.jpg', alt: '页 2' },
]}
/>设置
set后,进入查看模式的首图来自set[defaultPage],不再是src。如果需要让封面与查看模式共享一张图,把它放进set[0]并保留src。
按需关闭部分按钮:
<Zmage
src="/x.jpg"
controller={{
download: true, // 启用下载
rotate: false, // 关闭旋转
}}
/><Zmage src="/x.jpg" hotKey={{ flip: false }} /> // 禁用左右键翻页<Zmage src="/x.jpg" set={[...]} animate={{ flip: 'crossFade' }} /><Zmage src="/x.jpg" backdrop="linear-gradient(90deg, #00d4ff 0%, #1a5ed7 100%)" />const [open, setOpen] = useState(false)
return (
<>
<button onClick={() => setOpen(true)}>查看</button>
<Zmage
src="/x.jpg"
browsing={open}
onBrowsing={setOpen}
/>
</>
)<Zmage
src="/x.jpg"
onBrowsing={(state) => console.info('Browsing:', state)}
onZooming={(state) => console.info('Zooming:', state)}
onSwitching={(page) => console.info('Switching to page:', page)}
onRotating={(deg) => console.info('Rotating:', deg, 'deg')}
/>仓库布局是 pnpm + turbo 单仓多包:
packages/core— 发布到 npm 的react-zmage库packages/home— CSR 演示站源码(生产产物在docs/,可切换 React 版本)packages/sandbox-r{17,18,19}— 真实 npm 消费者类型 + SSR runtime smokepackages/sandbox-nextjs— Next.js 15 + RSC 真实消费者 build smokeapps/demo-ssr— Express + Vite SSR 演示(R19)apps/demo-nextjs— Next.js 15 App Router 演示
常用命令:
pnpm install # 安装所有 workspace 依赖
pnpm build # 构建 core 与 home
pnpm test # 跑单元测试
pnpm -w run check # 完整跨版本兼容性: build → pack → reinstall → 4 sandbox tsc + ssr-smoke
# 交互式 demo (用于人工验收 GUI / 动画 / 交互)
pnpm dev:csr-r17 # CSR · Vite SPA · React 17
pnpm dev:csr-r18 # CSR · Vite SPA · React 18
pnpm dev:csr-r19 # CSR · Vite SPA · React 19
pnpm dev:ssr-r19 # SSR · Express + renderToString · React 19 (:8090)
pnpm dev:nextjs # RSC · Next.js App Router · React 19 (:8095)每个 demo 顶部会显示 ContextBanner,标明当前实际加载的 React 版本与渲染模式,便于在切换不同环境时确认上下文。
- 图标来源:Material Icons