原创不易,但愿能关注下咱们,再顺手点个赞~~ |
本文首发于政采云前端团队博客: 基于 Vue 的商品主图放大镜方案javascript
在作电商类应用时,不免会遇到商品主图实现放大镜效果的场景,现有的基于Vue
的第三方包很少而且没法直接复用,今天,我来分享一种高稳定性的基于 Vue
的图片放大镜方法。css
放大镜的原理用一句话归纳,就是根据小图上的鼠标位置去定位大图。html
图1 原理图(以2倍放大为例)前端
相信原理图已经画的很明白了, 图中,左侧框是小图框,其蓝色区域为图片遮罩层(需放大区域),右侧框是整个大图目前所在区域,其蓝色区域是放大区域,设置超出隐藏,就实现了放大遮罩区域的效果。vue
显然,两块蓝色区域存在着某种对应关系,即遮罩的左上角位置(相对于小图,如下称 X 坐标)和放大区域(相对于大图)的左上角位置是成比例的,即放大倍数。计算出 X 坐标后,适当调整背景图的位置,使大图向反方向移动 scale 倍的 X 坐标便可。java
X 坐标为(maskX,maskY),以计算 maskX 为例:浏览器
鼠标移动中会产生 e.clientX ,标识鼠标与浏览器左侧的距离,小图与浏览器左侧的距离是 left ,因为遮罩始终是一个以鼠标为中心的正方形,因此:性能优化
maskX = e.clientX - left - mask/2函数
同理,post
maskY = e.clientY - top - mask/2
大图的对应样式设置为:
{
left: - maskX * scale + 'px';
top: - maskY * scale + 'px';
}
复制代码
图2 长图展现
图3 宽图展现
图4 两倍放大效果图
图5 四倍放大效果图
通常放大镜实现的是 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>
复制代码
这里主要有三个事件函数。
handOver() {
// 计算小图框在浏览器中的位置
this.imgObj = this.$el.getElementsByClassName('small-box')[0];
this.imgRectNow = this.imgObj.getBoundingClientRect();
this.showMagnifier = true;
this.showMask = true;
}
复制代码
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() {
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 事件中的移动数据计算错误。
解决这个问题有两种方案:
这里选择了第二种。
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。
其实图片放大镜的实现思路没有那么复杂,核心点有两点:
本文顺着这个思路,作了一个简单的实现,还有一些优化的空间,欢迎各位大佬在评论区讨论。虽然代码看起来不是很是优雅,可是足够明了,感兴趣的同窗能够本身尝试一下。
招人,前端,隶属政采云前端大团队(ZooTeam),50 余个小伙伴正等你加入一块儿浪。若是你想改变一直被事折腾,但愿开始能折腾事;若是你想改变一直被告诫须要多些想法,却无从破局;若是你想改变你有能力去作成那个结果,却不须要你;若是你想改变你想作成的事须要一个团队去支撑,但没你带人的位置;若是你想改变既定的节奏,将会是“5年工做时间3年工做经验”;若是你想改变原本悟性不错,但老是有那一层窗户纸的模糊… 若是你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的本身。若是你但愿参与到随着业务腾飞的过程,亲手参与一个有着深刻的业务理解、完善的技术体系、技术创造价值、影响力外溢的前端团队的成长历程,我以为咱们该聊聊。任什么时候间,等着你写点什么,发给 ZooTeam@cai-inc.com