MCPcopy Index your code
hub / github.com/Caldis/react-zmage

github.com/Caldis/react-zmage @1.9.0

Chat with this repo
repository ↗ · DeepWiki ↗ · release 1.9.0 ↗ · + Follow
1,760 symbols 5,050 edges 176 files 3 documented · 0% updated 53d ago1.9.0 · 2026-05-18★ 937

Browse by type

Functions 1,680 Types & classes 80
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

English | 简体中文

react-zmage logo

react-zmage

把任意 <code>&lt;img&gt;</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> 原位展开替换。原生属性(classNamestyleonClick 等)全部透传到底层 <img>,图片会从原位置展开进入全屏查看器。
  • SSR / RSC 安全。提供独立的 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 <20react-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 属性(classNamestyleonClickloading 等)都会按原样转发到内部 <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> 上:presetcontrollerhotKeyanimategesturebackdropzIndexportalTargetradiusedgeloopcoverVisiblehideOnScrollhideOnDblClickloadingDelay 和生命周期回调。
  • 需要让包裹区内图片作为共享图库时传 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 的并集类型。子类型 ControllerSetControllerPlacementControllerRenderControllerRenderStateControllerRenderActionsControllerRenderSlotsHotKeyAnimateAnimateCoverOptionsGestureSetGestureSwipeOptionsGestureDragExitOptionsGestureWheelZoomOptionsGesturePinchZoomOptionsGestureDoubleTapZoomOptionsGestureTouchActionSetPresetAnimateFlip 也都从 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) 上验证。


API

所有 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

ControllerSet

interface 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
}

rotateflip 是组合开关 —— 启用 umbrella 时同时强制开启对应的两侧按钮,覆盖单侧标志。

backdropcolor 可让工具栏和 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=24caption.inset=60;mobile preset 不预置 layout,除非你显式传入。layout.mobile 会在解析到 mobile 时叠加。render 接收 { state, actions, slots } 并替换整个控制器层;slots.Toolbarslots.Paginationslots.FlipLeftslots.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

HotKey

type 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 / rotatedownload 默认关闭,开启后会接管浏览器的 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

Extension points exported contracts — how you extend this code

Core symbols most depended-on inside this repo

Shape

Function 1,625
Interface 58
Method 55
Class 22

Languages

TypeScript100%

Modules by API surface

docs/assets/index-O2Mv1Wm0.js1,244 symbols
packages/core/src/components/Image/Image.utils.ts63 symbols
packages/core/src/__tests__/Zmage.test.tsx47 symbols
packages/core/src/utils/index.ts34 symbols
packages/core/src/types/global.ts29 symbols
packages/home/src/playground/CodeSnippet.tsx18 symbols
packages/core/src/components/Browser/Browser.tsx17 symbols
packages/home/src/docs/sections/Props.tsx14 symbols
packages/core/src/components/Image/Image.tsx14 symbols
packages/core/src/components/Browser/Browser.utils.ts14 symbols
packages/core/src/utils/geometry.ts12 symbols
packages/home/src/routes/Home.tsx9 symbols

For agents

$ claude mcp add react-zmage \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact

Ask about this repo answers extend the page