基于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 相关文章推荐
Jquery操作下拉框(DropDownList)实现取值赋值
Aug 13 Javascript
showModalDialog模态对话框的使用详解以及浏览器兼容
Jan 11 Javascript
做web开发 先学JavaScript
Dec 12 Javascript
javascript显示中文日期的方法
Jun 18 Javascript
react-router实现跳转传值的方法示例
May 27 Javascript
Vue内容分发slot(全面解析)
Aug 19 Javascript
vue下拉列表功能实例代码
Apr 08 Javascript
详解基于Vue,Nginx的前后端不分离部署教程
Dec 04 Javascript
详解如何在Vue项目中导出Excel
Apr 19 Javascript
js实现无缝轮播图特效
May 09 Javascript
JavaScript中document.activeELement焦点元素介绍
Nov 27 Javascript
JavaScript最完整的深浅拷贝实现方式详解
Feb 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
解析二进制流接口应用实例 pack、unpack、ord 函数使用方法
2013/06/18 PHP
php有道翻译api调用方法实例
2014/12/22 PHP
php+MySql实现登录系统与输出浏览者信息功能
2016/07/01 PHP
PHP实现截取中文字符串不出现?号的解决方法
2016/12/29 PHP
PHPMAILER实现PHP发邮件功能
2018/04/18 PHP
日期函数扩展类Ver0.1.1
2006/09/07 Javascript
jQuery '行 4954 错误: 不支持该属性或方法' 的问题解决方法
2011/01/19 Javascript
在JavaScript应用中实现延迟加载的方法
2015/06/25 Javascript
jquery实现标题字体变换的滑动门菜单效果
2015/09/07 Javascript
angular+ionic 的app上拉加载更新数据实现方法
2017/01/16 Javascript
原生JS仿QQ阅读点击展开、收起效果
2017/03/08 Javascript
Javascript封装id、class与元素选择器方法示例
2017/03/13 Javascript
JavaScript引用类型之基本包装类型实例分析【Boolean、Number和String】
2018/08/09 Javascript
解决bootstrap模态框数据缓存的问题方法
2018/08/10 Javascript
jQuery实现高级检索功能
2019/05/28 jQuery
layui对工具条进行选择性的显示方法
2019/09/19 Javascript
Vuex中实现数据状态查询与更改
2019/11/08 Javascript
JavaScript canvas仿代码流瀑布
2020/02/10 Javascript
JavaScript DOM常用操作代码汇总
2020/07/03 Javascript
Python 结巴分词实现关键词抽取分析
2017/10/21 Python
python爬虫系列Selenium定向爬取虎扑篮球图片详解
2017/11/15 Python
Python使用Scrapy保存控制台信息到文本解析
2017/12/27 Python
OpenCV 轮廓检测的实现方法
2019/07/03 Python
浅谈Python中re.match()和re.search()的使用及区别
2020/04/14 Python
实现CSS3中的border-radius(边框圆角)示例代码
2013/07/19 HTML / CSS
CSS3 实现footer 固定在底部(无论页面多高始终在底部)
2019/10/15 HTML / CSS
HTML5 Canvas实现图片缩放、翻转、颜色渐变的代码示例
2016/02/28 HTML / CSS
荷兰美妆护肤品海淘网站:Beautinow(中文)
2020/11/22 全球购物
请用Python写一个获取用户输入数字,并根据数字大小输出不同信息的脚本
2014/05/20 面试题
销售经理工作职责范文
2013/12/03 职场文书
舞蹈兴趣小组活动总结
2014/07/07 职场文书
市级三好学生事迹材料
2014/08/27 职场文书
临时用工协议书范本
2014/10/29 职场文书
毕业生爱心捐书倡议书
2015/04/27 职场文书
《月球之谜》教学反思
2016/02/20 职场文书
标准发言稿结尾
2019/07/18 职场文书