用Vue3怎么从零做一个实用又不臃肿的图片预览组件?
很多刚接触Vue3做项目的朋友,遇到图片展示需求都会先找第三方插件,结果要么体积太大占了首屏资源,要么配置太复杂,改个样式还要翻半天源码文档,其实一个满足核心需求的图片预览组件,自己写反而更顺手——比如支持左右切换、双击缩放、键盘控制、全屏、关闭这些,加起来代码量也不多,还能完全贴合自己的项目风格。
从零做之前,先理清楚这个组件需要哪些核心功能?
咱们得先抓用户的真实使用场景,别一开始就堆一堆没用的花活,核心功能大概有这几个:
- 基础的全屏模态展示,点击遮罩层或者关闭按钮能隐藏;
- 多图预览时的左右切换,用点击箭头或者键盘左右键都行;
- 图片的缩放、平移,鼠标滚轮可以调,双击要能在原始大小和自适应屏幕大小之间切换;
- 如果图片加载失败,要有个友好的占位图;
- 适配移动端,毕竟现在手机端看图的人比电脑端多,箭头可以藏起来,改成左右滑动切换。
核心原理:Vue3的Composition API+CSS Transforms+原生事件
第三方插件之所以体积大,很多是因为做了跨版本兼容(比如同时支持Vue2和Vue3)、一堆可选的自定义动画、水印功能什么的,咱们自己写就用Composition API,逻辑更清晰复用性也强;图片的缩放平移就用CSS的transform属性,性能比绝对定位好多了;事件方面用原生的DOM事件就行,不用引入额外的库。
对了,这里有个小细节,图片自适应屏幕大小的时候,不能只限制宽度或者只限制高度,得用“contain”的逻辑,但不是直接用background-size,因为咱们还要支持缩放平移,得用img标签本身。
开始写代码前的准备:组件的结构怎么划分?
一个完整的组件,得把视图、状态、逻辑分开写对吧?咱们用单文件组件(SFC),结构大概是这样:
- template部分:放遮罩层、图片容器、关闭按钮、左右切换箭头、加载/失败的提示;
- script setup部分:定义props(比如当前展示的图片索引、图片数组)、emits(比如关闭、切换图片索引)、各种响应式状态(比如是否显示、图片的缩放比例、位移、加载状态)、还有所有的事件处理函数;
- style scoped部分:写组件的样式,记得给遮罩层加个背景透明度,图片容器要占满全屏,箭头和关闭按钮要固定位置且层级够高,移动端适配的时候可以用媒体查询把箭头隐藏,给图片容器加滑动事件的touch处理。
一步步搭组件:先从基础的模态展示和切换开始
先写最简单的部分,把架子搭起来,首先是props,咱们要接收两个必须的:currentIndex(当前展示的第几张图,从0开始)和imageList(图片地址数组);还要一个可选的visible,用来控制组件的显示隐藏,不过也可以用v-model,记得emits里要加update:visible;还有个可选的loadingImage和errorImage,给用户自定义占位图的空间。
然后是响应式状态,比如transformOrigin(缩放原点,默认是中心)、scale(当前缩放比例,默认1)、translateX/Y(位移,默认0)、isLoading(图片是否在加载)、isError(图片是否加载失败)。
接下来是视图部分,遮罩层用div fixed定位,占满全屏,z-index设个高一点的,比如9999,点击事件触发关闭,图片容器放在遮罩层下面,也是fixed定位,flex布局居中放img标签,关闭按钮放在右上角,左右箭头放在左右两边中间,移动端的时候可以给箭头加个hidden-xs的类,媒体查询max-width:768px的时候隐藏。
关闭逻辑很简单,点击关闭按钮或者遮罩层,就把scale、translateX/Y重置为默认值,然后触发update:visible或者close的emit,切换图片的逻辑也差不多,点击左箭头或者键盘左箭头,如果currentIndex大于0就减1,然后重置缩放位移;点击右箭头或者键盘右箭头,如果currentIndex小于imageList.length-1就加1,同样重置。
进阶功能:缩放平移,这个是重点难点
很多自己写组件的朋友在这里会卡壳,其实掌握了原理就很简单,首先说鼠标滚轮缩放:给图片容器或者document添加wheel事件,注意要阻止默认行为,不然页面会跟着滚动,然后调整scale的值,不能太小也不能太大,比如最小0.5,最大5,缩放原点怎么改?如果是鼠标滚轮,就用event.clientX/Y减去图片容器的offsetLeft/offsetTop,再减去图片容器的宽高的一半?不对,应该是event.clientX/Y相对于图片容器左上角的坐标,然后再除以当前的scale?哦对了,这个细节很重要,不然缩放的时候图片会跳。
然后是鼠标拖拽平移:给图片容器添加mousedown、mousemove、mouseup事件,还要处理mouseleave,防止鼠标移出容器后松开按钮还在平移,mousedown的时候记录当前的鼠标位置和当前的translateX/Y,然后设置一个isDragging的状态为true;mousemove的时候如果isDragging为true,就计算鼠标移动的距离,更新translateX/Y;mouseup和mouseleave的时候把isDragging设为false。
还有双击缩放:给img标签添加dblclick事件,双击的时候如果scale是1,就改成2或者原始图片的大小(这个可以用naturalWidth和naturalHeight算),如果不是1就改回1,同时重置translateX/Y。
移动端适配:左右滑动切换+双指缩放
移动端要处理两个额外的事件:touchstart、touchmove、touchend、touchcancel,左右滑动切换的话,touchstart的时候记录第一个手指的x坐标,touchmove的时候如果移动距离超过某个阈值(比如50px),就判断是左滑还是右滑,然后切换图片,同样要重置缩放位移,双指缩放的话,touchstart的时候要判断有没有两个手指,如果有的话,记录两个手指的初始距离和当前的scale;touchmove的时候如果有两个手指,就计算当前两个手指的距离,除以初始距离,再乘以初始scale,得到新的scale,同样要限制最小最大值,缩放原点可以设为两个手指的中心。
图片加载和失败处理
给img标签添加load和error事件,load的时候把isLoading设为false,error的时候把isError设为true,loading的时候可以显示loadingImage或者一个旋转的小图标,error的时候显示errorImage或者一个“图片加载失败”的提示文字。
优化一下性能和体验
刚才写的逻辑里,有没有可以优化的地方?比如事件监听,应该在组件挂载的时候添加,组件卸载的时候移除,不然会有内存泄漏;还有响应式状态的更新,尽量用ref而不是reactive,因为ref的性能更好,尤其是对于基本类型的数据;还有图片的懒加载,虽然是预览组件,但如果imageList很长的话,可以只预加载当前图片的前后两张,其他的等需要的时候再加载;还有动画,关闭的时候可以加个淡出的动画,图片切换的时候可以加个淡入的动画,让体验更流畅。
测试一下,有没有什么问题?
写完之后要多测试几个场景:比如单图预览、多图预览、图片很大很小、图片加载失败、移动端的双指缩放和左右滑动、键盘操作、点击遮罩层和关闭按钮关闭,如果有问题,再调整一下逻辑。
自己写的这个组件,体积大概只有几百字节,比大多数第三方插件小多了,而且可以完全贴合自己的项目风格,改个样式或者加个功能都很方便,如果你正在做Vue3的项目,不妨试试自己写一个。
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
code前端网

