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

原創不易,但願能關注下咱們,再順手點個贊~~

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

前言

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

實現原理

放大鏡的原理用一句話歸納,就是根據小圖上的鼠標位置去定位大圖。html

圖1 原理圖(以2倍放大爲例)前端

image-20190915202151028

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

顯然,兩塊藍色區域存在着某種對應關係,即遮罩的左上角位置(相對於小圖,如下稱 X 座標)和放大區域(相對於大圖)的左上角位置是成比例的,即放大倍數。計算出 X 座標後,適當調整背景圖的位置,使大圖向反方向移動 scale 倍的 X 座標便可。java

X 座標爲(maskX,maskY),以計算 maskX 爲例:瀏覽器

鼠標移動中會產生 e.clientX ,標識鼠標與瀏覽器左側的距離,小圖與瀏覽器左側的距離是 left ,因爲遮罩始終是一個以鼠標爲中心的正方形,因此:性能優化

maskX = e.clientX - left - mask/2函數

同理,post

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

推薦閱讀

Vue 組件數據通訊方案總結

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

CSS 層疊上下文(Stacking Context)

相關文章
相關標籤/搜索