基于Vue的商品主图放大镜方案详解


Posted in Javascript onSeptember 19, 2019

前言

在做电商类应用时,难免会遇到商品主图实现放大镜效果的场景,现有的基于 Vue 的第三方包不多并且无法直接复用,今天,我来分享一种高稳定性的基于 Vue 的图片放大镜方法。

实现原理

放大镜的原理用一句话概括,就是根据小图上的鼠标位置去定位大图。

图1 原理图(以2倍放大为例)

基于Vue的商品主图放大镜方案详解

相信原理图已经画的很明白了, 图中,左侧框是小图框,其蓝色区域为图片遮罩层(需放大区域),右侧框是整个大图目前所在区域,其蓝色区域是放大区域,设置超出隐藏,就实现了放大遮罩区域的效果。

显然,两块蓝色区域存在着某种对应关系,即遮罩的左上角位置(相对于小图,以下称 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 长图展示

基于Vue的商品主图放大镜方案详解

图3 宽图展示

基于Vue的商品主图放大镜方案详解

图4 两倍放大效果图

基于Vue的商品主图放大镜方案详解

图5 四倍放大效果图

基于Vue的商品主图放大镜方案详解 

核心代码

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 的移动等。

本文顺着这个思路,做了一个简单的实现,还有一些优化的空间,欢迎各位大佬在评论区讨论。虽然代码看起来不是非常优雅,但是足够明了,感兴趣的同学可以自己尝试一下。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
javascript脚本编程解决考试分数统计问题
Oct 18 Javascript
Javascript实现的鼠标经过时播放声音
May 18 Javascript
用dtree实现树形菜单 dtree使用说明
Oct 17 Javascript
javascript同页面多次调用弹出层具体实例代码
Aug 16 Javascript
js字符串操作方法实例分析
May 06 Javascript
AngularJS 日期格式化详解
Dec 23 Javascript
浏览器兼容的JS写法总结
Apr 27 Javascript
jQuery EasyUI 为Combo,Combobox添加清除值功能的实例
Apr 13 jQuery
JavaScript实现创建自定义对象的常用方式总结
Jul 09 Javascript
使用 Node.js 实现图片的动态裁切及算法实例代码详解
Sep 29 Javascript
浅谈webpack性能榨汁机(打包速度优化)
Jan 09 Javascript
Vue中keep-alive组件作用详解
Feb 04 Javascript
vue 实现模糊检索并根据其他字符的首字母顺序排列
Sep 19 #Javascript
微信小程序错误this.setData报错及解决过程
Sep 18 #Javascript
解决layui富文本编辑器图片上传无法回显的问题
Sep 18 #Javascript
layui 富文本赋值,取值,取纯文本值的实例
Sep 18 #Javascript
layui 富文本编辑器和textarea值的相互传递方法
Sep 18 #Javascript
微信小程序获取地理位置及经纬度授权代码实例
Sep 18 #Javascript
layui复选框限制选择个数的方法
Sep 18 #Javascript
You might like
利用递归把多维数组转为一维数组的函数
2006/10/09 PHP
php win下Socket方式发邮件类
2009/08/21 PHP
一步一步学习PHP(4) php 函数 补充2
2010/02/15 PHP
php跨域cookie共享使用方法
2014/02/20 PHP
PHP读取txt文本文件并分页显示的方法
2015/03/11 PHP
PHP工厂模式的日常使用
2019/03/20 PHP
document 和 document.all 分别什么时候用
2006/06/22 Javascript
Javascript 的addEventListener()及attachEvent()区别分析
2009/05/21 Javascript
javascript一些实用技巧小结
2011/03/18 Javascript
各种页面定时跳转(倒计时跳转)代码总结
2013/10/24 Javascript
按Enter键触发事件的jquery方法实现代码
2014/02/17 Javascript
node.js中的fs.createWriteStream方法使用说明
2014/12/17 Javascript
JavaScript学习笔记之JS事件对象
2015/01/22 Javascript
jquery判断input值不为空的方法
2016/06/05 Javascript
vue完成项目后,打包成静态文件的方法
2018/09/03 Javascript
详细分析vue响应式原理
2020/06/22 Javascript
[06:09]辉夜杯主赛事开幕式
2015/12/25 DOTA
python 中文字符串的处理实现代码
2009/10/25 Python
python不带重复的全排列代码
2013/08/13 Python
python base64 decode incorrect padding错误解决方法
2015/01/08 Python
Python中多线程及程序锁浅析
2015/01/21 Python
Python随机生成均匀分布在三角形内或者任意多边形内的点
2017/12/14 Python
Python的iOS自动化打包实例代码
2018/11/22 Python
pandas中的series数据类型详解
2019/07/06 Python
Python递归函数 二分查找算法实现解析
2019/08/12 Python
python动态视频下载器的实现方法
2019/09/16 Python
Python qrcode 生成一个二维码的实例详解
2020/02/12 Python
解决python运行启动报错问题
2020/06/01 Python
Python中关于logging模块的学习笔记
2020/06/03 Python
python进度条显示-tqmd模块的实现示例
2020/08/23 Python
Pandas对每个分组应用apply函数的实现
2020/12/13 Python
工作证明英文模板
2014/10/21 职场文书
运动会闭幕词
2015/01/28 职场文书
2015年学校团委工作总结
2015/05/26 职场文书
python基础之文件处理知识总结
2021/05/23 Python
使用canvas对video视频某一刻截图功能
2021/09/25 HTML / CSS