JS使用canvas實現(xiàn)基本的截圖功能
思路分析
在開始動手之前,分析一下整個功能的實現(xiàn)過程:
根據(jù)圖片大小創(chuàng)建
canvas1
畫布,并將原圖片直接定位在canvas1
上;在畫布上添加一個蒙層,以區(qū)分當(dāng)前
canvas
圖像是被裁剪的原圖像;在蒙層上方,對裁剪區(qū)域(鼠標(biāo)移動形成的矩形范圍)再次進(jìn)行圖像繪制;
獲取裁剪區(qū)域的數(shù)據(jù),并將該數(shù)據(jù)定位到另一個
canvas
畫布上。
實現(xiàn)過程
準(zhǔn)備工作
首先,編寫所需的 HTML
結(jié)構(gòu)并獲取對應(yīng)元素。
<body> <!-- 上傳文件 --> <input type="file" id="imageFile" accept="image/*"> <!-- 保存被裁剪的原圖像,初始樣式需要設(shè)置 display: none --> <div class="canvasContainer1"> <canvas id="canvas1"></canvas> </div> <!-- 保存裁剪區(qū)域的圖像,初始樣式需要設(shè)置 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(); // 創(chuàng)建一個存放圖片的容器 </script>
繪制原圖像
我們需要監(jiān)聽 input
元素的 change
事件,以獲取上傳圖片的相關(guān)參數(shù),這里主要是為了獲取圖片的寬度和高度。
我們創(chuàng)建一個 FileReader() 對象并監(jiān)聽其 load
事件。load
事件在讀取操作成功后立刻執(zhí)行,在這個方法中我們就可以獲取圖片的寬高。
function init() { imageFile.addEventListener('change', handleFileChange, false); // 監(jiān)聽圖片上傳事件。 } 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();
此時還沒有圖片,我們創(chuàng)建一個自適應(yīng)圖片大小的 canvas1
畫布,并使用 drawImage()
方法將上傳的圖片直接定位到 canvas1
當(dāng)中。
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 容器。 // 圖片加載完畢后執(zhí)行 imageBox.onload = function () { // 獲取圖片的寬高。 const imgWidth = this.width, imgHeight = this.height; console.log(imgWidth, imgHeight); // 創(chuàng)建 canvas 畫布并繪制圖片。 generateCanvas(canvasContainer1, canvas1, imgWidth, imgHeight); ctx.drawImage(imageBox, 0, 0, imgWidth, imgHeight); } } if (imgFile) { reader.readAsDataURL(imgFile); // 將當(dāng)前file讀取成DataURL } } // 根據(jù) width 和 height 創(chuàng)建 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 區(qū)域。 }
可以看到原圖像已經(jīng)成功被繪制,接下來就可以開始動態(tài)繪制截圖區(qū)域了。
繪制截圖區(qū)域
在這個過程中,我們需要分別監(jiān)聽 imageBox
容器(原圖像)上的 mousedown
、mousemove
和 mouseup
事件,這些事件的作用如下:
mousedown
事件:記錄開始截圖的位置,并開始監(jiān)聽mousemove
和mouseup
事件。mousemove
事件:監(jiān)聽鼠標(biāo)的偏移量,以計算裁剪區(qū)域的寬度和高度。mouseup
事件:截圖結(jié)束,注銷監(jiān)聽mousedown
和mousemove
事件,并繪制裁剪區(qū)域。
let startPosition = []; // 記錄鼠標(biāo)點擊(開始截圖)的位置。 let screenshotData = []; // 保存截取部分的相關(guān)信息。 function init() { // 監(jiān)聽鼠標(biāo)點擊事件。 canvas1.addEventListener('mousedown', handleMouseDown, false); } // 記錄鼠標(biāo)點擊(開始截圖)的位置,并監(jiān)聽相關(guān)事件。 function handleMouseDown(e) { startPosition = [e.offsetX, e.offsetY]; canvas1.addEventListener('mousemove', handleMouseMove, false); canvas1.addEventListener('mouseup', handleMouseUp, false); } // 監(jiān)聽鼠標(biāo)的偏移量,以計算裁剪區(qū)域的寬度和高度。 function handleMouseMove(e) { // 獲取裁剪區(qū)域的寬度和高度。 const { offsetX, offsetY } = e; const [startX, startY] = startPosition; const [rectWidth, rectHeight] = [offsetX - startX, offsetY - startY]; console.log('rect', rectWidth, rectHeight); // 保存裁剪區(qū)域的相關(guān)信息。 screenshotData = [startX, startY, rectWidth, rectHeight]; } // 注銷監(jiān)聽事件等后續(xù)操作。 function handleMouseUp() { canvas1.removeEventListener('mousemove', handleMouseMove, false); canvas1.removeEventListener('mouseup', handleMouseUp, false); }
在 handleMouseMove
函數(shù)中,我們已經(jīng)獲取了裁剪區(qū)域的寬高,也就是生成截圖的寬高。
接下來,我們需要在原圖像上展示出我們所裁剪的區(qū)域,也就是這個效果:
可以看到,原圖像的上方、裁剪區(qū)域下方會覆蓋一層半透明黑色蒙層,它的作用是區(qū)分原圖層和裁剪部分圖層。所以我們需要在繪制截圖區(qū)域之前,添加一層蒙層。
注意,在已有內(nèi)容的 canvas
畫布上進(jìn)行再次繪制之前,需要先清除整個畫布的內(nèi)容。 這里通過 clearRect()
方法清除 canvas1
畫布上的所有內(nèi)容,并添加蒙層。
我們繼續(xù)來補(bǔ)充 handleMouseMove
和 handleMouseUp
函數(shù)中的邏輯:
const MASKER_OPACITY = 0.4; function handleMouseMove(e) { // 獲取裁剪區(qū)域的寬度和高度。 const { offsetX, offsetY } = e; const [startX, startY] = startPosition; const [rectWidth, rectHeight] = [offsetX - startX, offsetY - startY]; console.log('rect', rectWidth, rectHeight); // 保存裁剪區(qū)域的相關(guān)信息。 screenshotData = [startX, startY, rectWidth, rectHeight]; // 再次繪制前,清理 canvas1 畫布上的內(nèi)容。 const { width, height } = canvas1; ctx.clearRect(0, 0, width, height); // 在 canvas1 畫布上繪制蒙層。 drawImageMasker(0, 0, width, height, MASKER_OPACITY); // 繪制截圖區(qū)域。 drawScreenShot(width, height, rectWidth, rectHeight); } // ... // 繪制圖片蒙層,填充范圍和顏色,以便區(qū)分原圖層和裁剪部分圖層。 function drawImageMasker(x, y, width, height, opacity) { ctx.fillStyle = `rgba(0, 0, 0, ${opacity})`; ctx.fillRect(0, 0, width, height); } // 繪制裁剪的矩形區(qū)域。 function drawScreenShot(canWidth, canHeight, rectWidth, rectHeight) { // 在源圖像外繪制新圖像,只有源圖像外的目標(biāo)圖像部分會被顯示,源圖像是透明的。 ctx.globalCompositeOperation = 'destination-out'; ctx.fillStyle = '#2c2c2c'; ctx.fillRect(...startPosition, rectWidth, rectHeight); // 在現(xiàn)有畫布上繪制新的圖形。 ctx.globalCompositeOperation = 'destination-over'; ctx.drawImage(imageBox, 0, 0, canWidth, canHeight, 0, 0, canWidth, canHeight); }
然后,當(dāng)我們放開鼠標(biāo)(結(jié)束截圖動作)時,除了注銷對 mousedown
和 mousemove
事件的監(jiān)聽,還需要將所得的裁剪區(qū)域的圖像放入另一個 canvas
中。
在繪制新圖像的過程中,我們需要使用以下方法:
- getImageData():讀取
canvas
上的內(nèi)容,返回一個ImageData
對象,包含了每個像素的信息。 - putImageData():將
ImagaData
對象的數(shù)據(jù)放入canvas
中,覆蓋canvas
中的已有圖像。
function handleMouseUp() { canvas1.removeEventListener('mousemove', handleMouseMove, false); canvas1.removeEventListener('mouseup', handleMouseUp, false); // 開始繪制截圖區(qū)域圖片。 drawScreenshotImage(screenshotData); // 如果裁剪得到新圖像后,不希望保留原圖像,可以設(shè)置以下屬性。 // canvasContainer1.style.display = 'none'; } // 在新容器 canvas2 上繪制新圖像。 function drawScreenshotImage(screenshotData) { // 獲取 canvas1 的數(shù)據(jù)。 const data = ctx.getImageData(...screenshotData); // 創(chuàng)建 canvas2 畫布。 generateCanvas(canvasContainer2, canvas2, screenshotData[2], screenshotData[3]); // 每次繪制前,都先進(jìn)行清除操作。 ctx2.clearRect(...screenshotData); // 將 canvas1 的數(shù)據(jù)放入 canvas2 中。 ctx2.putImageData(data, 0, 0); }
經(jīng)過以上步驟,就可以實現(xiàn)我們所需的效果
總結(jié)
對于 canvas
的使用比較少,所以想記錄一下自己的使用過程。
如果本文中出現(xiàn)了什么錯誤或者有什么建議,歡迎大家指正~
以上就是JS使用canvas實現(xiàn)基本的截圖功能的詳細(xì)內(nèi)容,更多關(guān)于JS canvas截圖的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
javascript時間排序算法實現(xiàn)活動秒殺倒計時效果
這篇文章主要介紹了javascript時間排序算法實現(xiàn)活動秒殺倒計時效果,即一個頁面多個倒計時排序,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-03-03JavaScript動態(tài)創(chuàng)建div屬性和樣式示例代碼
動態(tài)創(chuàng)建div屬性和樣式在某些情況下還是比較實用的,下面為大家詳細(xì)介紹下js中div屬性和樣式的動態(tài)創(chuàng)建,感興趣的朋友可以參考下2013-10-10JavaScript中將一個值轉(zhuǎn)換為字符串的方法分析[譯]
在JavaScript中,主要有三種方法能讓任意值轉(zhuǎn)換為字符串.本文講解了每種方法以及各自的優(yōu)缺點2012-09-09js調(diào)用打印機(jī)打印網(wǎng)頁字體總是縮小一號的解決方法
直接調(diào)用window.print(),但是打印出來后,字體總是縮小一號,后來直接target="_blank",就可以正常打印了,下面是實現(xiàn)代碼2014-01-01如何使用moment.js獲取本周、前n周、后n周開始結(jié)束日期及動態(tài)計算周數(shù)
使用了momentjs之后發(fā)現(xiàn)這個日期處理控件實在是太強(qiáng)大了,下面這篇文章主要給大家介紹了關(guān)于如何使用moment.js獲取本周、前n周、后n周開始結(jié)束日期及動態(tài)計算周數(shù)的相關(guān)資料,需要的朋友可以參考下2022-09-09JS逆向之?webpack?打包站點實戰(zhàn)原理分享
本文主要介紹了JS逆向之webpack打包站點實戰(zhàn)原理分享,webpack是前端程序員用來進(jìn)行打包JS的技術(shù),打包之后的代碼特征非常明顯,更多相關(guān)知識需要的小伙伴可以參考下面文章詳細(xì)內(nèi)容2022-06-06用xhtml+css寫的相冊自適應(yīng) - 類似九宮格[兼容 ff ie6 ie7 opear ]
用xhtml+css寫的相冊自適應(yīng) - 類似九宮格[兼容 ff ie6 ie7 opear ]...2007-05-05