基于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 相关文章推荐
js中的string.format函数代码
Aug 11 Javascript
AngularJS入门教程之Hello World!
Dec 06 Javascript
JavaScript实现Iterator模式实例分析
Jun 09 Javascript
jQuery zTree加载树形菜单功能
Feb 25 Javascript
AngularJS优雅的自定义指令
Jul 01 Javascript
Bootstrap fileinput组件封装及使用详解
Mar 10 Javascript
node.js文件操作系统实例详解
Nov 05 Javascript
详解ECMAScript2019/ES10新属性
Dec 06 Javascript
使用uni-app开发微信小程序的实现
Dec 13 Javascript
Vue 自适应高度表格的实现方法
May 13 Javascript
使用js和canvas实现时钟效果
Sep 08 Javascript
react如何快速设置文件路径别名
Apr 28 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
PHP中读取照片exif信息的方法
2014/08/20 PHP
使用PHP编写发红包程序
2015/07/22 PHP
Zend Studio使用技巧两则
2016/04/01 PHP
php基于mcrypt_encrypt和mcrypt_decrypt实现字符串加密解密的方法
2016/07/12 PHP
微信支付扫码支付php版
2016/07/22 PHP
用JS剩余字数计算的代码
2008/07/03 Javascript
javascript cookies操作集合
2010/04/12 Javascript
初学js 新节点的创建 删除 的步骤
2011/07/04 Javascript
javascript获得服务器端控件的ID的实现代码
2011/12/28 Javascript
js中settimeout方法加参数的使用实例
2014/02/27 Javascript
JavaScript日期时间格式化函数分享
2014/05/05 Javascript
angularjs 源码解析之scope
2016/08/22 Javascript
js中的面向对象入门
2017/03/06 Javascript
从零开始学习Node.js系列教程一:http get和post用法分析
2017/04/13 Javascript
详谈innerHTML innerText的使用和区别
2017/08/18 Javascript
jqueryUI tab标签页代码分享
2017/10/09 jQuery
详解angular2 控制视图的封装模式
2018/12/27 Javascript
Vue表单控件绑定图文详解
2019/02/11 Javascript
python实现DNS正向查询、反向查询的例子
2014/04/25 Python
Linux系统上Nginx+Python的web.py与Django框架环境
2015/12/25 Python
Python计算一个点到所有点的欧式距离实现方法
2019/07/04 Python
Python字典生成式、集合生成式、生成器用法实例分析
2020/01/07 Python
ansible-playbook实现自动部署KVM及安装python3的详细教程
2020/05/11 Python
python爬虫使用requests发送post请求示例详解
2020/08/05 Python
PyCharm 2020.1版安装破解注册码永久激活(激活到2089年)
2020/09/24 Python
利用HTML5的新特点实现图片文件异步上传
2014/05/29 HTML / CSS
印尼太阳百货公司网站:Matahari
2018/02/04 全球购物
日本最大的旅游网站:Rakuten Travel(乐天旅游)
2018/08/02 全球购物
英国儿童设计师服装和玩具购物网站:Zac & Lulu
2020/10/19 全球购物
Python里面如何拷贝一个对象
2014/02/17 面试题
餐厅销售主管职责范本
2014/02/19 职场文书
指导教师评语
2014/04/26 职场文书
大学运动会加油稿200字(5篇)
2014/09/27 职场文书
批评与自我批评发言稿
2014/10/15 职场文书
导游词之蓬莱长岛
2019/12/17 职场文书
win10+anaconda安装yolov5的方法及问题解决方案
2021/04/29 Python