尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

viewer.min.js图片预览插件详解:从初始化到进阶优化

viewer.min.js图片预览插件详解:从初始化到进阶优化 简介一份 viewer.min.js 图像查看器库的完整资源包面向需要在网页中添加图片预览、缩放、旋转及平移等交互能力的前端开发者。该库以 JavaScript 实现适合商品图展示、相册浏览、内容详情等高频图片场景使用者应具备 HTML、CSS 与 JavaScript 基础才能更好地完成初始化和参数配置。压缩包共 177 个文件大小约 3.14MB其中包含 121 个 JavaScript 脚本用于核心逻辑7 个 CSS 文件负责查看器样式6 个 HTML 页面提供可直接运行的演示7 个 Markdown 文档对使用方式进行了说明同时附带样式源文件和工程配置方便调试、扩展与部署。目前已有 348 人学习下载。包内既有压缩版供线上环境使用也有未压缩源码便于阅读分析再配合多套样式与现成示例页开发者可以快速理解调用 API 并集成到工程中若需个性化效果也能基于源码二次开发省去从零搭建图片查看器的时间尤其适合需要快速交付或希望深度定制图片查看功能的项目。 最近在给一个后台管理系统的图片列表加预览功能时我把viewer.min.js这个前端图片查看器插件从入门到细节完整折腾了一遍。如果你也在做 Web 前端、后台管理、电商详情页这类需要图片预览、缩放、旋转的场景这篇文章应该能帮你省掉不少排查时间。viewer.min.js是一款纯 JavaScript 实现的图片查看器插件压缩后体积很小不依赖 jQuery支持图片缩放、旋转、翻转、拖拽、键盘操作、触屏手势和工具栏自定义。相比自己从头写一整套图片交互逻辑它能以非常低的成本把基础能力补齐。下面我会从选型、初始化、动态绑定、踩坑记录到进阶优化完整拆解整个使用过程。1. 项目里的图片查看需求为什么最后选了 viewer.min.js1.1 真实的业务场景我这边要做的是后台工单列表的图片预览。每条工单可能附带多张现场照片列表页只展示缩略图点击缩略图后需要弹出一张大图并且支持放大、缩小、旋转、左右切换。最初这个功能是直接在弹窗里放一张img然后用 CSS 手动实现缩放和拖拽代码越写越乱不说还要兼容触屏设备的双指缩放工作量远超预期。后续又考虑过用现成的 lightbox 类插件比如 fancybox、lightbox2但对比下来发现它们更偏向于“相册幻灯片”对单张图片的精细操作尤其是旋转、翻转、按固定比例缩放支持不够灵活。最后选择了viewer.min.js核心原因是它覆盖了我需要的全部手势操作和工具栏按钮而且不绑定 jQuery接口调用也简单。1.2 与“自己写轮子”相比viewer.min.js 解决了什么问题如果从头写图片查看器最麻烦的不是显示大图而是缩放和拖拽的状态管理。你需要记录当前的缩放比例、图片位移量、旋转角度然后把这些值组合成 transform 应用到图片上。看起来就是translate scale rotate几个属性但涉及到以鼠标位置为缩放中心、以图片中心旋转、多次操作后的坐标换算没有专门做过 Canvas 或图形处理的人很容易算错。viewer.min.js把这套复杂的状态计算完全封装了对外暴露的 API 非常直观。比如viewer.zoom(0.2)表示在当前缩放基础上放大 20%viewer.rotate(90)表示顺时针旋转 90 度你不需要关心底层矩阵怎么算。这也是我把这个插件放进项目的最大理由它能让我专注于业务逻辑而不是重复造轮子。提示如果你只需要类似“点击放大单张图片”的能力不一定要上viewer.min.js可以先用原生 dialog 加 transform 实现但一旦涉及多图切换、键盘操作、触屏手势老实用插件更稳妥。2. 上手前必须想清楚的关键点2.1 引入方式怎么选viewer.min.js的引入方式主要有三种CDN、npm 包、本地静态文件。在自己项目里我推荐优先使用 npm 安装因为它能跟随项目版本锁定也方便在构建工具里管理。npm install viewerjs --save然后在需要使用的模块里引入 JavaScript 和样式import Viewer from viewerjs; import viewerjs/dist/viewer.css;如果你用的是原生页面直接引入本地文件也行link relstylesheet href/libs/viewerjs/viewer.min.css script src/libs/viewerjs/viewer.min.js/script有一点特别容易踩坑viewer.min.js的 JavaScript 和viewer.min.css必须都引入。如果只引入 JS 没引入 CSS插件仍然能初始化但是所有图标的显示、遮罩层布局、工具栏位置都会错乱看起来就像“失效了”。我第一次接入时就漏了 CSS折腾了半天才发现问题。2.2 初始化哪个元素决定了你能预览哪些图片viewer.min.js最被我周围同事误解的一点是“它要初始化在img上”其实不是。官方推荐的用法是初始化在一组包含多张图片的容器上比如ul、div或figure。插件会对容器内的所有img自动建立索引点击任意一张都会从对应位置开始预览。const container document.getElementById(imageList); const viewer new Viewer(container, { // 配置项 });如果你把Viewer初始化在单张img上那只能预览这一张而且点击时是否触发要看url配置。平时做后台列表时我习惯用一个包裹所有缩略图的容器初始化这样既支持单图预览也天然支持多图切换。这里还有个细节如果容器内的图片本身是懒加载的>ul idimageList li img srcthumb.jpg>div idgallery img srcimages/1.jpg alt图1>.viewer-container { z-index: 9999 !important; }3.2 JavaScript 初始化与配置参数初始化代码非常简单let viewer; function initImageViewer(container) { if (viewer) { viewer.destroy(); } viewer new Viewer(container, { inline: false, // 非内嵌模式点击图片后以全屏遮罩方式预览 button: true, // 右上角显示关闭按钮 navbar: true, // 底部缩略图导航 title: true, // 显示图片标题 toolbar: true, // 显示工具栏 tooltip: true, movable: true, // 图片可拖动 zoomable: true, rotatable: true, scalable: true, transition: true, fullscreen: true, keyboard: true, // 支持方向键切换、Esc 关闭 toggleOnDblclick: true, // 双击图片切换最大/原始尺寸 loop: true, // 循环播放 loading: true, // 加载时显示 loading url: data-original // 读取>toolbar: { zoomIn: true, zoomOut: true, oneToOne: true, reset: true, prev: true, play: true, next: true, rotateLeft: true, rotateRight: true, flipHorizontal: true, flipVertical: true, }注意配置对象里的 key 名称是插件内置的不能随意更改。如果传进去不认识的 key按钮不会出现但也不会报错容易让人误以为配置不生效。3.3 常用方法调用和业务结合初始化完成后我们可以通过实例调用方法。举个例子业务里经常要“查看第一张图片”可以这样写viewer.show(); // 打开预览 viewer.view(0); // 跳转到第 0 张图片 viewer.zoom(0.1); // 放大 10% viewer.zoomTo(1); // 缩放到原始尺寸 viewer.rotate(90); // 顺时针旋转 90 度 viewer.rotateTo(180);// 旋转到某个角度 viewer.reset(); // 重置图片状态 viewer.destroy(); // 销毁实例如果是在列表页里点击“查看详情”按钮时打开图片查看器可以这样绑定document.getElementById(viewAllBtn).addEventListener(click, function () { viewer.view(0); viewer.show(); });有一点需要提醒viewer.view(index)本身会触发展示对应图片的预览但如果没有先初始化容器或者容器里图片数量还没更新调用时会报错或无效。通常要先viewer.update()再viewer.show()。事件回调也很实用。插件提供了show、shown、hide、hidden、view、viewed、zoom、rotated等事件。比如我想在图片查看器关闭后清空当前图片的高亮状态viewer.on(hidden, function () { console.log(预览已关闭); });建议在初始化时就把这些事件回调写清楚避免后续业务逻辑拆得到处都是。4. 踩过的坑和排查实录4.1 图片列表动态变化后点击没反应这是最常见的问题。列表页的图片是 Ajax 请求后动态渲染的如果我在渲染前就初始化了viewer插件只会记录初始化时的图片数量后续 DOM 新增图片它不会自动感知。表现就是点击第一张图能预览但点击新增的图片没反应或者在预览里切换不到新增图片。解决办法有两种。第一种是每次渲染完图片后重新初始化function renderImages(images) { // 渲染 DOM if (viewer) { viewer.destroy(); } viewer new Viewer(document.getElementById(gallery), options); }第二种是保留同一个实例并在 DOM 更新后调用update()function renderImages(images) { // 渲染 DOM viewer.update(); }两种方式实测都可行。区别在于destroy后重建会重新绑定点击事件整体开销略大但对状态清理更干净update则更轻量不过如果你在初始化时自定义了filter函数更新后它会重新过滤图片需要注意过滤条件是否正确。对于后台列表页的“搜索筛选后重新渲染”场景我更推荐destroy后重建因为新列表和旧列表往往在语义上已经是完全不同的集合了。4.2 弹窗或 Modal 里的图片无法初始化项目里有一种常见的弹窗结构点击按钮后弹窗里显示一组图片。如果我在弹窗动画还没有结束、图片还没有渲染出来时就去初始化viewer插件经常定位不到正确的img尺寸点击后图片不会居中甚至预览框是空的。排查思路是确认容器在 DOM 中处于可见状态。你可以把初始化放在弹窗shown回调里或者用setTimeout延迟到动画结束后再执行modal.addEventListener(shown, function () { setTimeout(function () { if (modalViewer) { modalViewer.destroy(); } modalViewer new Viewer(modal.querySelector(.modal-images), options); }, 100); });另一个更隐蔽的问题是如果弹窗里有多个图片容器而我在弹窗存在期间重复初始化同一个容器会积累大量事件监听。建议在弹窗关闭时统一销毁modal.addEventListener(hidden, function () { if (modalViewer) { modalViewer.destroy(); modalViewer null; } });4.3 自定义按钮和工具栏配置无效如果你发现toolbar配置里写了按钮但没显示先检查 key 是否拼写正确。viewer 内置的 key 包括zoomIn、zoomOut、oneToOne、reset、prev、play、next、rotateLeft、rotateRight、flipHorizontal、flipVertical。每个按钮还支持配置为对象比如toolbar: { zoomIn: { show: true, size: large }, custom: function () { // 自定义按钮点击事件 console.log(custom); } }不过有个限制如果传入自定义函数它会替换默认按钮图标但viewer.min.js的图标是字体图标自定义函数里你还是需要自己处理图标样式。我实际用下来最方便的方式是直接隐藏不需要的默认按钮而不是强行新增复杂按钮。比如后台场景只需保留缩放、旋转、关闭就够了toolbar: { zoomIn: 1, zoomOut: 1, reset: 1, rotateLeft: 1, rotateRight: 1, prev: 0, next: 0, play: 0, flipHorizontal: 0, flipVertical: 0, }1表示显示0表示隐藏。这个配置方法简单直观不容易出错。4.4 移动端触屏手势与页面滚动冲突在移动端页面里图片查看器打开后手指在图片上拖动时底层的页面也会跟着滚动体验很割裂。后来我排查发现viewer 默认会给自己的容器加touch-action但有些场景下被其他样式覆盖了。稳妥的办法是在全局样式里强制指定.viewer-canvas { touch-action: none; }如果只是想让图片只能在某个区域内拖动不希望拖动时带动页面可以设置const viewer new Viewer(container, { movable: true, zoomable: true, rotatable: true, scalable: true, });再配合上面的touch-action移动端体验基本能达标。注意不要同时给body加overflow: hidden否则弹窗关闭后可能造成滚动位置丢失这在 iOS Safari 上尤其明显。5. 进阶优化和工程化建议5.1 定制外观和主题viewer.min.js自带一套简洁的深色主题但很多后台系统需要配合自己的品牌色调。你可以直接覆盖.viewer-container、.viewer-toolbar、.viewer-button这几个核心样式。比如我想把遮罩层改成半透明的白按钮改成圆角浅色.viewer-container { background-color: rgba(255, 255, 255, 0.92); } .viewer-toolbar { background-color: #f5f5f5; border-radius: 24px; padding: 4px 8px; } .viewer-button { color: #333; border-radius: 50%; }需要注意覆盖优先级。如果你通过 npm 引入样式加载顺序如果比业务样式早业务样式可以正常覆盖如果比业务样式晚类名相同的情况下可能会把你自己写的样式覆盖回去。最省事的方法是给覆盖样式加!important或者确保业务样式在 webpack 打包时排在后面。5.2 按需加载与性能优化viewer.min.js整个文件并不大但在性能敏感的场景下还是希望在用户点击“预览大图”时再加载避免拖慢首屏。现代浏览器支持动态import()我可以把viewer的初始化逻辑拆成一个异步模块async function openViewerByImages(images) { const Viewer (await import(viewerjs)).default; import(viewerjs/dist/viewer.css); // 创建容器并初始化 }这样首屏就不会加载 viewer 相关的 JS 和 CSS只有用户真正打开预览时才发出请求。实测对图片数量特别多的后台仪表盘页面有一定收益。如果你的项目不用 webpack 或 vite也可以用 HTML 里的script加上defer或async来加载。个人不推荐在列表页同时引入完整版和压缩版容易出现重复定义除非你明确要区分调试和生产环境。5.3 多实例管理与内存释放一个页面里可能存在多个图片容器比如左侧列表一个、弹窗一个、详情页一个。如果每次都new Viewer(...)而不destroy页面会在关闭弹窗或切换 Tab 时留下无效实例长时间使用后通过开发者工具可以发现内存占用涨得很快。我习惯封装一个简单的管理工具class ImageViewerManager { constructor() { this.cache new Map(); } create(key, container, options) { if (this.cache.has(key)) { this.cache.get(key).destroy(); } const viewer new Viewer(container, options); this.cache.set(key, viewer); return viewer; } destroy(key) { if (this.cache.has(key)) { this.cache.get(key).destroy(); this.cache.delete(key); } } } const imageViewerManager new ImageViewerManager();用的时候这样调用const viewer imageViewerManager.create(galleryContainer, document.getElementById(gallery), { toolbar: true, });这样全局只保留各个容器的唯一实例任何切换场景都不会重复叠加事件绑定。实际项目里用了这个方案之后后台页面频繁打开关闭弹窗也几乎没有出现“预览卡顿”和“事件越绑越多”的问题。最后再分享一个我实际使用的小习惯初始化时我会统一把url配置成style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;" />
返回列表