本文首发于政采云前端团队博客:基于 Vue 的商品主图放大镜方案
https://www.zoo.team/article/vue-item-magnifier
前言
在做电商类应用时,难免会遇到商品主图实现放大镜效果的场景,现有的基于Vue
的第三方包不多并且无法直接复用,今天,我来分享一种高稳定性的基于Vue
的图片放大镜方法。
实现原理
放大镜的原理用一句话概括,就是根据小图上的鼠标位置去定位大图。图1 原理图(以2倍放大为例)
![e81476b9d0a4ee72c3e0044834e7478b.png](https://i-blog.csdnimg.cn/blog_migrate/4488b71c557f9296622603721b781341.jpeg)
{left: - maskX * scale + 'px';top: - maskY * scale + 'px';
}
效果演示
图2 长图展示
![24ee1e3b5e28642ba05a777d5177922c.png](https://i-blog.csdnimg.cn/blog_migrate/fe8628926c84d9c5426d959e745b531c.jpeg)
图3 宽图展示
![1a468bc5125120600064f15df9842c5c.png](https://i-blog.csdnimg.cn/blog_migrate/e793731cabfb53c367cd769edfe78ec5.jpeg)
图4 两倍放大效果图
![b088b4c212a8f420baacbbb846293194.gif](https://i-blog.csdnimg.cn/blog_migrate/33cb81c0878e50ae795ec2079afb7953.gif)
图5 四倍放大效果图
![28e5410aa0feb5916471390c8c08686a.gif](https://i-blog.csdnimg.cn/blog_migrate/a860a49246015ebd49995d0aafb05203.gif)
核心代码
HTML
一般放大镜实现的是 1:1 等宽等高的正方形图片,这里兼容了其他比例的图片,设置图片为垂直居中对齐,包括小图,大图。如果小图不够充满整个小图框,余留下的空白部分也可以有放大效果,只不过放大结果依然是空白。这样只需计算背景图的移动距离,不用过多的关注图片定位问题。<template><div class="magnifier"><div class="small-box" @mouseover="handOver" @mousemove="handMove" @mouseout="handOut"><img class="smallPic" :src="`${src}?x-oss-process=image/resize,l_836`" /><div class="magnifier-zoom" v-show="showMask":style="{
background: configs.maskColor,
height: configs.maskWidth + 'px',
width: configs.maskHeight + 'px',
opacity: configs.maskOpacity,
transform: transformMask
}"
>div>div><div class="magnifier-layer" v-show="showMagnifier":style="{
width: configs.width + 'px',
height: configs.height + 'px',
left: configs.width + 20 + 'px'
}"
><div class="big-box":style="{
width: bigWidth + 'px',
height: bigHeight + 'px',
left: moveLeft,
top: moveTop
}"
><div class="big-box-img":style="{
width: bigWidth - 2 + 'px',
height: bigHeight - 2 + 'px'
}"
><img:src="bigSrc":style="{
maxWidth: bigWidth - 2 + 'px',
maxHeight: bigHeight -2 + 'px'
}"
/>div>div>div>div>template>
JS
这里主要有三个事件函数。handOver:鼠标进入到小图框上的事件,此时显示遮罩和放大区域,并计算小图框的位置信息。
handOver() {// 计算小图框在浏览器中的位置this.imgObj = this.$el.getElementsByClassName('small-box')[0];this.imgRectNow = this.imgObj.getBoundingClientRect();this.showMagnifier = true;this.showMask = true;
}
handMove:鼠标在小图上的移动事件,此事件发生在 handOver 之后,计算数据,移动遮罩以及背景图;
handMove(e) {// 计算初始的遮罩左上角的坐标let objX = e.clientX - this.imgRectNow.left;let objY = e.clientY - this.imgRectNow.top;// 计算初始的遮罩左上角的坐标let maskX = objX - this.configs.maskWidth / 2;let maskY = objY - this.configs.maskHeight / 2;// 判断是否超出界限,并纠正
maskY = maskY 0 ? 0 : maskY;
maskX = maskX 0 ? 0 : maskX; if(maskY + this.configs.maskHeight >= this.imgRectNow.height) {
maskY = this.imgRectNow.height - this.configs.maskHeight;
}if(maskX + this.configs.maskWidth >= this.imgRectNow.width) {
maskX = this.imgRectNow.width - this.configs.maskWidth;
}// 遮罩移动this.transformMask = `translate(${maskX}px, ${maskY}px)`;// 背景图移动this.moveLeft = - maskX * this.configs.scale + "px";this.moveTop = - maskY * this.configs.scale + "px";
}
handOut:鼠标离开小图事件,此时无放大镜效果,隐藏遮罩和放大区域。
handOut() {this.showMagnifier = false;this.showMask = false;
}
以上三个事件基本上就实现了图片的放大镜功能。但仔细看,你会发现每次移入小图框都会触发一次 handOver 事件,并且计算一次小图框 DOM (imgObj) 。为了优化此问题,可以用 init 标识是否是页面加载后首次触发 handOver 事件,如果是初始化就计算imgObj 信息,否则不计算。
handOver() {if (!this.init) {this.init = true;// 原 handOver 事件
...
} this.showMagnifier = true;this.showMask = true;
},
在测试的过程中,发现页面滚动后,会出现遮罩定位错误的情况,原来是因为初始化时,我们固定死了小图框的位置信息(存放在 this.imgRectNow ),导致 handMove 事件中的移动数据计算错误。解决这个问题有两种方案:
监听 scroll 事件,更新 this.imgRectNow;
在 handMove 事件中更新 this.imgRectNow。
handMove(e) {// 动态获取小图的位置(或者监听 scroll )let imgRectNow = this.imgObj.getBoundingClientRect();let objX = e.clientX - imgRectNow.left;let objY = e.clientY - imgRectNow.top;// 原 handMove 事件剩余内容
...
},
综合以上,我们已经实现了一个完美的图片放大镜功能。最终的 js 如下所示:
data() {return {imgObj: {},moveLeft: 0,moveTop: 0,transformMask:`translate(0px, 0px)`,showMagnifier:false,showMask:false,init: false,
};
},computed: {
bigWidth(){return this.configs.scale * this.configs.width;
},
bigHeight(){return this.configs.scale * this.configs.height;
}
},methods: {
handMove(e) {// 动态获取小图的位置(或者监听 scroll )let imgRectNow = this.imgObj.getBoundingClientRect();let objX = e.clientX - imgRectNow.left;let objY = e.clientY - imgRectNow.top;// 计算初始的遮罩左上角的坐标let maskX = objX - this.configs.maskWidth / 2;let maskY = objY - this.configs.maskHeight / 2;// 判断是否超出界限,并纠正
maskY = maskY 0 ? 0 : maskY;
maskX = maskX 0 ? 0 : maskX; if(maskY + this.configs.maskHeight >= imgRectNow.height) {
maskY = imgRectNow.height - this.configs.maskHeight;
}if(maskX + this.configs.maskWidth >= imgRectNow.width) {
maskX = imgRectNow.width - this.configs.maskWidth;
}// 遮罩移动this.transformMask = `translate(${maskX}px, ${maskY}px)`;// 背景图移动this.moveLeft = - maskX * this.configs.scale + "px";this.moveTop = - maskY * this.configs.scale + "px";
},
handOut() {this.showMagnifier = false;this.showMask = false;
},
handOver() {if (!this.init) {this.init = true;this.imgObj = this.$el.getElementsByClassName('small-box')[0];
}this.showMagnifier = true;this.showMask = true;
}
}
使用方法
本示例中的固定参数:小图框:420 * 420 。程序可接受参数:// 小图地址
src: {type: String,
},// 大图地址
bigSrc: {type: String,
},// 配置项
configs: {type: Object,default() {return {width:420,//放大区域
height:420,//放大区域
maskWidth:210,//遮罩
maskHeight:210,//遮罩
maskColor:'rgba(25,122,255,0.5)',//遮罩样式
maskOpacity:0.6,scale:2,//放大比例
};
}
}
文中图 2 是一张长图,小图的最大边不超过 836px(二倍图) ,大图为了视觉效果,分辨率尽量高点,程序会根据配置项自动设置对应的 height , width ,长图与宽图的效果对比可参考图3。配置项可根据应用场景自行设置,本文示例的配置项是 2 倍放大,效果可参考图 4,四倍放大效果可参考图 5。
总结
其实图片放大镜的实现思路没有那么复杂,核心点有两点:小图、大图的定位,遮罩和放大区域的创建方法
放大镜的原理理解,并用代码实现 DOM 的移动等。