德胜云资讯,添加一些关于程序相关的内容,仅供大家学习交流(https://www.wxclwl.com)
日期:2023/03/23 20:48作者:王依婷人气:
扫码关注公众号「政采云前端团队」,获取更多不掺水的原创好文~
在做电商类应用时,难免会遇到商品主图实现放大镜效果的场景,现有的基于 Vue 的第三方包不多并且无法直接复用,今天,我来分享一种高鲁棒性的基于 Vue 的图片放大镜方法。
放大镜的原理用一句话概括,就是根据小图上的鼠标位置去定位大图。
图 1 原理图(以 2 倍放大为例)
相信原理图已经画的很明白了,图中,左侧框是小图框,其蓝色区域为图片遮罩层(需放大区域),右侧框是整个大图目前所在区域,其蓝色区域是放大区域,设置超出隐藏,就实现了放大遮罩区域的效果。
显然,两块蓝色区域存在着某种对应关系,即遮罩的左上角位置(相对于小图,以下称 X 坐标)和放大区域(相对于大图)的左上角位置是成比例的,即放大倍数。计算出 X 坐标后,适当调整背景图的位置,使大图向反方向移动 scale 倍的 X 坐标即可。
X 坐标为(maskX,maskY),以计算 maskX 为例:
鼠标移动中会产生 e.clientX,标识鼠标与浏览器左侧的距离,小图与浏览器左侧的距离是 left ,由于遮罩始终是一个以鼠标为中心的正方形,所以:
maskX = e.clientX - left - mask/2
同理,
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> <!-- 大图 --> <div class="magnifier-layer" :style="{ width:configs.width + px, height:this.configs.height+px, top:0px, left: configs.width + 20 + px }"> <div class="big-box" :style="{width:configs.scale * configs.width + px, height:configs.scale * configs.height + px}" > <div class="big-box-img" :style="{width:configs.scale * configs.width - 2 + px, height:configs.scale * configs.height - 2 + px}" > <img :src="bigSrc" :style="{maxWidth:configs.scale * configs.width - 2 + px, maxHeight:configs.scale * configs.height -2 + px}" /> </div> </div> </div> </div> </template>这里主要有三个事件函数,handOver 鼠标进入到小图框上的事件,此时创建遮罩和放大区域,并计算小图框的位置信息。
handOver() { this.imgObj = this.$el.getElementsByClassName(small-box)[0]; // 创建遮罩区域 this.mouseMask = document.createElement(div); this.mouseMask.className = magnifier-zoom; this.mouseMask.style.background = this.configs.maskColor; this.mouseMask.style.height = this.configs.maskWidth + px; this.mouseMask.style.width = this.configs.maskHeight + px; this.mouseMask.style.opacity = this.configs.maskOpacity; // 创建预览框样式 this.imgLayer = document.getElementsByClassName(magnifier-layer)[0]; this.imgLayer.style.display = block; this.bigBox = document.getElementsByClassName(big-box)[0]; this.imgObj.appendChild(this.mouseMask); // 计算小图框在浏览器中的位置 this.imgRectNow = this.imgObj.getBoundingClientRect(); }handMove 鼠标在小图上的移动事件,此事件发生在 handOver 之后,计算数据,移动遮罩以及背景图;
handMove(e) { // 计算初始的遮罩左上角的坐标 let objX = e.clientX - this.imgRectNow.left; let objY = e.clientY - this.imgRectNow.top; // 计算初始的遮罩左上角的坐标 let _maskX = objX - this.mouseMask.offsetHeight/2; let _maskY = objY - this.mouseMask.offsetWidth/2; // 判断是否超出界限,并纠正 _maskY = _maskY < 0 ? 0: _maskY; if(_maskY + this.mouseMask.offsetHeight >= imgRectNow.height){ _maskY = imgRectNow.height - this.mouseMask.offsetHeight; } _maskX = _maskX < 0 ? 0: _maskX; if(_maskX + this.mouseMask.offsetWidth >= imgRectNow.width){ _maskX = imgRectNow.width - this.mouseMask.offsetWidth; } let bigImgLeft = _maskX * this.configs.scale; let bigImgTop = _maskY * this.configs.scale; // 遮罩移动 this.mouseMask.style.transform=`translate(${_maskX}px, ${_maskY}px)`; // 背景图移动 this.bigBox.style.left = - bigImgLeft + "px"; this.bigBox.style.top = - bigImgTop + "px"; }handOut 鼠标离开小图事件,此时无放大镜效果,隐藏遮罩和放大区域。
handOut() { this.imgLayer.style.display = none; this.mouseMask.style.display = none; }以上三个事件基本上就实现了图片的放大镜功能。
但仔细看,你会发现每次移入小图框都会触发一次 handOver 事件,进而创建一份遮罩和放大区域等信息,其实,这些信息只需在页面加载后创建一次,多则浪费。
为了优化此问题,可以用 init 标识是否是页面加载后首次触发 handOver 事件,如果是初始化就创建遮罩和放大区域等,否则只需修改遮罩和放大区域的样式,使其显示即可。
handOver() { if (!this.init) { this.init = true; // 原 handOver 事件 ... } else { this.imgLayer.style.display = block; this.mouseMask.style.display = block; } },在测试的过程中,发现页面滚动后,会出现遮罩定位错误的情况,原来是因为初始化时,我们定死了小图框的位置信息(存放在 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:{}, mouseMask:{}, imgLayer:{}, init: false, }; }, 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.mouseMask.offsetHeight/2; let _maskY = objY - this.mouseMask.offsetWidth/2; // 判断是否超出界限,并纠正 _maskY = _maskY < 0 ? 0: _maskY; if(_maskY + this.mouseMask.offsetHeight >= imgRectNow.height){ _maskY = imgRectNow.height - this.mouseMask.offsetHeight; } _maskX = _maskX < 0 ? 0: _maskX; if(_maskX + this.mouseMask.offsetWidth >= imgRectNow.width){ _maskX = imgRectNow.width - this.mouseMask.offsetWidth; } let bigImgLeft = _maskX * this.configs.scale; let bigImgTop = _maskY * this.configs.scale; // 遮罩移动 this.mouseMask.style.transform=`translate(${_maskX}px, ${_maskY}px)`; // 背景图移动 this.bigBox.style.left = - bigImgLeft + "px"; this.bigBox.style.top = - bigImgTop + "px"; }, handOut() { this.imgLayer.style.display = none; this.mouseMask.style.display = none; }, handOver() { if (!this.init) { this.init = true; this.imgObj = this.$el.getElementsByClassName(small-box)[0]; // 创建遮罩区域 this.mouseMask = document.createElement(div); this.mouseMask.className = magnifier-zoom; this.mouseMask.style.background = this.configs.maskColor; this.mouseMask.style.height = this.configs.maskWidth + px; this.mouseMask.style.width = this.configs.maskHeight + px; this.mouseMask.style.opacity = this.configs.maskOpacity; // 创建预览框样式 this.imgLayer = document.getElementsByClassName(magnifier-layer)[0]; this.imgLayer.style.display = block; this.bigBox = document.getElementsByClassName(big-box)[0]; this.imgObj.appendChild(this.mouseMask); } else { this.imgLayer.style.display = block; this.mouseMask.style.display = block; } } }本示例中的固定参数:小图框: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),一个年轻富有激情和创造力的前端团队,隶属于政采云产品研发部,Base 在风景如画的杭州。团队现有 50 余个前端小伙伴,平均年龄 27 岁,近 3 成是全栈工程师,妥妥的青年风暴团。成员构成既有来自于阿里、网易的“老”兵,也有浙大、中科大、杭电等校的应届新人。团队在日常的业务对接之外,还在物料体系、工程平台、搭建平台、性能体验、云端应用、数据分析及可视化等方向进行技术探索和实战,推动并落地了一系列的内部技术产品,持续探索前端技术体系的新边界。
如果你想改变一直被事折腾,希望开始能折腾事;如果你想改变一直被告诫需要多些想法,却无从破局;如果你想改变你有能力去做成那个结果,却不需要你;如果你想改变你想做成的事需要一个团队去支撑,但没你带人的位置;如果你想改变既定的节奏,将会是“5 年工作时间 3 年工作经验”;如果你想改变本来悟性不错,但总是有那一层窗户纸的模糊… 如果你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的自己。如果你希望参与到随着业务腾飞的过程,亲手推动一个有着深入的业务理解、完善的技术体系、技术创造价值、影响力外溢的前端团队的成长历程,我觉得我们该聊聊。任何时间,等着你写点什么,发给 ZooTeam@cai-inc.com