JS使用canvas實(shí)現(xiàn)基本的截圖功能
思路分析
在開(kāi)始動(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)聽(tīng) input 元素的 change 事件,以獲取上傳圖片的相關(guān)參數(shù),這里主要是為了獲取圖片的寬度和高度。
我們創(chuàng)建一個(gè) FileReader() 對(duì)象并監(jiān)聽(tīng)其 load 事件。load 事件在讀取操作成功后立刻執(zhí)行,在這個(gè)方法中我們就可以獲取圖片的寬高。
function init() {
imageFile.addEventListener('change', handleFileChange, false); // 監(jiān)聽(tī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 容器。
// 等圖片加載完成后,獲取圖片的寬高。
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)就可以開(kāi)始動(dòng)態(tài)繪制截圖區(qū)域了。
繪制截圖區(qū)域
在這個(gè)過(guò)程中,我們需要分別監(jiān)聽(tīng) imageBox 容器(原圖像)上的 mousedown、mousemove 和 mouseup 事件,這些事件的作用如下:
mousedown事件:記錄開(kāi)始截圖的位置,并開(kāi)始監(jiān)聽(tīng)mousemove和mouseup事件。mousemove事件:監(jiān)聽(tīng)鼠標(biāo)的偏移量,以計(jì)算裁剪區(qū)域的寬度和高度。mouseup事件:截圖結(jié)束,注銷監(jiān)聽(tīng)mousedown和mousemove事件,并繪制裁剪區(qū)域。
let startPosition = []; // 記錄鼠標(biāo)點(diǎn)擊(開(kāi)始截圖)的位置。
let screenshotData = []; // 保存截取部分的相關(guān)信息。
function init() {
// 監(jiān)聽(tīng)鼠標(biāo)點(diǎn)擊事件。
canvas1.addEventListener('mousedown', handleMouseDown, false);
}
// 記錄鼠標(biāo)點(diǎn)擊(開(kāi)始截圖)的位置,并監(jiān)聽(tīng)相關(guān)事件。
function handleMouseDown(e) {
startPosition = [e.offsetX, e.offsetY];
canvas1.addEventListener('mousemove', handleMouseMove, false);
canvas1.addEventListener('mouseup', handleMouseUp, false);
}
// 監(jiān)聽(tīng)鼠標(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)聽(tīng)事件等后續(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)我們放開(kāi)鼠標(biāo)(結(jié)束截圖動(dòng)作)時(shí),除了注銷對(duì) mousedown 和 mousemove 事件的監(jiān)聽(tīng),還需要將所得的裁剪區(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);
// 開(kāi)始繪制截圖區(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)文章
js實(shí)現(xiàn)帶圓角的兩級(jí)導(dǎo)航菜單效果代碼
這篇文章主要介紹了js實(shí)現(xiàn)帶圓角的兩級(jí)導(dǎo)航菜單效果代碼,涉及javascript鼠標(biāo)事件及頁(yè)面元素樣式動(dòng)態(tài)變換技巧,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2015-08-08
微信小程序 列表的上拉加載和下拉刷新的實(shí)現(xiàn)
本文主要介紹了微信小程序中實(shí)現(xiàn)列表的上拉加載和下拉刷新的方法。具有很好的參考價(jià)值。下面跟著小編一起來(lái)看下吧2017-04-04
微信小程序onShareTimeline()實(shí)現(xiàn)分享朋友圈
這篇文章主要給大家介紹了關(guān)于微信小程序onShareTimeline()實(shí)現(xiàn)分享朋友圈的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01
javascript控制在光標(biāo)位置插入文字適合表情的插入
使用javascript控制在光標(biāo)位置插入文字,在實(shí)現(xiàn)表情的插入時(shí)會(huì)用到的,需要的朋友可以參考下2014-06-06
前端echarts tooltip展示多項(xiàng)自定義數(shù)據(jù)代碼示例
Echarts是一個(gè)基于JavaScript的開(kāi)源圖表庫(kù),提供了豐富的圖表類型,如折線圖、柱狀圖、餅圖、散點(diǎn)圖等,支持動(dòng)態(tài)數(shù)據(jù)交互和自定義配置,這篇文章主要給大家介紹了關(guān)于前端echarts tooltip展示多項(xiàng)自定義數(shù)據(jù)的相關(guān)資料,需要的朋友可以參考下2024-09-09
JavaScript如何實(shí)現(xiàn)圖片懶加載(lazyload) 提高用戶體驗(yàn)(增強(qiáng)版)
這篇文章主要介紹了JavaScript如何實(shí)現(xiàn)圖片懶加載(lazyload) 提高用戶體驗(yàn)(增強(qiáng)版)的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-11-11
js實(shí)現(xiàn)獲取當(dāng)前時(shí)間是本月第幾周的方法
這篇文章主要介紹了js實(shí)現(xiàn)獲取當(dāng)前時(shí)間是本月第幾周的方法,涉及javascript針對(duì)日期及時(shí)間的相關(guān)操作技巧,非常簡(jiǎn)單實(shí)用,需要的朋友可以參考下2015-08-08
echarts實(shí)現(xiàn)雷達(dá)圖的詳細(xì)步驟
這篇文章主要給大家介紹了關(guān)于echarts實(shí)現(xiàn)雷達(dá)圖的詳細(xì)步驟,雷達(dá)圖(Radar?Chart)是一種信息豐富的可視化工具,其中多個(gè)變量(三個(gè)或更多)在二維平面上進(jìn)行比較,文中給出了完整的代碼示例,需要的朋友可以參考下2024-01-01

