基于js?+?html2canvas實現(xiàn)網(wǎng)頁放大鏡功能
需求描述
開始之前,我們還是簡單梳理一下需求:
- 屏幕右下角放置操作按鈕,用于啟用或禁用放大鏡。
- 啟用放大鏡時:
- 頁面禁止交互(如滾動)
- 放大鏡默認展示在屏幕正中間
- 放大鏡支持調(diào)整尺寸(四個方向,即上、右、下、左)
- 放大鏡支持拖拽以展示不同的內(nèi)容
- 放大鏡的內(nèi)容直接呈現(xiàn)在放大鏡元素中
需求看似簡單,但實際上需要注意的點特別多,這里首先拋出幾個問題供大家思考:
- 啟用放大鏡時,如何截取屏幕當(dāng)前可視區(qū)域的內(nèi)容,并將其作為放大鏡的圖片源數(shù)據(jù)?
- 由于網(wǎng)頁是相對于屏幕左上角開始布局的,放大鏡可以很好的處理向右和向下的尺寸調(diào)整,那如果是向左或者向上呢?
- 拖拽放大鏡時,如何處理邊界?
- ....
思路
為了便于復(fù)用,將放大鏡邏輯封裝成類,樣式單獨抽離,后續(xù)使用時直接引入相應(yīng)的腳本和樣式即可。
在屏幕放置懸浮按鈕,用于激活放大鏡(做成開關(guān)效果),當(dāng)用戶點擊懸浮按鈕,掛載放大鏡,再次點擊,卸載放大鏡。
掛載放大鏡時:
- 禁止?jié)L動:可設(shè)置
body
標(biāo)簽的overflow
屬性為hidden
。 - 禁止交互:顯示透明遮罩層。
- 截取屏幕:通過 html2canvas 截取當(dāng)前可視區(qū)域的內(nèi)容作為放大鏡的源數(shù)據(jù)。
- 創(chuàng)建元素:基于瀏覽器的 DOM API 創(chuàng)建放大鏡相關(guān)的必要元素(后文講解)。
卸載放大鏡時:
- 設(shè)置
body
標(biāo)簽的overflow
屬性為auto
,使其支持滾動。 - 移除遮罩
- 移除事件監(jiān)聽
- 禁止?jié)L動:可設(shè)置
處理放大鏡的拖拽和縮放,可基于
mousedown
mousemove
mouseup
事件,計算尺寸和坐標(biāo)實現(xiàn)。
更多細節(jié),請參考具體實現(xiàn)。
實現(xiàn)
提示:
本文主要講解網(wǎng)頁放大鏡的實現(xiàn),頁面內(nèi)容及全局樣式不會在本文中講解,各位看官可根據(jù)實際需求布局你的頁面。
為了更好的理解,本文主要使用原生
HTML
CSS
JavaScript
實現(xiàn)。
準(zhǔn)備工作
首先請按如下目錄解構(gòu)創(chuàng)建項目:
. ├── libs │ ├── magnifier.css # 放大鏡相關(guān)樣式 │ └── magnifier.js # 放大鏡核心邏輯 ├── index.css # 全局樣式 ├── index.html # 布局 └── index.js # 腳本
布局元素
首先分析實現(xiàn)放大鏡功能所需的必要元素:
- 觸發(fā)按鈕(triggerButton):懸?。ü潭ǘㄎ唬┰谄聊挥覀?cè),控制放大鏡的掛載和卸載。
- 放大鏡容器·外層(container):遮罩,懸浮鋪滿屏幕可視區(qū)域,結(jié)合頁面
overflow
屬性可禁止用戶交互。 - 屏幕截圖(screenshots):源數(shù)據(jù),使用 canvas 存儲,便于裁剪,懸浮鋪滿屏幕可視區(qū)域,設(shè)置
opacity
為0
。 - 放大鏡容器·內(nèi)層 / 觸發(fā)縮放區(qū)域(magnifier):控制放大鏡容器大小和位置,同時用戶鼠標(biāo)在此區(qū)域按下,激活縮放狀態(tài)。
- 觸發(fā)拖拽區(qū)域(dragBox):當(dāng)用戶鼠標(biāo)在此區(qū)域按下,激活拖拽狀態(tài)。
- 放大鏡內(nèi)容(scaleImg):放大鏡呈現(xiàn)內(nèi)容
- 截圖裁剪區(qū)域(cropBox):放大鏡的內(nèi)容主要根據(jù)此區(qū)域的內(nèi)容呈現(xiàn)
提示:
可能大家會有疑問,為什么不把放大鏡的內(nèi)容作為
magnifier
的背景圖呈現(xiàn),而是單獨使用scaleImg
來顯示呢?這是因為在實現(xiàn)的過程中,使用背景圖呈現(xiàn)在更新放大鏡內(nèi)容時會比較卡,其次作為背景圖會使得內(nèi)容變得模糊,當(dāng)然如果大家有什么更好的方案,也可以在評論區(qū)留言。關(guān)于
cropBox
,它的作用主要是用于收集屏幕內(nèi)容作為放大鏡呈現(xiàn)內(nèi)容。上述必要元素中,除了 觸發(fā)按鈕 之外的其他元素,都是在掛載放大鏡時基于 DOM API 動態(tài)創(chuàng)建的。
這些元素的層級關(guān)系如下:
div.triggerButton div.container - canvas.screenshots - div.magnifier - div.dragBox - img.scaleImg - div.cropBox
相關(guān)初始代碼如下:
magnifier.js
class Magnifier { // -- 構(gòu)造函數(shù) constructor() {} // -- 掛載 mount = () => {}; // -- 銷毀 destory = () => {}; }
magnifier.css
/* 外層容器·遮罩層 */ .magnifier { width: 100%; height: 100%; position: fixed; top: 0; left: 0; z-index: 10000; } /* 屏幕截圖 */ .magnifier__screenshots { width: 100%; height: 100%; position: absolute; top: 0; left: 0; /* 作為放大鏡源數(shù)據(jù),無需呈現(xiàn)給用戶 */ opacity: 0; z-index: -1; } /* 裁剪區(qū)域 */ .magnifier__cropBox { width: 100px; height: 100px; box-sizing: border-box; border: 1px dashed red; /** 由于裁剪區(qū)域在拖拽區(qū)域之上,為了不影響拖拽事件,需設(shè)置此屬性用于事件穿透 */ pointer-events: none; position: absolute; top: 0; left: 0; } /* 放大鏡 & 縮放區(qū)域 */ .magnifier__magnifier { box-sizing: border-box; border: 2px dashed #7b68ee; /* 拖拽區(qū)域在此元素內(nèi)部,這里是讓拖拽元素在內(nèi)部居中 */ display: flex; justify-content: center; align-items: center; cursor: crosshair; position: absolute; top: 0; left: 0; } /* 拖拽元素 */ .magnifier__dragBox { box-sizing: border-box; border: 1px dashed green; cursor: move; } /* 放大鏡呈現(xiàn)內(nèi)容 */ .magnifier__scaleImg { pointer-events: none; width: 100%; height: 100%; position: absolute; top: 0; left: 0; z-index: -1; }
index.html
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>網(wǎng)頁放大鏡</title> <link rel="stylesheet" href="./libs/magnifier.css" rel="external nofollow" /> <link rel="stylesheet" href="./index.css" rel="external nofollow" /> </head> <body> <!-- 頁面內(nèi)容(根據(jù)需要自行設(shè)置) --> <script src="./libs/magnifier.js"></script> <script src="./index.js"></script> </body> </html>
提示:沒體現(xiàn)的代碼可以先不管,后續(xù)會慢慢完善。
觸發(fā)按鈕
index.html
首先在頁面中添加懸浮按鈕標(biāo)簽:
<!-- 放大鏡 --> <div class="triggerButton">放大鏡</div>
index.css
接著在全局樣式文件中設(shè)置按鈕樣式:
.triggerButton { cursor: pointer; background: #4169E1; color: #fff; padding: 8px 12px; position: fixed; right: 24px; top: 50%; z-index: 1; }
index.js
最后在腳本文件中設(shè)置按鈕點擊事件,用于控制放大鏡的掛載和卸載:
// -- 觸發(fā)按鈕 const triggerButton = document.querySelector(".triggerButton"); // -- 創(chuàng)建放大鏡實例 const magnifier = new Magnifier(); // -- 標(biāo)識放大鏡激活狀態(tài) let isActive = false; triggerButton.onclick = function () { // 切換狀態(tài) isActive = !isActive; if (isActive) { // 掛載放大鏡 magnifier.mount(); } else { // 移除放大鏡 magnifier.destroy(); } };
接下來,我們的關(guān)注點將放在 Magnifier
類的實現(xiàn)上。
掛載 / 卸載
首先在構(gòu)造函數(shù)中,定義實現(xiàn)放大鏡所需的屬性:
constructor() { // -- 類名前綴 this.prefixCls = "magnifier"; // -- 放大鏡初始尺寸 this.initialSize = { width: 200, height: 200 }; // -- 放大鏡最小尺寸 this.minSize = { width: 100, height: 100 }; // -- 放大鏡最大尺寸 this.maxSize = { width: 500, height: 500 }; // -- 四周觸發(fā)拖拽縮放的間距 this.resizeSpacing = 20; // -- 縮放比例 this.scaleRatio = 2; // -- 標(biāo)識當(dāng)前是否激活縮放狀態(tài) this.isResizing = false; // -- 標(biāo)識是否從放大鏡左側(cè)/上放激活縮放狀態(tài) this.isResizeTopLeft = false; // -- 標(biāo)識當(dāng)前是否激活拖拽狀態(tài) this.isDragging = false; // -- 記錄拖拽時的坐標(biāo) this.originalPoint = { x: 0, y: 0 }; // -- 記錄拖拽的尺寸 this.originalSize = { width: 0, height: 0 }; // -- 標(biāo)識原始偏移位置 this.originalOffset = { x: 0, y: 0 }; // -- 容器元素 this.container = null; // -- 屏幕截圖 this.screenshots = null; // -- 放大鏡 this.magnifier = null; // -- 拖拽區(qū)域 this.dragBox = null; // -- 放大鏡呈現(xiàn)內(nèi)容 this.scaleImg = null; // -- 截圖裁剪區(qū)域 this.cropBox = null; }
掛載函數(shù):
mount = () => { // -- 移除容器(避免重復(fù)調(diào)用掛載函數(shù)) this.container && this.destroy(); // -- 禁止頁面滾動 document.body.style.overflow = "hidden"; // -- 創(chuàng)建必要元素 this._createElement(); // -- 計算放大鏡初始位置(屏幕正中間) this._calcMagnifierPosition(); // -- 計算裁剪區(qū)域初始位置(屏幕正中間) this._calcCropBoxPosition(); // -- 獲取屏幕截圖 this._getScreenshots(); // -- 綁定事件(觸發(fā)開始拖拽/縮放) // ... };
提示:綁定事件部分的代碼后續(xù)再填充。
解析 _createElement()
:
_createElement = () => { // 1. 創(chuàng)建外層容器(遮罩層) const container = document.createElement("div"); container.setAttribute("data-html2canvas-ignore", "true"); container.classList.add(this.prefixCls); this.container = container; // 2. 創(chuàng)建裁剪元素 const cropBox = document.createElement("div"); cropBox.style.width = this.initialSize.width / this.scaleRatio + "px"; cropBox.style.height = this.initialSize.height / this.scaleRatio + "px"; cropBox.classList.add(this.prefixCls + "__cropBox"); this.cropBox = cropBox; // 3. 創(chuàng)建放大鏡元素 & 縮放區(qū)域元素 const magnifier = document.createElement("div"); magnifier.style.width = this.initialSize.width + "px"; magnifier.style.height = this.initialSize.height + "px"; magnifier.classList.add(this.prefixCls + "__magnifier"); this.magnifier = magnifier; // 4. 創(chuàng)建拖拽區(qū)域元素 const dragBox = document.createElement("div"); dragBox.classList.add(this.prefixCls + "__dragBox"); dragBox.style.width = "calc(100% - " + this.resizeSpacing * 2 + "px)"; dragBox.style.height = "calc(100% - " + this.resizeSpacing * 2 + "px)"; this.dragBox = dragBox; // 5. 創(chuàng)建放大圖片 const scaleImg = document.createElement("img"); scaleImg.classList.add(this.prefixCls + "__scaleImg"); this.scaleImg = scaleImg; // 6. 掛載元素 magnifier.appendChild(scaleImg); magnifier.appendChild(dragBox); container.appendChild(magnifier); container.appendChild(cropBox); document.body.appendChild(container); };
提示:
container
容器新增data-html2canvas-ignore
屬性是為了防止html2canvas
截取屏幕時把放大鏡的內(nèi)容給截取進去了。
cropBox
裁剪區(qū)域元素的尺寸根據(jù)設(shè)置的放大鏡比例縮小。
dragBox
拖拽區(qū)域元素的尺寸根據(jù)magnifier
和 設(shè)置的resizeSpacing
屬性動態(tài)計算。
解析 _calcMagnifierPosition
:計算放大鏡初始位置(屏幕正中間)
_calcMagnifierPosition = () => { if (!this.magnifier) return; const rect = this.magnifier.getBoundingClientRect(); const x = (window.innerWidth - rect.width) / 2; const y = (window.innerHeight - rect.height) / 2; this.magnifier.style.left = x + "px"; this.magnifier.style.top = y + "px"; };
解析 _calcCropBoxPosition()
:計算裁剪區(qū)域初始位置(屏幕正中間)
_calcCropBoxPosition = () => { if (!this.cropBox) return; const rect = this.cropBox.getBoundingClientRect(); const x = (window.innerWidth - rect.width) / 2; const y = (window.innerHeight - rect.height) / 2; this.cropBox.style.left = x + "px"; this.cropBox.style.top = y + "px"; };
解析 _getScreenshots()
:獲取屏幕截圖
_getScreenshots = () => { // -- 基于 html2canvas 截取屏幕 html2canvas(document.body, { // 是否允許跨源圖像污染畫布 allowTaint: true, // 背景顏色 backgroundColor: "#FFF", // 加載圖片的超時時間 imageTimeout: 60 * 1000, // 渲染比例,默認為瀏覽器設(shè)備像素比 scale: this.scaleRatio, // 是否嘗試使用 CORS 從服務(wù)器加載圖像 useCORS: true, // 裁剪畫布 x 坐標(biāo) x: document.documentElement.scrollLeft, // 裁剪畫布 y 坐標(biāo) y: document.documentElement.scrollTop, // canvas 的寬度 width: window.innerWidth, // canvas 的高度 height: window.innerHeight, }).then((canvas) => { canvas.classList.add(this.prefixCls + "__screenshots"); this.screenshots = canvas; this.container?.appendChild(this.screenshots); }); };
卸載函數(shù)
destroy = () => { // -- 恢復(fù)視窗 document.body.style.overflow = "auto"; // -- 移除事件 // ... // -- 移除容器 this.container?.remove(); // -- 恢復(fù)初始值 // -- 縮放相關(guān) this.isResizing = false; this.isResizeTopLeft = false; this.originalPoint = { x: 0, y: 0 }; this.originalSize = { width: 0, height: 0 }; // -- 拖拽相關(guān) this.isDragging = false; this.originalOffset = { x: 0, y: 0 }; // -- 置空元素 this.container = null; this.magnifier = null; this.dragBox = null; this.cropBox = null; this.scaleImg = null; this.screenshots = null; };
提示:移除事件部分的代碼后續(xù)再填充。
拖拽移動放大鏡
拖拽移動放大鏡主要通過監(jiān)聽 mousedown
mousemove
mouseup
mouseleave
事件實現(xiàn),可拖拽區(qū)域為當(dāng)前瀏覽器的可視區(qū)域,也就是遮罩層區(qū)域。
提示:代碼中寫有詳細注釋,這里不再敘述實現(xiàn)細節(jié)。
開始拖拽
- 激活拖拽狀態(tài)
isDragging
- 監(jiān)聽
mousemove
事件,觸發(fā)【拖拽中】事件函數(shù) - 監(jiān)聽
mouseup
mouseleave
事件,【拖拽結(jié)束】事件函數(shù)
_onDragStart = (event) => { // -- 阻止事件冒泡 event.stopPropagation(); // -- 異常處理 if (!this.container) return; // -- 激活拖拽狀態(tài) this.isDragging = true; // -- 監(jiān)聽鼠標(biāo)事件 this.container.addEventListener("mousemove", this._onDragging); this.container.addEventListener("mouseup", this._onDragEnd); this.container.addEventListener("mouseleave", this._onDragEnd); };
拖拽中
- 更新放大鏡的位置
- 更新裁剪區(qū)域的位置
_onDragging = (event) => { // -- 阻止事件冒泡 event.stopPropagation(); // -- 異常處理 if (!this.magnifier || !this.cropBox || !this.screenshots || !this.scaleImg) return; // -- 如果沒有激活拖拽狀態(tài),不做任何處理 if (!this.isDragging) return; // -- (放大鏡)獲取當(dāng)前移動位置 const { width: magnifierW, height: magnifierH } = this.magnifier.getBoundingClientRect(); let magnifierOffsetX = event.clientX - magnifierW / 2; let magnifierOffsetY = event.clientY - magnifierH / 2; // -- (放大鏡)獲取可移動的最大位置 const magnifierMaxOffsetX = window.innerWidth - magnifierW; const magnifierMaxOffsetY = window.innerHeight - magnifierH; // -- (放大鏡)處理邊界(水平/垂直) if (magnifierOffsetX < 0) { magnifierOffsetX = 0; } else if (magnifierOffsetX > magnifierMaxOffsetX) { magnifierOffsetX = magnifierMaxOffsetX; } if (magnifierOffsetY < 0) { magnifierOffsetY = 0; } else if (magnifierOffsetY > magnifierMaxOffsetY) { magnifierOffsetY = magnifierMaxOffsetY; } // -- (放大鏡)更新放大鏡的位置 this.magnifier.style.left = magnifierOffsetX + "px"; this.magnifier.style.top = magnifierOffsetY + "px"; // -- (裁剪區(qū)域)獲取當(dāng)前移動位置 const { width: cropBoxW, height: cropBoxH } = this.cropBox.getBoundingClientRect(); let cropBoxOffsetX = event.clientX - cropBoxW / 2; let cropBoxOffsetY = event.clientY - cropBoxH / 2; // -- (裁剪區(qū)域)獲取可移動的最大位置 const cropBoxMaxOffsetX = window.innerWidth - cropBoxW; const cropBoxMaxOffsetY = window.innerHeight - cropBoxH; // -- (裁剪區(qū)域)處理邊界(水平/垂直) if (cropBoxOffsetX < 0) { cropBoxOffsetX = 0; } else if (cropBoxOffsetX > cropBoxMaxOffsetX) { cropBoxOffsetX = cropBoxMaxOffsetX; } if (cropBoxOffsetY < 0) { cropBoxOffsetY = 0; } else if (cropBoxOffsetY > cropBoxMaxOffsetY) { cropBoxOffsetY = cropBoxMaxOffsetY; } // -- (裁剪區(qū)域) this.cropBox.style.left = cropBoxOffsetX + "px"; this.cropBox.style.top = cropBoxOffsetY + "px"; };
拖拽結(jié)束
- 更新拖拽狀態(tài)
- 移除
mousemove
mouseup
mouseleave
事件
_onDragEnd = (event) => { event.stopPropagation(); this.isDragging = false; this.container.removeEventListener("mousemove", this._onDragging); this.container.removeEventListener("mouseup", this._onDragEnd); this.container.removeEventListener("mouseleave", this._onDragEnd); };
在掛載時,監(jiān)聽 mousedown
事件,觸發(fā)開始拖拽
mount = () => { this.dragBox.addEventListener("mousedown", this._onDragStart); };
在掛載時,移除 mousedown
事件
destroy = () => { this.dragBox.removeEventListener("mousedown", this._onDragStart); };
提示:此時您可以查看頁面效果,嘗試拖拽放大鏡,觀察放大鏡內(nèi)層容器、拖拽區(qū)域以及裁剪區(qū)域的位置變化。
拖拽縮放放大鏡
拖拽縮放放大鏡同樣也是通過監(jiān)聽 mousedown
mousemove
mouseup
mouseleave
事件實現(xiàn),縮放需設(shè)置邊界值,也就是我們之前定義 定義的放大鏡最大尺寸(maxSize
)和最小尺寸(minSize
)。
開始縮放
- 激活縮放狀態(tài)
isResizing
- 記錄鼠標(biāo)按下時的位置,用于在拖拽過程中計算拖拽的距離
- 記錄放大鏡的原始尺寸,用于在拖拽過程中計算放大鏡的目標(biāo)尺寸
- 記錄鼠標(biāo)按下時放大鏡距離屏幕左上角的位置,用于在向上/向左拖拽時更新放大鏡的位置
- 判斷是否觸發(fā)向上/向左縮放(因為視圖相對于屏幕左上角布局,向左或向上縮放時,除了更新尺寸外,還需動態(tài)調(diào)整放大鏡的位置)
- 監(jiān)聽
mousemove
事件,觸發(fā)【縮放中】事件函數(shù) - 監(jiān)聽
mouseup
mouseleave
事件,【縮放結(jié)束】事件函數(shù)
_onResizeStart = (event) => { if (!this.magnifier) return; // -- 阻止事件冒泡 event.stopPropagation(); // -- 激活縮放 this.isResizing = true; // -- 記錄鼠標(biāo)按下時的位置,用于在拖拽過程中計算拖拽的距離 this.originalPoint = { x: event.clientX, y: event.clientY }; // -- 記錄放大鏡的原始尺寸,用于在拖拽過程中計算放大鏡的目標(biāo)尺寸 const rect = this.magnifier.getBoundingClientRect(); this.originalSize = { width: rect.width, height: rect.height }; // 記錄鼠標(biāo)按下時放大鏡距離屏幕左上角的位置,用于在向上/向左拖拽時更新放大鏡的位置 this.originalOffset = { x: event.clientX - rect.left, y: event.clientY - rect.top, }; // 判斷是否觸發(fā)向上/向左縮放(因為視圖相對于屏幕左上角布局,向左或向上縮放時,除了更新尺寸外,還需動態(tài)調(diào)整放大鏡的位置) if ( this.originalOffset.x <= this.resizeSpacing || this.originalOffset.y <= this.resizeSpacing ) { this.isResizeTopLeft = true; } else { this.isResizeTopLeft = false; } this.container?.addEventListener("mousemove", this._onResizing); this.container?.addEventListener("mouseup", this._onReszieEnd); this.container?.addEventListener("mouseleave", this._onReszieEnd); }
縮放中
- 獲取鼠標(biāo)相對于原點拖拽的距離
- 計算目標(biāo)尺寸
- 邊界值處理(判斷當(dāng)前放大鏡是否縮放到最大/小尺寸)
- 如果是從頂部/左側(cè)縮放,則需動態(tài)更新放大鏡在屏幕的位置
- 更新放大鏡尺寸
- 更新裁剪區(qū)域的尺寸和位置
_onResizing = (event) => { if (!this.magnifier || !this.cropBox || !this.screenshots || !this.scaleImg) return; // -- 阻止事件冒泡 event.stopPropagation(); // -- 如果沒有激活縮放狀態(tài),不做任何處理 if (!this.isResizing) return; // -- 獲取鼠標(biāo)相對于原點拖拽的距離 const deltaX = event.clientX - this.originalPoint.x; const deltaY = event.clientY - this.originalPoint.y; // -- 計算目標(biāo)尺寸 let targetWidth = 0; let targetHeight = 0; if (this.isResizeTopLeft) { targetWidth = this.originalSize.width - deltaX; targetHeight = this.originalSize.height - deltaY; } else { targetWidth = this.originalSize.width + deltaX; targetHeight = this.originalSize.height + deltaY; } // -- 邊界值處理(判斷當(dāng)前放大鏡是否縮放到最大/小尺寸) if ( targetWidth < this.minSize.width || targetHeight < this.minSize.height ) { this._onReszieEnd(); return; } if ( targetWidth > this.maxSize.width || targetHeight > this.maxSize.height ) { this._onReszieEnd(); return; } // -- 如果是從頂部/左側(cè)縮放,則需動態(tài)更新放大鏡在屏幕的位置 if (this.isResizeTopLeft) { let x = event.clientX - this.originalOffset.x; let y = event.clientY - this.originalOffset.y; this.magnifier.style.left = x + "px"; this.magnifier.style.top = y + "px"; } // -- 更新放大鏡尺寸 this.magnifier.style.width = targetWidth + "px"; this.magnifier.style.height = targetHeight + "px"; // -- 更新裁剪區(qū)域的尺寸和位置 const cropBoxW = targetWidth / this.scaleRatio; const cropBoxH = targetHeight / this.scaleRatio; this.cropBox.style.width = cropBoxW + "px"; this.cropBox.style.height = cropBoxH + "px"; const { top, left, width, height } = this.magnifier.getBoundingClientRect(); const cropBoxOffsetX = left + (width - cropBoxW) / 2; const cropBoxOffsetY = top + (height - cropBoxH) / 2; this.cropBox.style.left = cropBoxOffsetX + "px"; this.cropBox.style.top = cropBoxOffsetY + "px"; }
縮放結(jié)束
- 更新縮放狀態(tài)
- 移除
mousemove
mouseup
mouseleave
事件
_onReszieEnd = () => { this.isResizing = false; this.container.removeEventListener("mousemove", this._onResizing); this.container.removeEventListener("mouseup", this._onReszieEnd); this.container.removeEventListener("mouseleave", this._onReszieEnd); };
在掛載時,監(jiān)聽 mousedown
事件,觸發(fā)開始縮放
mount = () => { this.magnifier.addEventListener("mousedown", this._onResizeStart); };
在掛載時,移除 mousedown
事件
destroy = () => { this.magnifier.removeEventListener("mousedown", this._onResizeStart); };
提示:此時,放大鏡功能基本上已完成,只差最后一步,及將內(nèi)容呈現(xiàn)在放大鏡上。
呈現(xiàn)放大鏡內(nèi)容
當(dāng)我們在掛載放大鏡時,已經(jīng)基于 html2canvas 存儲屏幕快照作為源數(shù)據(jù),接下來只需要在初始化、拖拽中和縮放中實時更新放大鏡的內(nèi)容即可,為了方便調(diào)用,我們將其抽離成函數(shù),如下所示:
_updateScaleImg = () => { // -- 異常處理 if (!this.cropBox || !this.screenshots || !this.scaleImg) return; // -- 獲取裁剪區(qū)域的盒子信息 const { width: cropBoxW, height: cropBoxH, left: cropBoxOffsetX, top: cropBoxOffsetY, } = this.cropBox.getBoundingClientRect(); // -- 根據(jù)裁剪區(qū)域的盒子信息 + 縮放比例 計算裁剪放大鏡呈現(xiàn)內(nèi)容的尺寸和位置信息 const croppedW = cropBoxW * this.scaleRatio; const croppedH = cropBoxH * this.scaleRatio; const croppedOffsetX = cropBoxOffsetX * this.scaleRatio; const croppedOffsetY = cropBoxOffsetY * this.scaleRatio; // -- 創(chuàng)建 canvas,用于實現(xiàn)裁剪功能 const croppedCanvas = document.createElement("canvas"); croppedCanvas.width = croppedW; croppedCanvas.height = croppedH; const croppedCtx = croppedCanvas.getContext("2d"); if (!croppedCtx) return; croppedCtx.imageSmoothingEnabled = false; // -- 基于屏幕快照(源數(shù)據(jù))裁剪 croppedCtx.drawImage( this.screenshots, croppedOffsetX, croppedOffsetY, croppedW, croppedH, 0, 0, croppedW, croppedH ); // -- 將裁剪后的內(nèi)容轉(zhuǎn)換成鏈接 const url = croppedCanvas.toDataURL("image/jpeg"); // -- 更新放大鏡呈現(xiàn)內(nèi)容 this.scaleImg.src = url; }
① 在獲取屏幕快照時調(diào)用
_getScreenshots = () => { html2canvas(document.body, { ... }).then((canvas) => { ... this._updateScaleImg(); }); };
② 在拖拽放大鏡時調(diào)用
_onDragging = (event) => { ... this._updateScaleImg(); }
③ 在縮放放大鏡時調(diào)用
_onReszieEnd = () => { ... this._updateScaleImg(); }
尾言
到這里,關(guān)于 js 實現(xiàn)網(wǎng)頁放大鏡的效果就介紹完啦!完整代碼請移步至 這里
為了方便各位在生產(chǎn)環(huán)境使用,我封裝了網(wǎng)頁放大鏡的js 庫:@likg/magnifier >>
當(dāng)然,我認為在代碼實現(xiàn)上還是會有很多缺陷和值得優(yōu)化的地方,歡迎各位小伙伴留言評論一起探討。
以上就是基于js + html2canvas實現(xiàn)網(wǎng)頁放大鏡功能的詳細內(nèi)容,更多關(guān)于js html2canvas網(wǎng)頁放大鏡的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
基于JavaScript實現(xiàn)定時跳轉(zhuǎn)到指定頁面
本篇文章給大家介紹基于javascript實現(xiàn)定時跳轉(zhuǎn)到指定頁面的相關(guān)知識,涉及到j(luò)s跳轉(zhuǎn)到指定頁面的相關(guān)內(nèi)容,對js跳轉(zhuǎn)到指定頁面相關(guān)知識感興趣的朋友一起學(xué)習(xí)吧2016-01-01JavaScript?中?this?關(guān)鍵字的作用及改變其上下文的方法
這篇文章主要介紹了JavaScript?中?this?關(guān)鍵字的作用和如何改變其上下文,通過使用?call,?apply,?bind?方法,可以改變函數(shù)中的?this?指向,從而在不同的上下文中使用同一個函數(shù),需要的朋友可以參考下2023-01-01js 將input框中的輸入自動轉(zhuǎn)化成半角大寫(稅號輸入框)
本文主要介紹了稅號輸入框:將input框中的輸入自動轉(zhuǎn)化成半角大寫的方法,具有很好的參考價值,下面跟著小編一起來看下吧2017-02-02ant-design-pro?的EditableProTable表格驗證調(diào)用的實現(xiàn)代碼
這篇文章主要介紹了ant-design-pro?的EditableProTable表格驗證調(diào)用,這里的需求是點擊外部的保存要對整個表單進行驗證,本文通過實例代碼給大家介紹的非常詳細,需要的朋友可以參考下2022-06-06Bootstrap基本組件學(xué)習(xí)筆記之進度條(15)
這篇文章主要為大家詳細介紹了Bootstrap基本組件學(xué)習(xí)筆記之進度條,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-12-12