基于Vue的商品主圖放大鏡方案詳解
前言
在做電商類應(yīng)用時(shí),難免會(huì)遇到商品主圖實(shí)現(xiàn)放大鏡效果的場(chǎng)景,現(xiàn)有的基于 Vue 的第三方包不多并且無(wú)法直接復(fù)用,今天,我來(lái)分享一種高穩(wěn)定性的基于 Vue 的圖片放大鏡方法。
實(shí)現(xiàn)原理
放大鏡的原理用一句話概括,就是根據(jù)小圖上的鼠標(biāo)位置去定位大圖。
圖1 原理圖(以2倍放大為例)

相信原理圖已經(jīng)畫(huà)的很明白了, 圖中,左側(cè)框是小圖框,其藍(lán)色區(qū)域?yàn)閳D片遮罩層(需放大區(qū)域),右側(cè)框是整個(gè)大圖目前所在區(qū)域,其藍(lán)色區(qū)域是放大區(qū)域,設(shè)置超出隱藏,就實(shí)現(xiàn)了放大遮罩區(qū)域的效果。
顯然,兩塊藍(lán)色區(qū)域存在著某種對(duì)應(yīng)關(guān)系,即遮罩的左上角位置(相對(duì)于小圖,以下稱 X 坐標(biāo))和放大區(qū)域(相對(duì)于大圖)的左上角位置是成比例的,即放大倍數(shù)。計(jì)算出 X 坐標(biāo)后,適當(dāng)調(diào)整背景圖的位置,使大圖向反方向移動(dòng) scale 倍的 X 坐標(biāo)即可。
X 坐標(biāo)為(maskX,maskY),以計(jì)算 maskX 為例:
鼠標(biāo)移動(dòng)中會(huì)產(chǎn)生 e.clientX ,標(biāo)識(shí)鼠標(biāo)與瀏覽器左側(cè)的距離,小圖與瀏覽器左側(cè)的距離是 left ,由于遮罩始終是一個(gè)以鼠標(biāo)為中心的正方形,所以:
maskX = e.clientX - left - mask/2
同理,
maskY = e.clientY - top - mask/2
大圖的對(duì)應(yīng)樣式設(shè)置為:
{
left: - maskX * scale + 'px';
top: - maskY * scale + 'px';
}
效果演示
圖2 長(zhǎng)圖展示

圖3 寬圖展示

圖4 兩倍放大效果圖

圖5 四倍放大效果圖
核心代碼
HTML
一般放大鏡實(shí)現(xiàn)的是 1:1 等寬等高的正方形圖片,這里兼容了其他比例的圖片,設(shè)置圖片為垂直居中對(duì)齊,包括小圖,大圖。如果小圖不夠充滿整個(gè)小圖框,余留下的空白部分也可以有放大效果,只不過(guò)放大結(jié)果依然是空白。 這樣只需計(jì)算背景圖的移動(dòng)距離,不用過(guò)多的關(guān)注圖片定位問(wèn)題。
<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
這里主要有三個(gè)事件函數(shù)。
handOver:鼠標(biāo)進(jìn)入到小圖框上的事件,此時(shí)顯示遮罩和放大區(qū)域,并計(jì)算小圖框的位置信息。
handOver() {
// 計(jì)算小圖框在瀏覽器中的位置
this.imgObj = this.$el.getElementsByClassName('small-box')[0];
this.imgRectNow = this.imgObj.getBoundingClientRect();
this.showMagnifier = true;
this.showMask = true;
}
handMove:鼠標(biāo)在小圖上的移動(dòng)事件,此事件發(fā)生在 handOver 之后,計(jì)算數(shù)據(jù),移動(dòng)遮罩以及背景圖;
handMove(e) {
// 計(jì)算初始的遮罩左上角的坐標(biāo)
let objX = e.clientX - this.imgRectNow.left;
let objY = e.clientY - this.imgRectNow.top;
// 計(jì)算初始的遮罩左上角的坐標(biāo)
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;
}
// 遮罩移動(dòng)
this.transformMask = `translate(${maskX}px, ${maskY}px)`;
// 背景圖移動(dòng)
this.moveLeft = - maskX * this.configs.scale + "px";
this.moveTop = - maskY * this.configs.scale + "px";
}
handOut:鼠標(biāo)離開(kāi)小圖事件,此時(shí)無(wú)放大鏡效果,隱藏遮罩和放大區(qū)域。
handOut() {
this.showMagnifier = false;
this.showMask = false;
}
以上三個(gè)事件基本上就實(shí)現(xiàn)了圖片的放大鏡功能。
但仔細(xì)看,你會(huì)發(fā)現(xiàn)每次移入小圖框都會(huì)觸發(fā)一次 handOver 事件,并且計(jì)算一次小圖框 DOM (imgObj) 。
為了優(yōu)化此問(wèn)題,可以用 init 標(biāo)識(shí)是否是頁(yè)面加載后首次觸發(fā) handOver 事件,如果是初始化就計(jì)算imgObj 信息,否則不計(jì)算。
handOver() {
if (!this.init) {
this.init = true;
// 原 handOver 事件
...
}
this.showMagnifier = true;
this.showMask = true;
},
在測(cè)試的過(guò)程中,發(fā)現(xiàn)頁(yè)面滾動(dòng)后,會(huì)出現(xiàn)遮罩定位錯(cuò)誤的情況,原來(lái)是因?yàn)槌跏蓟瘯r(shí),我們固定死了小圖框的位置信息(存放在 this.imgRectNow ),導(dǎo)致 handMove 事件中的移動(dòng)數(shù)據(jù)計(jì)算錯(cuò)誤。
解決這個(gè)問(wèn)題有兩種方案:
- 監(jiān)聽(tīng) scroll 事件,更新 this.imgRectNow;
- 在 handMove 事件中更新 this.imgRectNow。
這里選擇了第二種。
handMove(e) {
// 動(dòng)態(tài)獲取小圖的位置(或者監(jiān)聽(tīng) scroll )
let imgRectNow = this.imgObj.getBoundingClientRect();
let objX = e.clientX - imgRectNow.left;
let objY = e.clientY - imgRectNow.top;
// 原 handMove 事件剩余內(nèi)容
...
},
綜合以上,我們已經(jīng)實(shí)現(xiàn)了一個(gè)完美的圖片放大鏡功能。最終的 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) {
// 動(dòng)態(tài)獲取小圖的位置(或者監(jiān)聽(tīng) scroll )
let imgRectNow = this.imgObj.getBoundingClientRect();
let objX = e.clientX - imgRectNow.left;
let objY = e.clientY - imgRectNow.top;
// 計(jì)算初始的遮罩左上角的坐標(biāo)
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;
}
// 遮罩移動(dòng)
this.transformMask = `translate(${maskX}px, ${maskY}px)`;
// 背景圖移動(dòng)
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;
}
}
使用方法
本示例中的固定參數(shù):小圖框:420 * 420 。
程序可接受參數(shù):
// 小圖地址
src: {
type: String,
},
// 大圖地址
bigSrc: {
type: String,
},
// 配置項(xiàng)
configs: {
type: Object,
default() {
return {
width:420,//放大區(qū)域
height:420,//放大區(qū)域
maskWidth:210,//遮罩
maskHeight:210,//遮罩
maskColor:'rgba(25,122,255,0.5)',//遮罩樣式
maskOpacity:0.6,
scale:2,//放大比例
};
}
}
文中圖 2 是一張長(zhǎng)圖,小圖的最大邊不超過(guò) 836px(二倍圖) ,大圖為了視覺(jué)效果,分辨率盡量高點(diǎn),程序會(huì)根據(jù)配置項(xiàng)自動(dòng)設(shè)置對(duì)應(yīng)的 height , width ,長(zhǎng)圖與寬圖的效果對(duì)比可參考圖3。
配置項(xiàng)可根據(jù)應(yīng)用場(chǎng)景自行設(shè)置,本文示例的配置項(xiàng)是 2 倍放大,效果可參考圖 4,四倍放大效果可參考圖 5。
總結(jié)
其實(shí)圖片放大鏡的實(shí)現(xiàn)思路沒(méi)有那么復(fù)雜,核心點(diǎn)有兩點(diǎn):
- 小圖、大圖的定位,遮罩和放大區(qū)域的創(chuàng)建方法
- 放大鏡的原理理解,并用代碼實(shí)現(xiàn) DOM 的移動(dòng)等。
本文順著這個(gè)思路,做了一個(gè)簡(jiǎn)單的實(shí)現(xiàn),還有一些優(yōu)化的空間,歡迎各位大佬在評(píng)論區(qū)討論。雖然代碼看起來(lái)不是非常優(yōu)雅,但是足夠明了,感興趣的同學(xué)可以自己嘗試一下。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
深入理解vue.js雙向綁定的實(shí)現(xiàn)原理
vue.js是MVVM結(jié)構(gòu)的,同類的還有AngularJs;至于MVC、MVP、MVVM的比較網(wǎng)上已經(jīng)有很多了,這樣不再重復(fù)。這篇文章將給大家深入的介紹vue.js雙向綁定的實(shí)現(xiàn)原理,有需要的朋友們可以參考借鑒,下面跟著小編一起來(lái)看看吧。2016-12-12
vue中如何實(shí)現(xiàn)pdf文件預(yù)覽的方法
這篇文章主要介紹了vue中如何實(shí)現(xiàn)pdf文件預(yù)覽的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-07-07
element step組件在另一側(cè)加時(shí)間軸顯示
本文主要介紹了element step組件在另一側(cè)加時(shí)間軸顯示,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-06-06
vue新建項(xiàng)目并配置標(biāo)準(zhǔn)路由過(guò)程解析
這篇文章主要介紹了vue新建項(xiàng)目并配置標(biāo)準(zhǔn)路由過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-12-12
Vue2利用Axios發(fā)起請(qǐng)求的詳細(xì)過(guò)程記錄
有很多時(shí)候你在構(gòu)建應(yīng)用時(shí)需要訪問(wèn)一個(gè)API并展示其數(shù)據(jù),做這件事的方法有好幾種,而使用基于promise的HTTP客戶端axios則是其中非常流行的一種,這篇文章主要給大家介紹了關(guān)于Vue2利用Axios發(fā)起請(qǐng)求的詳細(xì)過(guò)程,需要的朋友可以參考下2021-12-12

