亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

基于js?+?html2canvas實(shí)現(xiàn)網(wǎng)頁(yè)放大鏡功能

 更新時(shí)間:2023年12月19日 09:13:56   作者:李鴻耀  
最近接到任務(wù),需實(shí)現(xiàn)【網(wǎng)頁(yè)】放大鏡的效果,百度搜索?【js?放大鏡】關(guān)鍵字,千篇一律的都是一些仿淘寶/京東等電商網(wǎng)站中查看規(guī)格大圖的效果實(shí)現(xiàn),根本無(wú)法滿(mǎn)足我的需求,于是自己花了點(diǎn)時(shí)間調(diào)研實(shí)現(xiàn),在這里分享給大家,感興趣的朋友可以參考下

需求描述

開(kāi)始之前,我們還是簡(jiǎn)單梳理一下需求:

  • 屏幕右下角放置操作按鈕,用于啟用或禁用放大鏡。
  • 啟用放大鏡時(shí):
    • 頁(yè)面禁止交互(如滾動(dòng))
    • 放大鏡默認(rèn)展示在屏幕正中間
    • 放大鏡支持調(diào)整尺寸(四個(gè)方向,即上、右、下、左)
    • 放大鏡支持拖拽以展示不同的內(nèi)容
    • 放大鏡的內(nèi)容直接呈現(xiàn)在放大鏡元素中

需求看似簡(jiǎn)單,但實(shí)際上需要注意的點(diǎn)特別多,這里首先拋出幾個(gè)問(wèn)題供大家思考:

  • 啟用放大鏡時(shí),如何截取屏幕當(dāng)前可視區(qū)域的內(nèi)容,并將其作為放大鏡的圖片源數(shù)據(jù)?
  • 由于網(wǎng)頁(yè)是相對(duì)于屏幕左上角開(kāi)始布局的,放大鏡可以很好的處理向右和向下的尺寸調(diào)整,那如果是向左或者向上呢?
  • 拖拽放大鏡時(shí),如何處理邊界?
  • ....

思路

  • 為了便于復(fù)用,將放大鏡邏輯封裝成類(lèi),樣式單獨(dú)抽離,后續(xù)使用時(shí)直接引入相應(yīng)的腳本和樣式即可。

  • 在屏幕放置懸浮按鈕,用于激活放大鏡(做成開(kāi)關(guān)效果),當(dāng)用戶(hù)點(diǎn)擊懸浮按鈕,掛載放大鏡,再次點(diǎn)擊,卸載放大鏡。

    掛載放大鏡時(shí):

    • 禁止?jié)L動(dòng):可設(shè)置 body 標(biāo)簽的 overflow 屬性為 hidden。
    • 禁止交互:顯示透明遮罩層。
    • 截取屏幕:通過(guò) html2canvas 截取當(dāng)前可視區(qū)域的內(nèi)容作為放大鏡的源數(shù)據(jù)。
    • 創(chuàng)建元素:基于瀏覽器的 DOM API 創(chuàng)建放大鏡相關(guān)的必要元素(后文講解)。

    卸載放大鏡時(shí):

    • 設(shè)置 body 標(biāo)簽的 overflow 屬性為 auto,使其支持滾動(dòng)。
    • 移除遮罩
    • 移除事件監(jiān)聽(tīng)
  • 處理放大鏡的拖拽和縮放,可基于 mousedown mousemove mouseup 事件,計(jì)算尺寸和坐標(biāo)實(shí)現(xiàn)。

更多細(xì)節(jié),請(qǐng)參考具體實(shí)現(xiàn)。

實(shí)現(xiàn)

提示:

本文主要講解網(wǎng)頁(yè)放大鏡的實(shí)現(xiàn),頁(yè)面內(nèi)容及全局樣式不會(huì)在本文中講解,各位看官可根據(jù)實(shí)際需求布局你的頁(yè)面。

為了更好的理解,本文主要使用原生 HTML CSS JavaScript 實(shí)現(xiàn)。

準(zhǔn)備工作

首先請(qǐng)按如下目錄解構(gòu)創(chuàng)建項(xiàng)目:

.
├── libs 
│   ├── magnifier.css # 放大鏡相關(guān)樣式     
│   └── magnifier.js  # 放大鏡核心邏輯      
├── index.css  # 全局樣式
├── index.html # 布局
└── index.js   # 腳本

布局元素

首先分析實(shí)現(xiàn)放大鏡功能所需的必要元素:

  • 觸發(fā)按鈕(triggerButton):懸浮(固定定位)在屏幕右側(cè),控制放大鏡的掛載和卸載。
  • 放大鏡容器·外層(container):遮罩,懸浮鋪滿(mǎn)屏幕可視區(qū)域,結(jié)合頁(yè)面 overflow 屬性可禁止用戶(hù)交互。
  • 屏幕截圖(screenshots):源數(shù)據(jù),使用 canvas 存儲(chǔ),便于裁剪,懸浮鋪滿(mǎn)屏幕可視區(qū)域,設(shè)置 opacity0。
  • 放大鏡容器·內(nèi)層 / 觸發(fā)縮放區(qū)域(magnifier):控制放大鏡容器大小和位置,同時(shí)用戶(hù)鼠標(biāo)在此區(qū)域按下,激活縮放狀態(tài)。
  • 觸發(fā)拖拽區(qū)域(dragBox):當(dāng)用戶(hù)鼠標(biāo)在此區(qū)域按下,激活拖拽狀態(tài)。
  • 放大鏡內(nèi)容(scaleImg):放大鏡呈現(xiàn)內(nèi)容
  • 截圖裁剪區(qū)域(cropBox):放大鏡的內(nèi)容主要根據(jù)此區(qū)域的內(nèi)容呈現(xiàn)

提示:

可能大家會(huì)有疑問(wèn),為什么不把放大鏡的內(nèi)容作為 magnifier 的背景圖呈現(xiàn),而是單獨(dú)使用 scaleImg 來(lái)顯示呢?這是因?yàn)樵趯?shí)現(xiàn)的過(guò)程中,使用背景圖呈現(xiàn)在更新放大鏡內(nèi)容時(shí)會(huì)比較卡,其次作為背景圖會(huì)使得內(nèi)容變得模糊,當(dāng)然如果大家有什么更好的方案,也可以在評(píng)論區(qū)留言。

關(guān)于 cropBox,它的作用主要是用于收集屏幕內(nèi)容作為放大鏡呈現(xiàn)內(nèi)容。

上述必要元素中,除了 觸發(fā)按鈕 之外的其他元素,都是在掛載放大鏡時(shí)基于 DOM API 動(dòng)態(tài)創(chuàng)建的。

這些元素的層級(jí)關(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 = () => {};
  // -- 銷(xiāo)毀
  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ù),無(wú)需呈現(xiàn)給用戶(hù) */
  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)頁(yè)放大鏡</title>
    <link rel="stylesheet" href="./libs/magnifier.css" rel="external nofollow"  />
    <link rel="stylesheet" href="./index.css" rel="external nofollow"  />
  </head>
  <body>
    <!-- 頁(yè)面內(nèi)容(根據(jù)需要自行設(shè)置) -->
    <script src="./libs/magnifier.js"></script>
    <script src="./index.js"></script>
  </body>
</html>

提示:沒(méi)體現(xiàn)的代碼可以先不管,后續(xù)會(huì)慢慢完善。

觸發(fā)按鈕

index.html

首先在頁(yè)面中添加懸浮按鈕標(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è)置按鈕點(diǎn)擊事件,用于控制放大鏡的掛載和卸載:

// -- 觸發(fā)按鈕
const triggerButton = document.querySelector(".triggerButton");
// -- 創(chuàng)建放大鏡實(shí)例
const magnifier = new Magnifier();
// -- 標(biāo)識(shí)放大鏡激活狀態(tài)
let isActive = false;
triggerButton.onclick = function () {
  // 切換狀態(tài)
  isActive = !isActive;
  if (isActive) {
    // 掛載放大鏡
    magnifier.mount();
  } else {
    // 移除放大鏡
    magnifier.destroy();
  }
};

接下來(lái),我們的關(guān)注點(diǎn)將放在 Magnifier 類(lèi)的實(shí)現(xiàn)上。

掛載 / 卸載

首先在構(gòu)造函數(shù)中,定義實(shí)現(xiàn)放大鏡所需的屬性:

constructor() {
  // -- 類(lèi)名前綴
  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)識(shí)當(dāng)前是否激活縮放狀態(tài)
  this.isResizing = false;
  // -- 標(biāo)識(shí)是否從放大鏡左側(cè)/上放激活縮放狀態(tài)
  this.isResizeTopLeft = false;
  // -- 標(biāo)識(shí)當(dāng)前是否激活拖拽狀態(tài)
  this.isDragging = false;
  // -- 記錄拖拽時(shí)的坐標(biāo)
  this.originalPoint = { x: 0, y: 0 };
  // -- 記錄拖拽的尺寸
  this.originalSize = { width: 0, height: 0 };
  // -- 標(biāo)識(shí)原始偏移位置
  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();
  // -- 禁止頁(yè)面滾動(dòng)
  document.body.style.overflow = "hidden";
  // -- 創(chuàng)建必要元素
  this._createElement();
  // -- 計(jì)算放大鏡初始位置(屏幕正中間)
  this._calcMagnifierPosition();
  // -- 計(jì)算裁剪區(qū)域初始位置(屏幕正中間)
  this._calcCropBoxPosition();
  // -- 獲取屏幕截圖
  this._getScreenshots();
  // -- 綁定事件(觸發(fā)開(kāi)始拖拽/縮放)
  // ...
};

提示:綁定事件部分的代碼后續(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 截取屏幕時(shí)把放大鏡的內(nèi)容給截取進(jìn)去了。

cropBox 裁剪區(qū)域元素的尺寸根據(jù)設(shè)置的放大鏡比例縮小。

dragBox 拖拽區(qū)域元素的尺寸根據(jù) magnifier 和 設(shè)置的 resizeSpacing 屬性動(dòng)態(tài)計(jì)算。

解析 _calcMagnifierPosition:計(jì)算放大鏡初始位置(屏幕正中間)

_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():計(jì)算裁剪區(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, {
    // 是否允許跨源圖像污染畫(huà)布
    allowTaint: true,
    // 背景顏色
    backgroundColor: "#FFF",
    // 加載圖片的超時(shí)時(shí)間
    imageTimeout: 60 * 1000,
    // 渲染比例,默認(rèn)為瀏覽器設(shè)備像素比
    scale: this.scaleRatio,
    // 是否嘗試使用 CORS 從服務(wù)器加載圖像
    useCORS: true,
    // 裁剪畫(huà)布 x 坐標(biāo)
    x: document.documentElement.scrollLeft,
    // 裁剪畫(huà)布 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ù)再填充。

拖拽移動(dòng)放大鏡

拖拽移動(dòng)放大鏡主要通過(guò)監(jiān)聽(tīng) mousedown mousemove mouseup mouseleave 事件實(shí)現(xiàn),可拖拽區(qū)域?yàn)楫?dāng)前瀏覽器的可視區(qū)域,也就是遮罩層區(qū)域。

提示:代碼中寫(xiě)有詳細(xì)注釋?zhuān)@里不再敘述實(shí)現(xiàn)細(xì)節(jié)。

開(kāi)始拖拽

  • 激活拖拽狀態(tài) isDragging
  • 監(jiān)聽(tīng) mousemove 事件,觸發(fā)【拖拽中】事件函數(shù)
  • 監(jiān)聽(tīng) mouseup mouseleave 事件,【拖拽結(jié)束】事件函數(shù)
_onDragStart = (event) => {
  // -- 阻止事件冒泡
  event.stopPropagation();
  // -- 異常處理
  if (!this.container) return;
  // -- 激活拖拽狀態(tài)
  this.isDragging = true;
  // -- 監(jiān)聽(tīng)鼠標(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;
  // -- 如果沒(méi)有激活拖拽狀態(tài),不做任何處理
  if (!this.isDragging) return;
  // -- (放大鏡)獲取當(dāng)前移動(dòng)位置
  const { width: magnifierW, height: magnifierH } =
    this.magnifier.getBoundingClientRect();
  let magnifierOffsetX = event.clientX - magnifierW / 2;
  let magnifierOffsetY = event.clientY - magnifierH / 2;
  // -- (放大鏡)獲取可移動(dòng)的最大位置
  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)前移動(dòng)位置
  const { width: cropBoxW, height: cropBoxH } =
    this.cropBox.getBoundingClientRect();
  let cropBoxOffsetX = event.clientX - cropBoxW / 2;
  let cropBoxOffsetY = event.clientY - cropBoxH / 2;
  // -- (裁剪區(qū)域)獲取可移動(dòng)的最大位置
  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);
};

在掛載時(shí),監(jiān)聽(tīng) mousedown 事件,觸發(fā)開(kāi)始拖拽

mount = () => {
  this.dragBox.addEventListener("mousedown", this._onDragStart);
};

在掛載時(shí),移除 mousedown 事件

destroy = () => {
  this.dragBox.removeEventListener("mousedown", this._onDragStart);
};

提示:此時(shí)您可以查看頁(yè)面效果,嘗試拖拽放大鏡,觀察放大鏡內(nèi)層容器、拖拽區(qū)域以及裁剪區(qū)域的位置變化。

拖拽縮放放大鏡

拖拽縮放放大鏡同樣也是通過(guò)監(jiān)聽(tīng) mousedown mousemove mouseup mouseleave 事件實(shí)現(xiàn),縮放需設(shè)置邊界值,也就是我們之前定義 定義的放大鏡最大尺寸(maxSize)和最小尺寸(minSize)。

開(kāi)始縮放

  • 激活縮放狀態(tài) isResizing
  • 記錄鼠標(biāo)按下時(shí)的位置,用于在拖拽過(guò)程中計(jì)算拖拽的距離
  • 記錄放大鏡的原始尺寸,用于在拖拽過(guò)程中計(jì)算放大鏡的目標(biāo)尺寸
  • 記錄鼠標(biāo)按下時(shí)放大鏡距離屏幕左上角的位置,用于在向上/向左拖拽時(shí)更新放大鏡的位置
  • 判斷是否觸發(fā)向上/向左縮放(因?yàn)橐晥D相對(duì)于屏幕左上角布局,向左或向上縮放時(shí),除了更新尺寸外,還需動(dòng)態(tài)調(diào)整放大鏡的位置)
  • 監(jiān)聽(tīng) mousemove 事件,觸發(fā)【縮放中】事件函數(shù)
  • 監(jiān)聽(tīng) mouseup mouseleave 事件,【縮放結(jié)束】事件函數(shù)
_onResizeStart = (event) => {
  if (!this.magnifier) return;
  // -- 阻止事件冒泡
  event.stopPropagation();
  // -- 激活縮放
  this.isResizing = true;
  // -- 記錄鼠標(biāo)按下時(shí)的位置,用于在拖拽過(guò)程中計(jì)算拖拽的距離
  this.originalPoint = { x: event.clientX, y: event.clientY };
  // -- 記錄放大鏡的原始尺寸,用于在拖拽過(guò)程中計(jì)算放大鏡的目標(biāo)尺寸
  const rect = this.magnifier.getBoundingClientRect();
  this.originalSize = { width: rect.width, height: rect.height };

  // 記錄鼠標(biāo)按下時(shí)放大鏡距離屏幕左上角的位置,用于在向上/向左拖拽時(shí)更新放大鏡的位置
  this.originalOffset = {
    x: event.clientX - rect.left,
    y: event.clientY - rect.top,
  };

  // 判斷是否觸發(fā)向上/向左縮放(因?yàn)橐晥D相對(duì)于屏幕左上角布局,向左或向上縮放時(shí),除了更新尺寸外,還需動(dòng)態(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)相對(duì)于原點(diǎn)拖拽的距離
  • 計(jì)算目標(biāo)尺寸
  • 邊界值處理(判斷當(dāng)前放大鏡是否縮放到最大/小尺寸)
  • 如果是從頂部/左側(cè)縮放,則需動(dòng)態(tài)更新放大鏡在屏幕的位置
  • 更新放大鏡尺寸
  • 更新裁剪區(qū)域的尺寸和位置
_onResizing = (event) => {
  if (!this.magnifier || !this.cropBox || !this.screenshots || !this.scaleImg)
    return;
  // -- 阻止事件冒泡
  event.stopPropagation();
  // -- 如果沒(méi)有激活縮放狀態(tài),不做任何處理
  if (!this.isResizing) return;
  // -- 獲取鼠標(biāo)相對(duì)于原點(diǎn)拖拽的距離
  const deltaX = event.clientX - this.originalPoint.x;
  const deltaY = event.clientY - this.originalPoint.y;
  // -- 計(jì)算目標(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è)縮放,則需動(dòng)態(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);
};

在掛載時(shí),監(jiān)聽(tīng) mousedown 事件,觸發(fā)開(kāi)始縮放

mount = () => {
  this.magnifier.addEventListener("mousedown", this._onResizeStart);
};

在掛載時(shí),移除 mousedown 事件

destroy = () => {
  this.magnifier.removeEventListener("mousedown", this._onResizeStart);
};

提示:此時(shí),放大鏡功能基本上已完成,只差最后一步,及將內(nèi)容呈現(xiàn)在放大鏡上。

呈現(xiàn)放大鏡內(nèi)容

當(dāng)我們?cè)趻燧d放大鏡時(shí),已經(jīng)基于 html2canvas 存儲(chǔ)屏幕快照作為源數(shù)據(jù),接下來(lái)只需要在初始化、拖拽中和縮放中實(shí)時(shí)更新放大鏡的內(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ū)域的盒子信息 + 縮放比例 計(jì)算裁剪放大鏡呈現(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,用于實(shí)現(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;
}

① 在獲取屏幕快照時(shí)調(diào)用

_getScreenshots = () => {
  html2canvas(document.body, { ... }).then((canvas) => {
    ...
    this._updateScaleImg();
  });
};

② 在拖拽放大鏡時(shí)調(diào)用

_onDragging = (event) => {
  ...
  this._updateScaleImg();
}

③ 在縮放放大鏡時(shí)調(diào)用

_onReszieEnd = () => {
  ...
  this._updateScaleImg();
}

尾言

到這里,關(guān)于 js 實(shí)現(xiàn)網(wǎng)頁(yè)放大鏡的效果就介紹完啦!完整代碼請(qǐng)移步至 這里

為了方便各位在生產(chǎn)環(huán)境使用,我封裝了網(wǎng)頁(yè)放大鏡的js 庫(kù):@likg/magnifier >>

當(dāng)然,我認(rèn)為在代碼實(shí)現(xiàn)上還是會(huì)有很多缺陷和值得優(yōu)化的地方,歡迎各位小伙伴留言評(píng)論一起探討。

以上就是基于js + html2canvas實(shí)現(xiàn)網(wǎng)頁(yè)放大鏡功能的詳細(xì)內(nèi)容,更多關(guān)于js html2canvas網(wǎng)頁(yè)放大鏡的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Bootstrap Fileinput文件上傳組件用法詳解

    Bootstrap Fileinput文件上傳組件用法詳解

    這篇文章主要介紹了Bootstrap Fileinput文件上傳組件用法詳解的相關(guān)資料
    2016-05-05
  • 基于JavaScript實(shí)現(xiàn)定時(shí)跳轉(zhuǎn)到指定頁(yè)面

    基于JavaScript實(shí)現(xiàn)定時(shí)跳轉(zhuǎn)到指定頁(yè)面

    本篇文章給大家介紹基于javascript實(shí)現(xiàn)定時(shí)跳轉(zhuǎn)到指定頁(yè)面的相關(guān)知識(shí),涉及到j(luò)s跳轉(zhuǎn)到指定頁(yè)面的相關(guān)內(nèi)容,對(duì)js跳轉(zhuǎn)到指定頁(yè)面相關(guān)知識(shí)感興趣的朋友一起學(xué)習(xí)吧
    2016-01-01
  • JavaScript?中?this?關(guān)鍵字的作用及改變其上下文的方法

    JavaScript?中?this?關(guān)鍵字的作用及改變其上下文的方法

    這篇文章主要介紹了JavaScript?中?this?關(guān)鍵字的作用和如何改變其上下文,通過(guò)使用?call,?apply,?bind?方法,可以改變函數(shù)中的?this?指向,從而在不同的上下文中使用同一個(gè)函數(shù),需要的朋友可以參考下
    2023-01-01
  • js 將input框中的輸入自動(dòng)轉(zhuǎn)化成半角大寫(xiě)(稅號(hào)輸入框)

    js 將input框中的輸入自動(dòng)轉(zhuǎn)化成半角大寫(xiě)(稅號(hào)輸入框)

    本文主要介紹了稅號(hào)輸入框:將input框中的輸入自動(dòng)轉(zhuǎn)化成半角大寫(xiě)的方法,具有很好的參考價(jià)值,下面跟著小編一起來(lái)看下吧
    2017-02-02
  • Javascript的this用法

    Javascript的this用法

    本文主要介紹了Javascript的this用法,具有很好的參考價(jià)值,有需要了解的朋友可以看看
    2017-01-01
  • ant-design-pro?的EditableProTable表格驗(yàn)證調(diào)用的實(shí)現(xiàn)代碼

    ant-design-pro?的EditableProTable表格驗(yàn)證調(diào)用的實(shí)現(xiàn)代碼

    這篇文章主要介紹了ant-design-pro?的EditableProTable表格驗(yàn)證調(diào)用,這里的需求是點(diǎn)擊外部的保存要對(duì)整個(gè)表單進(jìn)行驗(yàn)證,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2022-06-06
  • Bootstrap基本組件學(xué)習(xí)筆記之進(jìn)度條(15)

    Bootstrap基本組件學(xué)習(xí)筆記之進(jìn)度條(15)

    這篇文章主要為大家詳細(xì)介紹了Bootstrap基本組件學(xué)習(xí)筆記之進(jìn)度條,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2016-12-12
  • 深度解析JavaScript對(duì)象繼承

    深度解析JavaScript對(duì)象繼承

    JavaScript中的對(duì)象繼承是構(gòu)建靈活、可維護(hù)代碼的關(guān)鍵部分,本文主要介紹了深度解析JavaScript對(duì)象繼承,包括原型鏈繼承、構(gòu)造函數(shù)繼承、組合繼承等,感興趣的可以了解一下
    2024-01-01
  • 深入淺析knockout源碼分析之訂閱

    深入淺析knockout源碼分析之訂閱

    Knockout是一款很優(yōu)秀的JavaScript庫(kù),它可以幫助你僅使用一個(gè)清晰整潔的底層數(shù)據(jù)模型(data model)即可創(chuàng)建一個(gè)富文本且具有良好的顯示和編輯功能的用戶(hù)界面。這篇文章主要介紹了knockout源碼分析之訂閱的相關(guān)資料,需要的朋友可以參考下
    2016-07-07
  • 深入淺析var,let,const的異同點(diǎn)

    深入淺析var,let,const的異同點(diǎn)

    這篇文章主要介紹了var,let,const異同點(diǎn),文中較詳細(xì)的給大家介紹了let和const的相同點(diǎn)和不同點(diǎn),需要的朋友可以參考下
    2018-08-08

最新評(píng)論