天天看點

放大鏡效果放大鏡效果

放大鏡效果

這裡的放大鏡效果是指當滑鼠(手指)和頁面(螢幕)中一個方框内的圖檔互動時,圖檔呈現放大效果,圖檔在框内顯示的放大區域随滑鼠(頁面)位置的改變而改變

放大鏡效果放大鏡效果

示範連結:https://codesandbox.io/s/fangdajingxiaoguo-jxdfo

源碼:https://gitee.com/thisismyaddress/webpage-effects/tree/master/magnifier-effect

以下我嘗試了幾種不同的實作方式

移動圖檔框内的圖檔

這裡是通過改變圖檔框内的一張圖檔的大小和絕對定位偏移量實作最終效果

HTML的基本結構如下:

<div id="image">
    <!-- 該圖檔設定為絕對定位 -->
    <img src="./img/doge.webp" alt="doge">
</div>
           

這裡JS中涉及到的事件有

mouseenter

mouseleave

mousemove

,這些事件都和圖檔框綁定(

imageBox

mouseenter

事件處理函數中,隻需要将框中的圖檔尺寸放大即可:

imageBox.addEventListener('mouseenter', () => {
   	// 這裡将圖檔(圖檔和框一樣為正方形)放大為 450x450 ,相當于圖檔框的3倍(150x150)
    img.style.width = '450px';
})
           

mouseleave

事件處理函數中則隻需要将定位偏移的樣式和圖檔放大尺寸樣式清除即可:

// 清除所有樣式 >> 恢複原樣
imageBox.addEventListener('mouseleave', () => {
    img.style.width = '';
    img.style.top = '';
    img.style.left = '';
})
           

圖檔的放大效果的實作重點是

mousemove

的事件處理函數中關于圖檔定位偏移量的計算

這裡的思路是找出目前滑鼠位置相對于圖檔框左上角的坐标位置

x, y

,接着計算出這個坐标位置在圖檔框中的相對百分比位置(比如該點在框的正中間的話就是

0.5, 0.5

或者

50%, 50%

的位置,這個百分比是相對于框體的左上角而言的),利用這個百分比位置在放大的圖檔中找到相同的相對位置下,圖檔中的這個目标點的具體坐标位置

targetX, targetY

,然後将

x, y

targetX, targetY

這兩個點通過定位偏移重疊即可,這就要求出這兩個點的水準偏移距離和垂直偏移距離

distanceX, distanceY

,由于需要将圖檔相對于圖檔框的進行絕對定位偏移,那麼就需要進行反向偏移,則需要對求出的

distanceX, distanceY

取負值,也就是最終的定位偏移為

left: - distanceX + 'px'; top: - distanceY + 'px'

以下為需要求出的量的具體計算:

  • 目前滑鼠位置相對于圖檔框左上角的坐标

    x, y

    滑鼠在頁面中的具體位置可以通過

    e.pageX, e.pageY

    獲得(

    e

    表示滑鼠事件對象),這個具體位置是相對于頁面的左上角計算的,那麼相對于框體的左上角位置,可以通過減去框體的頂邊和左邊距離頁面的頂邊和左邊的距離(也就是

    imageBox.offsetTop, imageBox.offsetLeft

    )獲得:
    x = e.pageX - imageBox.offsetLeft;
    y = e.pageY - imageBox.offsetTop;
               
  • 坐标在圖檔框中的相對百分比位置:

    相對于整個框體的相對位置的計算需要使用到框體的高度和寬度(

    imageBox.offsetWidth, imageBox.offsetHeight

    x / imageBox.offsetWidth;
    y / imageBox.offsetHeight;
               
  • 放大的圖檔中相同相對位置下的目标點

    targetX, targetY

    :

    這需要利用到上述計算得到的相對位置,再去乘以放大的圖檔的尺寸(

    img.offsetWidth, img.offsetHeight

    )獲得
    targetX = img.offsetWidth * x / imageBox.offsetWidth;
    targetY = img.offsetHeight * y / imageBox.offsetHeight;
               
  • 需要進行偏移的距離

    distanceX, distanceY

    :

    偏移的水準距離和垂直距離就是上述兩個相對位置

    x, y

    targetX, targetY

    的水準距離和垂直距離的內插補點:
    distanceX = targetX - x;
    distanceY = targetY - y;
               
  • 将圖檔相對于圖檔框進行絕對定位偏移,需要将圖檔反向移動,是以需要對偏移距離的值取負:
    img.style.left = - distanceX + 'px';
    img.style.top = - distanceY + 'px';
               

至此對于圖檔定位偏移量的計算就結束了(具體計算可以參見下圖解析)

放大鏡效果放大鏡效果

是以最終的

mousemove

的事件處理函數為:

imageBox.addEventListener('mousemove', (e) => {
    // 目前滑鼠位置相對于圖檔框左上角的坐标x, y
    let x = e.pageX - imageBox.offsetLeft;
    let y = e.pageY - imageBox.offsetTop;
    // 放大的圖檔中相同相對位置下的目标點targetX, targetY
    let targetX = img.offsetWidth * x / imageBox.offsetWidth;
    let targetY = img.offsetHeight * y / imageBox.offsetHeight;
    // 需要進行偏移的距離distanceX, distanceY
    let distanceX = targetX - x;
    let distanceY = targetY - y;
    // 将圖檔相對于圖檔框進行絕對定位偏移,需要将圖檔反向移動,是以需要對偏移距離的值取負
    img.style.left = - distanceX + 'px';
    img.style.top = - distanceY + 'px';
})
           

移動圖檔框的背景圖檔

這裡将上述的圖檔框内的圖檔移除,直接将要放大的圖檔作為圖檔框的背景圖檔,通過改變背景圖檔大小和背景圖檔位置來實作最終效果

HTML的基本結構如下:

這裡涉及到的三個JS事件還是

mouseenter

mouseleave

mousemove

,這些事件都和圖檔框綁定(

imageBox

mouseenter

事件處理函數中,隻需要将框的背景圖檔尺寸放大即可:

image.addEventListener('mouseenter', (e) => {
    // image.style.backgroundSize = '450px';
    e.target.style.backgroundSize = '450px';
});
           

mouseleave

事件處理函數中則是隻需要将背景圖檔的尺寸和位置樣式清除即可:

image.addEventListener('mouseleave', (e) => {
    // image.style.backgroundSize = '';
    // image.style.backgroundPosition = '';
    e.target.style.backgroundSize = '';
    e.target.style.backgroundPosition = '';
});
           

這裡的實作重點依然是對圖檔偏移量的計算,不過不一樣的是,這裡的背景圖檔尺寸資訊比較難找到相關屬性值來動态擷取,不能像上述采用絕對定位的做法那樣求出具體的偏移距離。而如果設為定值,那麼當背景圖檔的放大尺寸變了,程式要改的地方就比較多了

這裡利用背景圖檔位置的百分比取值來實作将圖檔移動到目标位置的目的(這樣甚至都不用計算具體偏移距離了)

這裡簡單講一下背景圖檔位置

background-position

取值為百分數時的表現:

百分比值的偏移指定圖檔的相對位置和容器的相對位置重合,執行的是下述公式

(container width - image width) * (position x%) = (x offset value)
(container height - image height) * (position y%) = (y offset value)
參考:https://developer.mozilla.org/zh-CN/docs/Web/CSS/background-position
           

從上述公式可以看出當背景圖檔大小和容器大小相同時,使用百分比取值是不會有偏移的

其實百分比取值的表現說白了就是指定一個相對于圖檔左上角的一個相對位置,這個位置會和容器相對于左上角的相同的相對位置重疊,比如

background-position: 50% 50%

,這個點指定的是背景圖檔的正中心,那麼此時該取值會使背景圖檔的正中心和容器的正中心重合

回歸正題,此時采用背景圖檔位置的百分比取值,則隻需要計算出目前滑鼠位置相對于圖檔框左上角的坐标

x, y

在圖檔框中的相對百分比位置,就可以實作背景圖檔的正确偏移

放大鏡效果放大鏡效果
image.addEventListener('mousemove', (e) => {
    // let x = e.pageX - image.offsetLeft;
    // let y = e.pageY - image.offsetTop;
    // // 偏移百分比
    // let xOffset = x / image.offsetWidth * 100;
    // let yOffset = y / image.offsetHeight * 100;

    // image.style.backgroundPosition = xOffset + '% ' + yOffset + '%';

    // 計算出滑鼠目前相對于圖檔框左上角的坐标
    let x = e.pageX - e.target.offsetLeft;
    let y = e.pageY - e.target.offsetTop;

    // 計算出相對位置
    // 乘 100 是為了轉成百分數的大小
    let xOffset = x / e.target.offsetWidth * 100;
    let yOffset = y / e.target.offsetHeight * 100;

    e.target.style.backgroundPosition = xOffset + '% ' + yOffset + '%';
});
           

移動圖檔框的背景圖檔——使用盡可能多的CSS特性

這裡主要是我看了B站的一個視訊教程後的一個嘗試,雖然其中的步驟略顯繁雜,但在對其實作的過程讓我學習到了新的知識

這裡的實作使用了盡可能多的CSS特性,另外還嘗試了未曾用過的API(

getBoundingClientRect()

e.offsetX

e.offsetY

這裡依然使用移動背景圖檔的方式,涉及到的JS事件還是

mouseenter

mouseleave

mousemove

(都和圖檔框綁定)

mouseenter

中放大圖檔的操作不再是直接放大背景圖檔,而是通過添加屬性的方式間接實作:

這裡為圖檔框設定一個

zoomed="1"

的屬性,來表示此時處于放大狀态,這樣的話需要配合CSS選擇器來改變背景圖檔尺寸:

#image[zoomed] {
    background-size: 450px auto;
}
           
image.addEventListener('mouseenter', (e) => {
    e.target.setAttribute('zoomed', 1);
})
           

當滑鼠離開時需要圖檔縮小,那麼隻需要移除屬性即可:

image.addEventListener('mouseleave', (e) => {
	e.target.removeAttribute('zoomed');
});
           
放大鏡效果放大鏡效果

接下來在

mousemove

實踐處理函數中還是通過改變背景圖檔位置的百分比取值實作圖檔位置的偏移,但這裡不直接改變背景圖檔位置的樣式,而是通過改變兩個CSS屬性(

--x

--y

)來間接改變圖檔位置樣式。并且圖檔框尺寸是通過

getBoundingClientRect()

傳回的一個對象的

width

height

屬性擷取。另外目前滑鼠位置相對于框體左上角的坐标位置不再需要特地進行計算獲得,而是直接使用

e.offsetX

e.offsetY

,這兩個屬性表示的就是相對于框體左上角計算的滑鼠位置

image.addEventListener('mousemove', moveHandler(e) {
    // 這裡特地嘗試使用了 getBoundingClientRect()
    // 這裡不作解釋,隻要知道它可以方法傳回元素的大小及其相對于視口的位置即可
    let rect = e.target.getBoundingClientRect();

	// 這裡的 e.offsetX 和 e.offsetY 表示的是滑鼠相對于框體左上角的位置,相當于上述特地計算的 x 和 y
	// rect.width 相當于 e.target.offsetWidth ,rect.height 相當于 e.target.offsetHeight
    let xOffset = e.offsetX / rect.width;
    let yOffset = e.offsetY / rect.height;

	// 通過改變兩個CSS屬性(--x和--y)來間接改變圖檔位置樣式
    e.target.style.setProperty('--x', xOffset);
    e.target.style.setProperty('--y', yOffset);
});
           

上述事件處理函數中最後設定了兩個CSS變量的值,這時應該使用起來,使其生效:

#image[zoomed] {
    background-size: 450px auto;
    /* 将 --x 和 --y 通過 var() 解析出計算值并應用到背景圖檔位置樣式中 */
    background-position: calc(var(--x) * 100%) calc(var(--y) * 100%);
}
           

嘗試相容移動端

上述的應用都是在PC端下的,是無法在移動端使用的,因為移動端下并沒有滑鼠事件,是以上述的事件都不會觸發,自然的,放大鏡的效果就無法實作了

在移動端下需要使用觸摸事件來實作這個效果,這裡涉及到的觸摸事件有

touchstart

touchend

touchmove

,這些事件都和圖檔框綁定。在具體實作上的思路和上述一緻:觸摸開始(

touchstart

)時圖檔放大,觸摸結束(

touchend

)時圖檔恢複原來大小并清除圖檔偏移樣式,觸摸點移動時(

touchmove

)需要計算圖檔的偏移。隻是在具體實作上需要進行一些編碼調整

這裡采用移動背景圖檔的方式(使用移動圖檔框内圖檔的方式大同小異,隻是在計算偏移量方面的計算量會大一點),并且是對上面的那個使用了盡可能多的CSS屬性的做法進行的移動端相容

touchstart

touchend

的事件處理函數的内容可以和上述

mouseenter

mouseleave

的分别一樣:

image.addEventListener('touchstart', (e) => {
    e.target.setAttribute('zoomed', 1);
});

image.addEventListener('touchend', (e) => {
    e.target.removeAttribute('zoomed');
});
           

這裡差異較大的地方在于

touchmove

的事件處理函數内容。

  • 由于觸發觸摸事件的觸摸點可能不止一個(多個手指觸摸螢幕),是以這裡在進行計算時需要特别的指明一個觸摸點,使用

    e.touches[0]

    而不是

    e

    指定一個觸摸事件對象,避免出現混亂,那麼觸摸事件源可以通過

    e.touches[0].target

    獲得
  • 由于觸摸事件對象并沒有

    e.offsetX

    e.offsetY

    這兩個屬性,是以觸摸點位置相對于框體左上角的坐标依然需要特地進行計算。這裡由于需要特地的去嘗試

    getBoundingClientRect()

    ,在計算過程中為了擷取框體的位置資訊,使用該方法傳回的對象的

    top

    left

    屬性獲得,并且要注意:該方法傳回的框體位置資訊是相對于視口而不是相對于頁面而言的,是以如果頁面發生滾動時,使用相對于頁面計算的觸摸點位置屬性

    pageX

    pageY

    計算出的結果是不準确,這裡應該采用同樣相對于視口計算的觸摸點位置屬性

    clientX

    clientY

    // 使用相對于視口計算的觸摸點位置 clientX 和 clientY
    // rect = e.target.getBoundingClientRect()
    x = e.touches[0].clientX - rect.left;
    y = e.touches[0].clientY - rect.top;
    
    // 如果要使用相對于頁面計算的觸摸點位置 pageX 和 pageY,
    // 則需要采用同樣相對于頁面計算的框體位置資訊 offsetLeft 和 offsetTop
    x = e.touches[0].pageX - e.touches[0].target.offsetLeft;
    y = e.touches[0].pageY - e.touches[0].target.offsetTop;
               
    這樣就可以計算出相對位置的資訊了:
    // rect = e.target.getBoundingClientRect()
    xOffset = x / rect.width;
    yOffset = y / rect.height;
    
    // xOffset = x / e.touches[0].target.offsetWidth;
    // yOffset = y / e.touches[0].target.offsetHeight;
               

由于在對螢幕進行觸摸移動時,預設會拖動頁面,為了避免這種情況,需要使用

e.preventDefault()

,另外由于一個觸摸點長時間接觸螢幕而不移動時,會觸發頁面菜單,需要通過

contextmenu

事件來禁止長按菜單

放大鏡效果放大鏡效果
// 禁止長按菜單
document.addEventListener('contextmenu', (e) => {
    e.preventDefault();
})
           
image.addEventListener('touchmove', (e) => {
    // 防止頁面被拖動
    e.preventDefault();

    let rect = e.target.getBoundingClientRect();

    // 由于 touch 事件沒有 offsetX 和 offsetY 屬性,是以需要進行額外的計算操作
    // 可能有多個觸摸點,這裡隻取一個觸摸點來計算觸摸的位置 e.touches[0]
    // 使用相對于視口計算的觸摸點位置 clientX 和 clientY(方法傳回的框體位置資訊是相對于視口而言的)
    // rect = e.target.getBoundingClientRect()
    let x = e.touches[0].clientX - rect.left;
    let y = e.touches[0].clientY - rect.top;

    // 如果要使用相對于頁面計算的觸摸點位置 pageX 和 pageY,
    // 則需要采用同樣相對于頁面計算的框體位置資訊 offsetLeft 和 offsetTop
    // let x = e.touches[0].pageX - e.touches[0].target.offsetLeft;
    // let y = e.touches[0].pageY - e.touches[0].target.offsetTop;

    let xOffset = x / rect.width;
    let yOffset = y / rect.height;

    // let xOffset = x / e.touches[0].target.offsetWidth;
    // let yOffset = y / e.touches[0].target.offsetHeight;

    e.target.style.setProperty('--x', xOffset);
    e.target.style.setProperty('--y', yOffset);
});
           

此時有個問題就是當觸摸點觸摸螢幕但不移動時,圖檔顯示的放大區域可能是不正确的(如下,即使觸摸的位置是其他位置,也總是顯示圖檔左上角的那隻耳朵)

放大鏡效果放大鏡效果

這是因為此時觸摸點不移動,就不會觸發

touchmove

事件,導緻無法對圖檔進行移動,是以這裡需要在

touchstart

事件處理函數中添加相關步驟移動圖檔的步驟

// 這裡将觸摸移動事件處理函數體抽離為一個獨立的函數
image.addEventListener('touchstart', (e) => {
    e.target.setAttribute('zoomed', 1);
    // 避免重新觸摸後未移動時圖檔位置不正确
    moveHandler(e);
});

image.addEventListener('touchmove', moveHandler);

function moveHandler(e) {
    // 防止頁面被拖動
    e.preventDefault();

    let rect = e.target.getBoundingClientRect();

    // 由于 touch 事件沒有 offsetX 和 offsetY 屬性,是以需要進行額外的計算操作
    // 可能有多個觸摸點,這裡隻取一個觸摸點來計算觸摸的位置 e.touches[0]
    // 使用相對于視口計算的觸摸點位置 clientX 和 clientY(方法傳回的框體位置資訊是相對于視口而言的)
    // rect = e.target.getBoundingClientRect()
    let x = e.touches[0].clientX - rect.left;
    let y = e.touches[0].clientY - rect.top;

    // 如果要使用相對于頁面計算的觸摸點位置 pageX 和 pageY,則需要采用同樣相對于頁面計算的框體位置資訊 offsetLeft 和 offsetTop
    // let x = e.touches[0].pageX - e.touches[0].target.offsetLeft;
    // let y = e.touches[0].pageY - e.touches[0].target.offsetTop;

    let xOffset = x / rect.width;
    let yOffset = y / rect.height;

    // let xOffset = x / e.touches[0].target.offsetWidth;
    // let yOffset = y / e.touches[0].target.offsetHeight;

    e.target.style.setProperty('--x', xOffset);
    e.target.style.setProperty('--y', yOffset);
}
           

修改後效果如下(現在觸摸哪裡,放大的位置就是哪裡)

放大鏡效果放大鏡效果

現在還有一個問題就是當觸摸點在圖檔框内移動到框外區域時,

touchmove

事件還是處于被觸發的狀态,此時觸摸點的位置在框外,而對于圖檔偏移量的計算依然根據觸摸點位置計算,那麼此時就會導緻圖檔被移動到可見區域外

放大鏡效果放大鏡效果

為解決這個問題需要限制圖檔可偏移的範圍,隻需要做些簡單判斷即可

function moveHandler(e) {
    // ......
    
    // 使用相對于視口計算的觸摸點位置 clientX 和 clientY(方法傳回的框體位置資訊是相對于視口而言的)
    // rect = e.target.getBoundingClientRect()
    let x = e.touches[0].clientX - rect.left;
    let y = e.touches[0].clientY - rect.top;
    // 限制範圍
    x = x < 0 ? 0 : x;
    x = x > rect.width ? rect.width : x;
    y = y < 0 ? 0 : y;
    y = y > rect.height ? rect.height : y;

    // 如果要使用相對于頁面計算的觸摸點位置 pageX 和 pageY,則需要采用同樣相對于頁面計算的框體位置資訊 offsetLeft 和 offsetTop
    // let x = e.touches[0].pageX - e.touches[0].target.offsetLeft;
    // let y = e.touches[0].pageY - e.touches[0].target.offsetTop;
    // 限制範圍
    // x = x < 0 ? 0 : x;
    // x = x > e.touches[0].target.offsetWidth ? e.touches[0].target.offsetWidth : x;
    // y = y < 0 ? 0 : y;
    // y = y > e.touches[0].target.offsetHeight ? e.touches[0].target.offsetHeight : y;

    // ......
}
           

為了讓代碼看起來NB一點,可以将上述的判斷語句合并一下:

function moveHandler(e) {
    // ......
    
    // 限制範圍
    x = Math.min(Math.max(0, x), rect.width);
    y = Math.min(Math.max(0, y), rect.height);
    // x = x < 0 ? 0 : x;
    // x = x > rect.width ? rect.width : x;
    // y = y < 0 ? 0 : y;
    // y = y > rect.height ? rect.height : y;

    // ......
    // 限制範圍
    // x = Math.min(Math.max(0, x), e.touches[0].target.offsetWidth);
    // y = Math.min(Math.max(0, y), e.touches[0].target.offsetHeight);
    // x = x < 0 ? 0 : x;
    // x = x > e.touches[0].target.offsetWidth ? e.touches[0].target.offsetWidth : x;
    // y = y < 0 ? 0 : y;
    // y = y > e.touches[0].target.offsetHeight ? e.touches[0].target.offsetHeight : y;

    // ......
}
           

至此,觸摸點即使移動出了圖檔框外也可以正确顯示放大的圖檔了

放大鏡效果放大鏡效果

至此移動端的放大鏡效果實作就完成了,現在需要将移動端的相關代碼和PC端的相關代碼合并達成相容的目的。為了友善為滑鼠事件和觸摸事件賦予事件處理函數,同時使代碼結構看起來優雅一點,這裡我将事件處理函數的代碼進行抽離。由于

mouseenter

mouseleave

的事件處理函數分别和

touchstart

touchend

的内容幾乎一緻,是以這四個事件的處理函數并不需要做太多的差異編碼。這裡有一個禁止長按菜單的操作(

contextMenu

事件)是隻需要在移動端内設定的,是以需要判斷此時觸發的事件是移動端事件還是PC端的,以此來決定是否要禁止長按菜單

image.addEventListener('mouseenter', enterHandler);
image.addEventListener('mouseleave', leaveHandler);

image.addEventListener('touchstart', enterHandler);
image.addEventListener('touchend', leaveHandler);

// 這裡需要特地抽離出一個事件處理函數,因為無法移除一個為匿名函數的監聽器
function prevent(e) {
    e.preventDefault();
}

function enterHandler(e) {
    e.target.setAttribute('zoomed', 1);
    // 避免重新觸摸後未移動時圖檔位置不正确
    moveHandler(e);

    if (e.type === 'touchstart') {
        // 禁止長按菜單
        document.addEventListener('contextmenu', prevent)
    } else {
        // 取消禁止長按菜單
        document.removeEventListener('contextmenu', prevent);
    }
}

function leaveHandler(e) {
    e.target.removeAttribute('zoomed');
}
           

mousemove

touchmove

事件的事件處理函數内容差異較大,需要進行的差異編碼比較多,其中重要的一個就是對事件類型的判斷(詳見以下代碼)

image.addEventListener('mousemove', moveHandler);
image.addEventListener('touchmove', moveHandler);

function moveHandler(e) {
    let rect = e.target.getBoundingClientRect();

    // 由于 touch 事件沒有 offsetX 和 offsetY 屬性,是以需要進行額外的計算操作
    let x = 0;
    let y = 0;

    // 對事件類型的判斷
    if (['touchstart', 'touchend', 'touchmove'].includes(e.type)) {
        // 防止頁面被拖動
    	e.preventDefault();
        
        // 可能有多個觸摸點,這裡隻取一個觸摸點來計算觸摸的位置 e.touches[0]
        // 使用相對于視口計算的觸摸點位置 clientX 和 clientY(方法傳回的框體位置資訊是相對于視口而言的)
        // rect = e.target.getBoundingClientRect()
        x = e.touches[0].clientX - rect.left;
        y = e.touches[0].clientY - rect.top;
        // 限制範圍
        x = Math.min(Math.max(0, x), rect.width);
        y = Math.min(Math.max(0, y), rect.height);
        // x = x < 0 ? 0 : x;
        // x = x > rect.width ? rect.width : x;
        // y = y < 0 ? 0 : y;
        // y = y > rect.height ? rect.height : y;
    } else {
        x = e.offsetX;
        y = e.offsetY;
    }

    let xOffset = x / rect.width;
    let yOffset = y / rect.height;

    e.target.style.setProperty('--x', xOffset);
    e.target.style.setProperty('--y', yOffset);
}
           

至此這個相容就完成了

精簡代碼

可以看到,在我嘗試使用

getBoundingClientRect()

以及

e.offsetX, e.offsetY

時,相容PC端和移動端時,代碼量有點多,實際上可以改變做法,精簡代碼

image.addEventListener('mousemove', moveHandler);
image.addEventListener('touchmove', moveHandler);s

function moveHandler(e) {
    let eventObj = null;

    if (['touchstart', 'touchend', 'touchmove'].includes(e.type)) {
        // 防止頁面被拖動
        e.preventDefault();

        // 可能有多個觸摸點,這裡隻取一個觸摸點來計算觸摸的位置 e.touches[0]
        eventObj = e.touches[0];
    } else {
        eventObj = e;
    }

    let x = eventObj.pageX - eventObj.target.offsetLeft;
    let y = eventObj.pageY - eventObj.target.offsetTop;

    // 限制範圍
    x = Math.min(Math.max(0, x), eventObj.target.offsetWidth);
    y = Math.min(Math.max(0, y), eventObj.target.offsetHeight);

    let xOffset = x / eventObj.target.offsetWidth;
    let yOffset = y / eventObj.target.offsetHeight;

    e.target.style.setProperty('--x', xOffset);
    e.target.style.setProperty('--y', yOffset);
}
           

總結

放大鏡效果涉及到的事件在PC端是

mouseenter

mouseleave

mousemove

,在移動端是

touchstart

touchend

touchmove

。實作的方式有不止一種,但總的來說就三個步驟:進入圖檔框時放大圖檔,離開圖檔框時恢複圖檔大小并将圖檔偏移樣式清除,在圖檔框内移動時圖檔的移動随滑鼠或接觸點的位置進行變化,保證可視區域目前顯示的圖檔放大位置為 滑鼠或接觸點在圖檔框相對位置 和 放大的圖檔相同相對位置 重疊時的圖檔部分

參考:

https://www.bilibili.com/video/BV12N411974Q

繼續閱讀