這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 思路分析 在開始動手之前,分析一下整個功能的實現過程: 根據圖片大小創建 canvas1 畫布,並將原圖片直接定位在 canvas1 上; 在畫布上添加一個蒙層,以區分當前 canvas 圖像是被裁剪的原圖像; 在蒙層上方,對裁剪區域(鼠 ...
這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助
思路分析
在開始動手之前,分析一下整個功能的實現過程:
-
根據圖片大小創建
canvas1
畫布,並將原圖片直接定位在canvas1
上; -
在畫布上添加一個蒙層,以區分當前
canvas
圖像是被裁剪的原圖像; -
在蒙層上方,對裁剪區域(滑鼠移動形成的矩形範圍)再次進行圖像繪製;
-
獲取裁剪區域的數據,並將該數據定位到另一個
canvas
畫布上。
實現過程
準備工作
首先,編寫所需的 HTML
結構並獲取對應元素。
<body> <!-- 上傳文件 --> <input type="file" id="imageFile" accept="image/*"> <!-- 保存被裁剪的原圖像,初始樣式需要設置 display: none --> <div class="canvasContainer1"> <canvas id="canvas1"></canvas> </div> <!-- 保存裁剪區域的圖像,初始樣式需要設置 display: none --> <div class="canvasContainer2"> <canvas id="canvas2"></canvas> </div> </body> <script> const imageFile = document.querySelector('#imageFile'); const canvasContainer1 = document.querySelector('.canvasContainer1'); const canvasContainer2 = document.querySelector('.canvasContainer2'); const canvas1 = document.querySelector('#canvas1'); const canvas2 = document.querySelector('#canvas2'); const ctx = canvas1.getContext('2d'); const ctx2 = canvas2.getContext('2d'); const imageBox = new Image(); // 創建一個存放圖片的容器 </script>
繪製原圖像
我們需要監聽 input
元素的 change
事件,以獲取上傳圖片的相關參數,這裡主要是為了獲取圖片的寬度和高度。
我們創建一個 FileReader() 對象並監聽其 load
事件。load
事件在讀取操作成功後立刻執行,在這個方法中我們就可以獲取圖片的寬高。
function init() { imageFile.addEventListener('change', handleFileChange, false); // 監聽圖片上傳事件。 } function handleFileChange(e) { const imgFile = e.target.files[0]; // 獲取上傳的圖片對象。 const reader = new FileReader(); reader.onload = function(e) { const imgSrc = e.target.result; // 圖片文件的 base64 編碼格式。 imageBox.src = imgSrc; // 把圖片放入 img 容器。 // 等圖片載入完成後,獲取圖片的寬高。 imageBox.onload = function () { const imgWidth = this.width, imgHeight = this.height; console.log(imgWidth, imgHeight); } } if (imgFile) { reader.readAsDataURL(imgFile); // 讀取圖片文件,讀取完成才能獲取 result 屬性。 } } init();此時還沒有圖片,我們創建一個自適應圖片大小的
canvas1
畫布,並使用 drawImage()
方法將上傳的圖片直接定位到 canvas1
當中。function handleFileChange(e) { const imgFile = e.target.files[0]; // 獲取上傳的圖片對象。 const reader = new FileReader(); reader.onload = function (e) { const imgSrc = e.target.result; // 圖片的 base64 編碼。 imageBox.src = imgSrc; // 把上傳的圖像放入 img 容器。 // 圖片載入完畢後執行 imageBox.onload = function () { // 獲取圖片的寬高。 const imgWidth = this.width, imgHeight = this.height; console.log(imgWidth, imgHeight); // 創建 canvas 畫布並繪製圖片。 generateCanvas(canvasContainer1, canvas1, imgWidth, imgHeight); ctx.drawImage(imageBox, 0, 0, imgWidth, imgHeight); } } if (imgFile) { reader.readAsDataURL(imgFile); // 將當前file讀取成DataURL } } // 根據 width 和 height 創建 canvas 畫布。 function generateCanvas(container, canvas, width, height) { container.width = width + 'px'; container.height = height + 'px'; canvas.width = width; canvas.height = height; container.style.display = 'block'; // 顯示 canvas 區域。 }
可以看到原圖像已經成功被繪製,接下來就可以開始動態繪製截圖區域了。
繪製截圖區域
在這個過程中,我們需要分別監聽 imageBox
容器(原圖像)上的 mousedown
、mousemove
和 mouseup
事件,這些事件的作用如下:
mousedown
事件:記錄開始截圖的位置,並開始監聽mousemove
和mouseup
事件。mousemove
事件:監聽滑鼠的偏移量,以計算裁剪區域的寬度和高度。mouseup
事件:截圖結束,註銷監聽mousedown
和mousemove
事件,並繪製裁剪區域。
let startPosition = []; // 記錄滑鼠點擊(開始截圖)的位置。 let screenshotData = []; // 保存截取部分的相關信息。 function init() { // 監聽滑鼠點擊事件。 canvas1.addEventListener('mousedown', handleMouseDown, false); } // 記錄滑鼠點擊(開始截圖)的位置,並監聽相關事件。 function handleMouseDown(e) { startPosition = [e.offsetX, e.offsetY]; canvas1.addEventListener('mousemove', handleMouseMove, false); canvas1.addEventListener('mouseup', handleMouseUp, false); } // 監聽滑鼠的偏移量,以計算裁剪區域的寬度和高度。 function handleMouseMove(e) { // 獲取裁剪區域的寬度和高度。 const { offsetX, offsetY } = e; const [startX, startY] = startPosition; const [rectWidth, rectHeight] = [offsetX - startX, offsetY - startY]; console.log('rect', rectWidth, rectHeight); // 保存裁剪區域的相關信息。 screenshotData = [startX, startY, rectWidth, rectHeight]; } // 註銷監聽事件等後續操作。 function handleMouseUp() { canvas1.removeEventListener('mousemove', handleMouseMove, false); canvas1.removeEventListener('mouseup', handleMouseUp, false); }
在 handleMouseMove
函數中,我們已經獲取了裁剪區域的寬高,也就是生成截圖的寬高。
接下來,我們需要在原圖像上展示出我們所裁剪的區域,也就是這個效果:
可以看到,原圖像的上方、裁剪區域下方會覆蓋一層半透明黑色蒙層,它的作用是區分原圖層和裁剪部分圖層。所以我們需要在繪製截圖區域之前,添加一層蒙層。
註意,在已有內容的 canvas
畫布上進行再次繪製之前,需要先清除整個畫布的內容。 這裡通過 clearRect()
方法清除 canvas1
畫布上的所有內容,並添加蒙層。
我們繼續來補充 handleMouseMove
和 handleMouseUp
函數中的邏輯:
const MASKER_OPACITY = 0.4; function handleMouseMove(e) { // 獲取裁剪區域的寬度和高度。 const { offsetX, offsetY } = e; const [startX, startY] = startPosition; const [rectWidth, rectHeight] = [offsetX - startX, offsetY - startY]; console.log('rect', rectWidth, rectHeight); // 保存裁剪區域的相關信息。 screenshotData = [startX, startY, rectWidth, rectHeight]; // 再次繪製前,清理 canvas1 畫布上的內容。 const { width, height } = canvas1; ctx.clearRect(0, 0, width, height); // 在 canvas1 畫布上繪製蒙層。 drawImageMasker(0, 0, width, height, MASKER_OPACITY); // 繪製截圖區域。 drawScreenShot(width, height, rectWidth, rectHeight); } // ... // 繪製圖片蒙層,填充範圍和顏色,以便區分原圖層和裁剪部分圖層。 function drawImageMasker(x, y, width, height, opacity) { ctx.fillStyle = `rgba(0, 0, 0, ${opacity})`; ctx.fillRect(0, 0, width, height); } // 繪製裁剪的矩形區域。 function drawScreenShot(canWidth, canHeight, rectWidth, rectHeight) { // 在源圖像外繪製新圖像,只有源圖像外的目標圖像部分會被顯示,源圖像是透明的。 ctx.globalCompositeOperation = 'destination-out'; ctx.fillStyle = '#2c2c2c'; ctx.fillRect(...startPosition, rectWidth, rectHeight); // 在現有畫布上繪製新的圖形。 ctx.globalCompositeOperation = 'destination-over'; ctx.drawImage(imageBox, 0, 0, canWidth, canHeight, 0, 0, canWidth, canHeight); }
然後,當我們放開滑鼠(結束截圖動作)時,除了註銷對 mousedown
和 mousemove
事件的監聽,還需要將所得的裁剪區域的圖像放入另一個 canvas
中。
在繪製新圖像的過程中,我們需要使用以下方法:
- getImageData():讀取
canvas
上的內容,返回一個ImageData
對象,包含了每個像素的信息。 - putImageData():將
ImagaData
對象的數據放入canvas
中,覆蓋canvas
中的已有圖像。
function handleMouseUp() { canvas1.removeEventListener('mousemove', handleMouseMove, false); canvas1.removeEventListener('mouseup', handleMouseUp, false); // 開始繪製截圖區域圖片。 drawScreenshotImage(screenshotData); // 如果裁剪得到新圖像後,不希望保留原圖像,可以設置以下屬性。 // canvasContainer1.style.display = 'none'; } // 在新容器 canvas2 上繪製新圖像。 function drawScreenshotImage(screenshotData) { // 獲取 canvas1 的數據。 const data = ctx.getImageData(...screenshotData); // 創建 canvas2 畫布。 generateCanvas(canvasContainer2, canvas2, screenshotData[2], screenshotData[3]); // 每次繪製前,都先進行清除操作。 ctx2.clearRect(...screenshotData); // 將 canvas1 的數據放入 canvas2 中。 ctx2.putImageData(data, 0, 0); }經過以上步驟,就可以實現我們所需的效果