原創不易,希望能關注下我們,再順手點個贊~~
本文首發於政採雲前端團隊部落格: 基於 Vue 的商品主圖放大鏡方案
前言
在做電商類應用時,難免會遇到商品主圖實現放大鏡效果的場景,現有的基於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 四倍放大效果圖
核心程式碼
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 的移動等。
本文順著這個思路,做了一個簡單的實現,還有一些優化的空間,歡迎各位大佬在評論區討論。雖然程式碼看起來不是非常優雅,但是足夠明瞭,感興趣的同學可以自己嘗試一下。
招賢納士
招人,前端,隸屬政採雲前端大團隊(ZooTeam),50 餘個小夥伴正等你加入一起浪。如果你想改變一直被事折騰,希望開始能折騰事;如果你想改變一直被告誡需要多些想法,卻無從破局;如果你想改變你有能力去做成那個結果,卻不需要你;如果你想改變你想做成的事需要一個團隊去支撐,但沒你帶人的位置;如果你想改變既定的節奏,將會是“5年工作時間3年工作經驗”;如果你想改變本來悟性不錯,但總是有那一層窗戶紙的模糊… 如果你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的自己。如果你希望參與到隨著業務騰飛的過程,親手參與一個有著深入的業務理解、完善的技術體系、技術創造價值、影響力外溢的前端團隊的成長曆程,我覺得我們該聊聊。任何時間,等著你寫點什麼,發給 ZooTeam@cai-inc.com