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