
一、概述
1.1 关于 viewerJs
在现代 Web 应用中,图片展示功能是提升用户体验的关键环节。无论是电商平台的产品详情页、摄影网站的作品展示,还是企业官网的图片画廊,都需要一个功能完善、交互友好的图片查看器。viewer.js 作为一款轻量级的 JavaScript 库,专门用于实现图片查看和预览功能,支持模态弹窗、页面内联两种显示模式,内置缩放、旋转、翻转、拖拽移动等完整图片操作能力,同时适配桌面端键盘快捷键和移动端多点触控手势,兼容所有主流现代浏览器。

1.2 环境准备与安装
要开始使用 Viewer.js,首先需要将其集成到项目中。使用 npm 安装 Viewer.js 非常简单,只需运行以下命令:
bash
# 安装核心包
npm install viewerjs
# TS项目额外安装类型提示(可选)
npm install @types/viewerjs
1.3 引入与注册
安装完成后,如果项目中多个页面都需要使用图片裁剪,可以考虑全局注册。在 main.js 中添加以下代码:
javascript
import { createApp } from 'vue'
import App from './App.vue'
// 引入核心JS与样式
import Viewer from 'viewerjs'
import 'viewerjs/dist/viewer.css'
const app = createApp(App)
// 挂载全局实例,组件内直接使用
app.config.globalProperties.$Viewer = Viewer
app.mount('#app')
全局注册后,所有组件都可以直接使用、无需重复引入。但注意,这会导致组件始终被打包,如果只有少数页面使用,推荐使用局部引入,这能更好地减少打包体积。请注意,必须同时引入 CSS 样式文件,否则样式会错乱:
javascript
import Viewer from 'viewerjs';
import 'viewerjs/dist/viewer.css';
1.4 基础使用
-
引入核心文件:在项目中引入 viewerJs 及其 CSS 文件。
-
HTML 结构:需要为图片提供一个块级容器,单张或多张图片均可。单张图片需包裹在容器中,通过 id 或类名绑定。而多张图片这使用使用 ul 或 div 包裹多个 img 标签。
-
初始化Viewer实例:在Vue组件的 mounted 钩子函数中,创建 Viewer 对象,指定图片容器和配置选项。
javascriptnew Viewer(element[, options])
单图单独预览
这段代码实现了一个基础的图片查看功能,点击图片后会弹出模态框,提供缩放、旋转、翻转等操作。
html
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount } from 'vue'
import Viewer from 'viewerjs'
import 'viewerjs/dist/viewer.css'
const singleBox = ref()
const singleImg = ref('https://fengyuanchen.github.io/viewerjs/images/tibet-9.jpg')
let viewerInstance: Viewer;
onMounted(() => {
viewerInstance = new Viewer(singleBox.value)
})
onBeforeUnmount(() => viewerInstance?.destroy())
</script>
<template>
<div ref="singleBox">
<img :src="singleImg" alt="单图" />
</div>
</template>
基础多图预览
在网站中,详情页通常需要展示多张图片,可以在图片之间切换查看,下面实现一个多图浏览功能。
html
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount, nextTick, watch } from 'vue';
// 引入viewer核心与样式
import Viewer from 'viewerjs';
import 'viewerjs/dist/viewer.css';
const viewerBox = ref();
let viewerInstance: Viewer;
// 图片数据源
const imageList = ref([
'https://picsum.photos/id/10/400/300',
'https://picsum.photos/id/20/400/300',
'https://picsum.photos/id/30/400/300',
'https://picsum.photos/id/40/400/300',
'https://picsum.photos/id/50/400/300'
]);
// 初始化viewer
const initViewer = () => {
// 先销毁旧实例,防止重复创建
if (viewerInstance) viewerInstance.destroy();
nextTick(() => { // 等待DOM渲染完成
viewerInstance = new Viewer(viewerBox.value, {
zIndex: 9999, // 弹窗层级,避免被其他组件遮挡
});
});
}
// 监听图片列表变化(接口动态加载图片必备)
watch(imageList,initViewer, { deep: true });
// 初始化
onMounted(initViewer);
// 组件销毁释放实例,内存泄漏/路由残留弹窗
onBeforeUnmount(() => viewerInstance?.destroy());
</script>
<template>
<!-- 所有图片包裹在同一个父容器,绑定ref -->
<div class="img-container" ref="viewerBox">
<div v-for="(item, index) in imageList" :key="index" class="img-item">
<img :src="item" alt="预览图" />
</div>
</div>
</template>
<style scoped>
.img-container {
display: flex;
gap: 12px;
flex-wrap: wrap;
}
.img-item img {
width: 160px;
height: 120px;
object-fit: cover;
cursor: zoom-in;
}
</style>
二、配置项
2.1 基础显示配置
用于控制查看器中各个 UI 元素的显示与隐藏。
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| inline | boolean | false | 是否开启内嵌预览模式,false 开启弹窗模式 |
| button | boolean | true | 查看图片时是否显示右上角的关闭按钮 |
| backdrop | boolean | true | 是否启用模态背景遮罩。 设置为 static 时,点击背景不会关闭查看器。 |
| navbar | boolean、Visibility | true | 是否显示底部的缩略图 |
| toolbar | Boolean、Visibility、ToolbarOptions | true | 是否显示工具栏(数值控制响应式显示条件) |
| title | boolean、Visibility、Function | true | 是否显示当前图片的标题(默认读取 alt 属性及图片尺寸) |
| tooltip | boolean | true | 在缩放图片时,是否显示带有图片比例(百分比)的提示 |
| loading | boolean | true | 加载图片时是否显示加载动画 |
| className | string | 自定义类名,用于自定义样式 |
这里提一下有关 Visibility 的取值规则,如下表所示:
| 属性值 | 简要说明 |
|---|---|
| 0 | 隐藏工具栏 |
| 1 | 显示工具栏 |
| 2 | 当屏幕宽度大于 768 像素时显示工具栏 |
| 3 | 当屏幕宽度大于 992 像素时显示工具栏 |
| 4 | 当屏幕宽度大于 1200 像素时显示工具栏 |
2.2 交互与操作配置
用于定义用户与图片的交互方式。
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| movable | Boolean | true | 是否允许拖动、移动图片 |
| zoomable | Boolean | true | 是否允许缩放图片 |
| rotatable | Boolean | true | 是否允许旋转图片 |
| scalable | Boolean | true | 是否允许翻转图片(水平/垂直) |
| transition | Boolean | true | 是否使用 CSS3 过渡动画 |
| keyboard | Boolean | true | 是否支持键盘快捷键操作(如 Esc 退出、方向键切换) |
| loop | Boolean | true | 切换图片时是否循环播放 |
| toggleOnDblclick | boolean | true | 当放大或者缩小图片时,双击还原 |
| fullscreen | boolean、FullscreenOptions | true | 播放幻灯片时是否全屏 |
| focus | boolean | true | 是否在图片加载完成后自动聚焦到图片上 |
| slideOnTouch | boolean | true | 是否在触摸设备上滑动时切换图片 |
2.3 尺寸、层级与缩放配置
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| container | string、HTMLElement | body | 容器元素选择器,只有在 inline为 false的时候才可以使用 |
| zoomRatio | Number | 0.1 | 鼠标滚轮每次滚动时的缩放比例增量 |
| minZoomRatio | Number | 0.01 | 允许的最小缩放比例 |
| maxZoomRatio | Number | 100 | 允许的最大缩放比例 |
| minHeight | number | 定义图片查看器的最小高度,单位为像素 | |
| minWidth | number | 定义图片查看器的最小宽度,单位为像素 | |
| zIndex | number | 2015 | 设置图片查看器弹窗层级,避免被其他组件遮挡 |
| zIndexInline | number | 0 | 设置图片查看器内联层级 |
| initialCoverage | number | 0.9 | 初始缩放比例,必须是介于 0 (0%) 和 1 (100%) 之间的正数 |
| initialViewIndex | number | 0 | 定义用于查看的图像的初始索引 |
| zoomOnTouch | boolean | true | 是否在触摸设备上缩放图片 |
| zoomOnWheel | boolean | true | 是否在鼠标滚轮上缩放图片 |
2.4 播放与数据源配置
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| url | String、Function | src | 获取原始图片 URL 的位置。 如果是字符串,应该是每个图片元素的属性之一。 如果是函数,应返回有效的图片 URL |
| filter | Function | 筛选用于查看的图片。如果图片可查看返回 true,否则返回 false | |
| interval | Number | 5000 | 播放时自动切换图片的延迟时间,单位为毫秒 |
三、事件监听与交互扩展
viewer.js 提供了一套完善的事件回调机制,允许开发者在图片查看器的不同生命周期阶段介入自定义逻辑。通过事件回调,可以实现自定义 Loading 效果、埋点统计、UI 联动等高级功能。
3.1 查看
| 名称 | 说明 |
|---|---|
| view(event: CustomEvent) | 当图片开始被展示时触发,此时图片可能还在加载中 |
| viewed(event: CustomEvent) | 当图片查看完成(图片完全加载并渲染完毕)后触发,所有操作图片的方法都已就绪 |
| hide(event: CustomEvent) | 查看器开始隐藏时触发(动画开始前) |
| hidden(event: CustomEvent) | 查看器隐藏完成时触发(动画结束后) |
| show(event: CustomEvent) | 查看器开始显示时触发(动画开始前) |
| shown(event: CustomEvent) | 查看器显示完成时触发(动画结束后) |
javascript
const viewer = new Viewer(imageElement, {
shown() {
console.log('查看器已显示');
},
viewed() {
console.log('图片已加载完成');
},
hidden() {
console.log('查看器已隐藏');
}
});
3.2 图片操作交互
| 名称 | 说明 |
|---|---|
| zoom(event: ZoomEvent) | 图片缩放过程中持续触发,实时监听缩放,用于自定义 UI 上的缩放百分比显示 |
| zoomed(event: ZoomedEvent) | 图片缩放完成后触发,缩放结束后进行某些计算或状态保存 |
| rotate(event: RotateEvent) | 图片旋转过程中触发,实时监听旋转角度 |
| rotated(event: RotatedEvent) | 图片旋转完成后触发,旋转结束后更新 UI 状态 |
| move(event: MoveEvent) | 图片移动/拖拽过程中持续触发,实时追踪拖拽位置 |
| moved(event: MovedEvent) | 图片移动/拖拽完成后触发,拖拽结束后记录最终位置 |
| scale(event: ScaleEvent) | 图片翻转时触发的回调函数 |
| scaled(event: ScaledEvent) | 图片翻转完成后触发的回调函数 |
3.3 多图切换与播放
| 名称 | 说明 |
|---|---|
| play(event: CustomEvent) | 点击播放按钮,开始幻灯片播放时触发 |
| stop(event: CustomEvent) | 点击停止按钮或手动切换图片,停止幻灯片播放时触发 |
3.4 其他
| 名称 | 说明 |
|---|---|
| ready(event: CustomEvent) | 初始化完成后触发,只会触发一次 |
四、常用 API 方法
viewer.js 提供了完整的图片操作API,覆盖从基础显示到高级变换的全部功能。
4.1 显示与导航
| 方法名 | 参数 | 简要说明 | 示例 |
|---|---|---|---|
| show(immediate?: boolean) | Immediate:是否立即显示 | 手动触发预览器显示 | viewer.show(true) |
| hide(immediate?: boolean) | Immediate:是否立即隐藏 | 手动隐藏预览器 | viewer.hide() |
| view(index) | 查看指定索引的图片,若不传参数则触发当前图片的查看 | viewer.view(2) | |
| prev(loop) | 查看下一张图片 | viewer.prev(true) | |
| next(loop) | 查看下一张图片 | viewer.next(true) |
4.2 图片变换
| 方法名 | 参数 | 简要说明 |
|---|---|---|
| zoom(ratio: number, hasTooltip?: boolean) | 按相对比例进行缩放。showTooltip 是否显示提示信息 | |
| zoomTo(ratio: number, hasTooltip?: boolean) | 将图片缩放到指定的比例 | |
| rotate(degree: number) | 按相对角度进行旋转,正数右转、负数左转 | |
| rotateTo(degree: number) | 将图片旋转至指定的绝对角度 | |
| scale(scaleX: number, scaleY?: number) | 对图片进行水平或垂直翻转(镜像) | |
| scaleX(scaleX: number) | 水平方向翻转图片 | |
| scaleY(scaleY: number) | 垂直方向翻转图片 | |
| move(offsetX: number, offsetY?: number) | 将图片移动到指定的坐标位置。 move(1)右移、move(-1,0)左移、move(0,-1)上移、move(0,1)下移 | |
| moveTo(x: number, y?: number) | 将图片移动到指定的绝对坐标位置 |
viewer.js 支持丰富的图片变换操作,所有方法均支持链式调用:
javascript
viewer.rotate(90) // 顺时针旋转90度
.scale(1.5) // 放大1.5倍
.move(100, 50) // 向右移动100px,向下移动50px
.zoomTo(2); // 缩放到原始尺寸的2倍
4.3 播放与全屏控制
| 方法名 | 参数 | 简要说明 |
|---|---|---|
| play(fullscreen?: boolean) | 开始全屏幻灯片自动播放 | |
| stop() | 停止幻灯片自动播放 | |
| full() | 进入全屏模式 | |
| exit() | 退出全屏模式 | |
| reset() | 将图片重置为其初始状态 |
4.4 实例管理
| 方法名 | 参数 | 简要说明 | 示例 |
|---|---|---|---|
| update() | 更新查看器,通常用于容器内图片列表发生动态变化时 | ||
| destroy() | 销毁查看器实例,释放所有绑定事件 |
五、进阶技巧:打造个性化图片浏览体验
五、进阶技巧:打造个性化图片浏览体验
5.1 自定义工具栏
Viewer.js 提供了非常灵活的工具栏自定义功能,允许通过配置 toolbar 选项灵活实现内置按钮的显示/隐藏、添加自定义按钮并绑定事件,或者通过 CSS 调整按钮样式,甚至添加完全自定义的功能按钮(如下载、分享等)。
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| toolbar | boolean、Visibility、ToolbarOptions | true | 是否显示工具栏并设置其在工具栏中的显示优先级 |
Viewer.js 允许在 toolbar 配置对象中添加新的键值对,如果该键不是内置按钮名称,且对应的值是一个函数,Viewer.js 会将其渲染为一个自定义按钮。
| 属性 | 类型 | 简要说明 | 默认显示 |
|---|---|---|---|
| zoomIn | boolean | 放大图片的按钮 | ✅ |
| zoomOut | boolean | 缩小图片的按钮 | ✅ |
| oneToOne | boolean | 1:1 原始尺寸 | ✅ |
| reset | boolean | 重置图片大小的按钮 | ✅ |
| prev | boolean | 查看上一张图片的按钮 | ✅ |
| play | boolean | 播放图片的按钮 | ✅ |
| next | boolean | 查看下一张图片的按钮 | ✅ |
| rotateLeft | boolean | 向左旋转图片的按钮 | ✅ |
| rotateRight | boolean | 向右旋转图片的按钮 | ✅ |
| flipHorizontal | boolean | 图片左右翻转的按钮 | ✅ |
| flipVertical | boolean | 图片上下翻转的按钮 | ✅ |
html
<script setup>
import { ref, onMounted } from 'vue'
import Viewer from 'viewerjs'
import 'viewerjs/dist/viewer.css'
const viewerWrapRef = ref();
let viewer;
const imgList = ref([
'https://picsum.photos/id/237/800/800',
'https://picsum.photos/id/10/800/800'
])
const openViewer = (index) => {
if (viewer) {
viewer.view(index)
return
}
viewer = new Viewer(viewerWrapRef.value, {
initialViewIndex: index,
// 工具栏开关配置:true显示 false隐藏
toolbar: {
zoomIn: true,
zoomOut: true,
oneToOne: false, // 原图尺寸按钮隐藏
reset: true,
prev: true,
play: false, // 自动播放隐藏
next: true,
rotateLeft: true,
rotateRight: true,
flipHorizontal: false,
flipVertical: false
},
title: true,
movable: true,
zoomable: true,
rotatable: true
})
viewer.view(index)
}
onMounted(() => {})
</script>
<template>
<div ref="viewerWrapRef">
<img v-for="(src, idx) in imgList" :key="idx" :src="src" class="preview-img" @click="openViewer(idx)" alt="图片" />
</div>
</template>
<style scoped>
.preview-img {
width: 150px;
margin: 8px;
cursor: pointer;
}
</style>
5.2 完全自定义工具栏
html
<script setup>
import { ref, onUnmounted } from 'vue'
import Viewer from 'viewerjs'
import 'viewerjs/dist/viewer.css'
let viewer = null
const showTool = ref(false)
const imgList = ref([
'https://picsum.photos/id/237/800/800',
'https://picsum.photos/id/10/800/800',
'https://picsum.photos/id/100/800/800'
])
const viewerWrapRef = ref();
// 初始化预览,关闭原生工具栏、关闭右上角关闭按钮
const createViewer = (startIdx) => {
viewer = new Viewer(viewerWrapRef.value, {
initialViewIndex: startIdx,
toolbar: false, // 关闭默认工具栏
button: false, // 关闭右上角关闭按钮
viewed: () => {
// 图片打开后显示自定义工具栏
showTool.value = true
},
hidden: () => {
// 关闭预览隐藏工具栏
showTool.value = false
}
})
}
// 打开预览,指定索引图片开始显示
const openViewer = (index) => {
if (!viewer) createViewer(index)
viewer.view(index)
}
// 自定义按钮事件
const handleZoomIn = () => viewer.zoom(0.1)
const handleZoomOut = () => viewer.zoom(-0.1)
const handleRotateL = () => viewer.rotate(-90)
const handleRotateR = () => viewer.rotate(90)
const handleReset = () => viewer.reset()
const handlePrev = () => viewer.prev()
const handleNext = () => viewer.next()
const handleClose = () => viewer.hide()
// 自定义拓展功能:下载当前图片
const downloadImg = () => {
const currSrc = viewer.image.src
const a = document.createElement('a')
a.href = currSrc
a.download = 'preview-img'
a.click()
}
onUnmounted(() => {
viewer?.destroy()
})
</script>
<template>
<div class="img-wrap" ref="viewerWrapRef">
<!-- 缩略图 -->
<img v-for="(src, idx) in imgList" :key="idx" :src="src" class="thumb" @click="openViewer(idx)" alt="图片" />
<!-- 自定义工具栏弹窗(viewer遮罩上层) -->
<div v-if="showTool" class="custom-toolbar">
<button @click="handleZoomIn">放大</button>
<button @click="handleZoomOut">缩小</button>
<button @click="handleRotateL">左旋转</button>
<button @click="handleRotateR">右旋转</button>
<button @click="handleReset">重置</button>
<button @click="handlePrev">上一张</button>
<button @click="handleNext">下一张</button>
<button @click="downloadImg">下载图片</button>
<button @click="handleClose">关闭</button>
</div>
</div>
</template>
<style scoped>
.thumb {
width: 120px;
margin: 6px;
cursor: pointer;
}
/* 自定义工具栏固定在底部居中,层级高于viewer遮罩 */
.custom-toolbar {
position: fixed;
bottom: 40px;
left: 50%;
transform: translateX(-50%);
z-index: 99999;
background: rgba(0,0,0,0.6);
padding: 12px 20px;
border-radius: 8px;
display: flex;
gap: 10px;
}
.custom-toolbar button {
color: #fff;
background: transparent;
border: 1px solid #fff;
padding: 6px 12px;
border-radius: 4px;
cursor: pointer;
}
</style>
