基於 Vue 的商品主圖放大鏡方案

政採雲前端團隊發表於2019-09-18

原創不易,希望能關注下我們,再順手點個贊~~

本文首發於政採雲前端團隊部落格: 基於 Vue 的商品主圖放大鏡方案

一玲.png

前言

在做電商類應用時,難免會遇到商品主圖實現放大鏡效果的場景,現有的基於Vue的第三方包不多並且無法直接複用,今天,我來分享一種高穩定性的基於 Vue 的圖片放大鏡方法。

實現原理

放大鏡的原理用一句話概括,就是根據小圖上的滑鼠位置去定位大圖。

圖1 原理圖(以2倍放大為例)

image-20190915202151028

相信原理圖已經畫的很明白了, 圖中,左側框是小圖框,其藍色區域為圖片遮罩層(需放大區域),右側框是整個大圖目前所在區域,其藍色區域是放大區域,設定超出隱藏,就實現了放大遮罩區域的效果。

顯然,兩塊藍色區域存在著某種對應關係,即遮罩的左上角位置(相對於小圖,以下稱 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 寬圖展示

基於 Vue 的商品主圖放大鏡方案

圖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

基於 Vue 的商品主圖放大鏡方案

推薦閱讀

Vue 元件資料通訊方案總結

自動化 Web 效能優化分析方案

CSS 層疊上下文(Stacking Context)

相關文章