Skip to content

文档

安装

bash
pnpm add viewer-pro

也可以用 npm install viewer-pro / yarn add viewer-pro。样式内联在构建产物里,无需单独引入 CSS。

快速上手

typescript
import { ViewerPro, type ViewerItem } from 'viewer-pro'

const images: ViewerItem[] = [
  { src: 'https://example.com/1.jpg', thumbnail: 'https://example.com/1-s.jpg', title: '图片 1' },
  { src: 'https://example.com/2.jpg', thumbnail: 'https://example.com/2-s.jpg', title: '图片 2' },
]

const viewer = new ViewerPro({ images })
viewer.open(0)

Vue、React 等框架里,自己渲染图片列表,点击时调用 viewer.open(index) 即可。

使用 init() 自动绑定

如果你用原生 HTML,给图片容器加上 .viewer-pro-item 类名,调用 init() 即可自动绑定点击打开预览:

html
<div class="viewer-pro-item"><img src="thumb1.jpg" /></div>
<div class="viewer-pro-item"><img src="thumb2.jpg" /></div>
typescript
const viewer = new ViewerPro({ images })
viewer.init() // 自动按 DOM 顺序绑定点击 → open(index)

选择器可通过 itemSelector 自定义:

typescript
const viewer = new ViewerPro({ images, itemSelector: '.my-gallery-item' })
viewer.init()

完整 HTML 示例

html
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>ViewerPro Demo</title>
  <style>
    .image-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; max-width: 700px; }
    .viewer-pro-item { cursor: pointer; border-radius: 8px; overflow: hidden; }
    .viewer-pro-item img { width: 100%; height: 140px; object-fit: cover; display: block; }
  </style>
</head>
<body>
  <div class="image-grid">
    <div class="viewer-pro-item"><img src="https://picsum.photos/seed/120/240/160" /></div>
    <div class="viewer-pro-item"><img src="https://picsum.photos/seed/121/240/160" /></div>
    <div class="viewer-pro-item"><img src="https://picsum.photos/seed/122/240/160" /></div>
  </div>

  <script src="https://fastly.jsdelivr.net/npm/viewer-pro@latest/dist/ViewerPro.js"></script>
  <script>
    const viewer = new ViewerPro({
      images: [
        { src: "https://picsum.photos/seed/120/1200/800", thumbnail: "https://picsum.photos/seed/120/240/160", title: "图片 1" },
        { src: "https://picsum.photos/seed/121/1200/800", thumbnail: "https://picsum.photos/seed/121/240/160", title: "图片 2" },
        { src: "https://picsum.photos/seed/122/1200/800", thumbnail: "https://picsum.photos/seed/122/240/160", title: "图片 3" },
      ]
    });
    viewer.init();
  </script>
</body>
</html>

API

typescript
const viewer = new ViewerPro(options?: ViewerProOptions)

实例方法

方法说明
open(index)打开指定索引的预览
close()关闭预览
addImages(images)替换图片列表(不会立即请求缩略图)
getState()获取当前 scale / translate / rotation / index / image
onTransform(listener)订阅变换状态变化,返回取消订阅函数
notifyContentReady()手动通知内容就绪(自定义渲染场景)
closeLoading()手动关闭 loading
showInfoPanel() / hideInfoPanel() / toggleInfo()控制信息面板
showThumbnails() / hideThumbnails() / toggleThumbnailNav()控制缩略图导航
setTheme(theme) / getTheme()设置/获取主题 'dark' | 'light' | 'auto'
setZoomConfig(config) / getZoomConfig()设置/获取缩放配置
destroy()销毁实例,清理事件和资源
init()可选。自动给 itemSelector 匹配的元素绑定点击事件(默认 .viewer-pro-item);框架中推荐直接 open(index)

键盘快捷键

预览器激活时有效(keyboardShortcuts: false 可关闭):

按键功能
Esc关闭预览(信息面板打开时先关面板)
/ 上一张 / 下一张
+ / =放大
-缩小
0重置缩放
f全屏
d下载

配置

typescript
const viewer = new ViewerPro({
  images,
  theme: 'auto',               // 'dark' | 'light' | 'auto'
  backend: 'auto',             // 'auto' | 'css' | 'webgl'
  webglFiltering: 'linear',    // 'linear' | 'nearest'
  keyboardShortcuts: true,
  mobileSwipeToNavigate: true,
  mobileToolbar: ['zoomIn', 'zoomOut', 'reset', 'thumbnails', 'info'],
  preloadAdjacent: false,
  preloadCacheLimit: 5,
  zoomConfig: { min: 0.5, max: 5, step: 0.3 },
  swipeConfig: { maxDistance: 120, viewportRatio: 0.25, axisLockRatio: 1.2 },
})

想在线调整并实时预览效果,打开 配置实验台

自定义扩展

自定义 Loading

typescript
new ViewerPro({
  images,
  loadingNode: (item) => ({
    node: buildLoadingNode(item),
    done: (ctx) => {
      ctx.onImageLoaded(() => ctx.closeLoading())
      ctx.onImageError(() => ctx.closeLoading())
    },
  }),
})

done(context) 暴露:getImageLoadingStatus()getMediaLoadingStatus()onImageLoaded()onImageError()getCurrentImage()closeLoading()

自定义渲染

renderNode 完全接管预览 DOM。缩放/位移/旋转需要自己在 onTransformChange 里同步:

typescript
new ViewerPro({
  images,
  renderNode: (item, index) => {
    const box = document.createElement('div')
    box.id = `render-${index}`
    const img = document.createElement('img')
    img.src = item.src
    box.appendChild(img)
    return box
  },
  onTransformChange: ({ scale, translateX, translateY, rotation, index }) => {
    const el = document.getElementById(`render-${index}`)
    if (el) el.style.transform = `translate(${translateX}px,${translateY}px) scale(${scale}) rotate(${rotation}deg)`
  },
})

BlurHash 占位、Live Photo 等完整示例见 Live Photo 与 BlurHash

自定义信息面板

typescript
new ViewerPro({
  images,
  infoRender: (item, index) => {
    const el = document.createElement('div')
    el.innerHTML = `<div>标题:${item.title || '-'}</div><div>索引:${index + 1}</div>`
    return el
  },
})

viewer.showInfoPanel()
viewer.hideInfoPanel()
viewer.toggleInfo()

类型定义

ViewerItem

typescript
interface ViewerItem {
  src: string
  thumbnail?: string
  title?: string
  type?: string
  photoSrc?: string
  videoSrc?: string
  [key: string]: any
}

ViewerProOptions

typescript
interface ViewerProOptions {
  images?: ViewerItem[]
  loadingNode?: HTMLElement | ((item: ViewerItem, idx: number) => HTMLElement | LoadingNodeResult)
  renderNode?: HTMLElement | ((item: ViewerItem, idx: number) => HTMLElement)
  infoRender?: HTMLElement | ((item: ViewerItem, idx: number) => HTMLElement)
  theme?: 'dark' | 'light' | 'auto'
  zoomConfig?: ZoomConfig
  backend?: 'auto' | 'css' | 'webgl'
  webglFiltering?: 'linear' | 'nearest'
  toolbar?: ToolbarAction[]
  mobileToolbar?: ToolbarAction[]
  mobileSwipeToNavigate?: boolean
  swipeConfig?: SwipeConfig
  preloadAdjacent?: boolean
  preloadCacheLimit?: number
  keyboardShortcuts?: boolean
  itemSelector?: string
  onImageLoad?: (item: ViewerItem, idx: number) => void
  onContentReady?: (item: ViewerItem, idx: number) => void
  onTransformChange?: (state: TransformChangeState) => void
  onOpen?: (item: ViewerItem, idx: number) => void
  onClose?: () => void
  onIndexChange?: (item: ViewerItem, idx: number) => void
  onInfoPanelOpen?: (item: ViewerItem, idx: number) => void
  onInfoPanelClose?: (item: ViewerItem, idx: number) => void
}
选项说明默认值
images预览项数组[]
loadingNode自定义 loading 节点或工厂函数-
renderNode自定义预览内容渲染节点-
infoRender自定义信息面板渲染节点-
theme主题模式'dark'
backend渲染后端'auto'
webglFilteringWebGL 纹理过滤'linear'
toolbar桌面端工具栏按钮全部
mobileToolbar移动端工具栏按钮部分
mobileSwipeToNavigate移动端滑动切换true
preloadAdjacent预加载相邻原图false
preloadCacheLimit预加载缓存上限5
keyboardShortcuts键盘快捷键true
itemSelectorinit() 绑定点击的 CSS 选择器'.viewer-pro-item'

事件回调

回调说明
onImageLoad图片加载完成
onContentReady内容就绪(自定义渲染场景)
onTransformChange缩放/位移/旋转/索引变化
onOpen预览打开
onClose预览关闭
onIndexChange索引变化
onInfoPanelOpen / onInfoPanelClose信息面板打开/关闭

ZoomConfig

typescript
interface ZoomConfig {
  min?: number              // 最小缩放
  max?: number              // 最大缩放
  step?: number             // 按钮步长
  wheelBaseStep?: number    // 滚轮基础步长
  wheelMaxStep?: number     // 滚轮最大步长
  wheelSpeedMultiplier?: number // 速度系数
}

SwipeConfig

typescript
interface SwipeConfig {
  maxDistance?: number       // 触发切换的最大距离
  viewportRatio?: number    // 触发比例(相对视口宽度)
  axisLockRatio?: number    // 轴锁定比例
}

TransformChangeState

typescript
interface TransformChangeState {
  scale: number
  translateX: number
  translateY: number
  rotation: number
  index: number
  image: ViewerItem | null
}

LoadingContext

typescript
interface LoadingContext {
  getImageLoadingStatus: () => Promise<{ loaded: boolean; error?: string }>
  getMediaLoadingStatus: () => Promise<{ images: boolean[]; videos: boolean[]; audios: boolean[] }>
  onImageLoaded: (callback: () => void) => void
  onImageError: (callback: (error: string) => void) => void
  getCurrentImage: () => { image: ViewerItem; index: number }
  closeLoading: () => void
}

ToolbarAction

typescript
type ToolbarAction = 'zoomIn' | 'zoomOut' | 'reset' | 'rotateLeft' | 'rotateRight' | 'thumbnails' | 'fullscreen' | 'download' | 'info'

Released under the MIT License.