JS+Canvas實(shí)現(xiàn)上傳圖片截圖功能
1. 前言
在我們平時(shí)開發(fā)圖片上傳時(shí),有時(shí)需要實(shí)現(xiàn)圖片的裁剪功能。通常我們使用Vue-cropper來實(shí)現(xiàn)圖片的裁剪功能。
如何實(shí)現(xiàn)呢?本文介紹下圖片裁剪的功能的實(shí)現(xiàn)過程。
2. 實(shí)現(xiàn)思路
- 使用
input
元素讓用戶選擇圖片文件,然后使用FileReader
API 讀取圖片文件的內(nèi)容,并將其設(shè)置為img
元素的src
屬性。 - 創(chuàng)建一個(gè)
canvas
元素并獲取其 2D 渲染上下文,然后使用drawImage
方法將圖片繪制到 canvas 上。 - 監(jiān)聽鼠標(biāo)的
mousedown
、mousemove
和mouseup
事件,用于創(chuàng)建和調(diào)整裁剪框。當(dāng)用戶點(diǎn)擊鼠標(biāo)并移動(dòng)時(shí),記錄鼠標(biāo)的位置并在 canvas 上繪制一個(gè)矩形作為裁剪框。 - 當(dāng)用戶釋放鼠標(biāo)按鈕時(shí),使用
getImageData
方法獲取裁剪框內(nèi)的像素?cái)?shù)據(jù),然后創(chuàng)建一個(gè)新的 canvas 并使用putImageData
方法將這些像素?cái)?shù)據(jù)放入新的 canvas 中。這樣就得到了裁剪后的圖片。 - 最后,使用
toDataURL
方法將裁剪后的圖片轉(zhuǎn)換為 Base64 格式的字符串,或者使用toBlob
方法將其轉(zhuǎn)換為 Blob 對(duì)象,然后可以將其下載到用戶的計(jì)算機(jī)上,或者上傳到服務(wù)器。
3. 實(shí)現(xiàn)步驟
使用原生js進(jìn)行實(shí)現(xiàn),使用兩個(gè)canvas,一個(gè)是原圖展示,一個(gè)是截圖后的。
HTML部分:
<!-- 文件上傳 --> <input type="file" id="inputFile" accept="image/*" /> <!-- 展示讀取的圖片的canvas --> <div class="clipContainer"> <canvas id="clip"></canvas> </div> <!-- 截圖后的圖片的canvas --> <div class="drawContainer"> <canvas id="draw"></canvas> </div>
JS公共部分:
const inputNode = document.querySelector("#inputFile"); const clipCanvas = document.querySelector("#clip"); const drawCanvas = document.querySelector("#draw"); const clipContainer = document.querySelector( ".clipContainer" ); const drawContainer = document.querySelector( ".drawContainer" ); const clipCanvasCtx = clipCanvas.getContext("2d"); const drawCanvasCtx = drawCanvas.getContext("2d"); const imageNode = document.createElement('img'); const drawImageNode = document.createElement('img');//用于最終展示圖片 let resultFile = null;//最終生成的File對(duì)象圖片文件
3.1 選擇文件進(jìn)行預(yù)覽
使用FileReader
讀取文件的url和寬高,進(jìn)行cavas繪制。
// 1. 選擇圖片 讀取圖片 inputNode.addEventListener( "change", function (e) { const file = e.target.files[0]; const reader = new FileReader(); reader.onload = function (e) { imageNode.src = e.target.result; imageNode.onload = function () { // 獲取真實(shí)的圖片寬高 canvas繪制圖片 generateCanvas(clipContainer, clipCanvas, this.width, this.height); clipCanvasCtx.drawImage(imageNode, 0, 0, this.width, this.height); }; }; file && reader.readAsDataURL(file); }, false ); // 2. canvas繪制圖片 function generateCanvas(canvasContainer, canvas, width, height) { // 2.1 外部容器的寬高 canvasContainer.style.width = width + 'px'; canvasContainer.style.height = height + 'px'; // 2.2 canvas的寬高 canvas.width = width; canvas.height = height; }
結(jié)果如下:
3.2 監(jiān)聽mousedown事件
監(jiān)聽mousedown
記錄起點(diǎn)坐標(biāo),使用startPositon進(jìn)行記錄起始位置。
// 記錄點(diǎn)擊開始結(jié)束位置 let startPosition = [] clipCanvas.addEventListener('mousedown', function (e) { // 3.1 記錄開始截圖的位置 startPosition = [e.offsetX, e.offsetY] // 3.2 開始監(jiān)聽 mousemove 和 mouseup 事件 clipCanvas.addEventListener('mousemove', mousemoveHandler, false) clipCanvas.addEventListener('mouseup', mouseupHandler, false) })
3.3 監(jiān)聽mousemove事件
記錄鼠標(biāo)移動(dòng)坐標(biāo),計(jì)算出截圖大小,繪制截圖蒙層區(qū)域
// 記錄截圖的偏移x,y和截圖的寬高w,h let screenShotData = [] function mousemoveHandler(e) { // 1. 計(jì)算裁剪區(qū)域的寬度和高度 const { offsetX, offsetY } = e const [startX, startY] = startPosition // 偏移位置-起始位置 = 真實(shí)寬高 const [rectWidth, rectHeight] = [offsetX - startX, offsetY - startY] // 2. 保存裁剪區(qū)域的相關(guān)信息 screenShotData = [startX, startY, rectWidth, rectHeight] // 3. 重新繪制 fill(clipCanvasCtx, clipCanvas, screenShotData) } function fill(ctx, canvas, sizeData) { const { width, height } = canvas const [x, y, w, h] = sizeData // 1. 再次繪制前,清理canvas1畫布上的內(nèi)容 ctx.clearRect(0, 0, width, height) // 2. 畫布上繪制蒙層 ctx.fillStyle = `rgba(0,0,0,0.6)` ctx.fillRect(0, 0, width, height) // 3. 繪制裁剪區(qū)域 // 在源圖像外繪制新圖像,只有源圖像外的目標(biāo)圖像部分會(huì)被顯示,源圖像是透明的 ctx.globalCompositeOperation = 'destination-out' ctx.fillStyle = '#2c2c2c' ctx.fillRect(...sizeData) // 設(shè)置在現(xiàn)有畫布上繪制新的圖形 ctx.globalCompositeOperation = 'destination-over' // 剪切圖像,并在畫布上定位被剪切的部分 ctx.drawImage(imageNode, 0, 0, width, height, 0, 0, width, height); }
結(jié)果如下:
3.4 監(jiān)聽mouseup事件
注銷事件監(jiān)聽,將裁剪區(qū)域放入另一個(gè)canvas中進(jìn)行處理,并讀取File對(duì)象
function mouseupHandler(e) { // 1. 注銷監(jiān)聽 mousedown 和 mousemove 事件 clipCanvas.removeEventListener('mousemove', mousemoveHandler, false) clipCanvas.removeEventListener('mouseup', mouseupHandler, false) // 2. 開始繪制截圖區(qū)域圖片 const data = clipCanvasCtx.getImageData(...screenShotData) // 3. 使用新畫布 const [x, y, w, h] = screenShotData generateCanvas(drawCanvas, drawContainer, w, h) // 每次繪制前,都先進(jìn)行清除操作 drawCanvasCtx.clearRect(...screenShotData); // 將 clipCanvas 裁剪區(qū)域的數(shù)據(jù)放入 drawCanvas 中 drawCanvasCtx.putImageData(data, 0, 0); drawCanvas.style.display = 'none' // 4. 展示圖片 const imageUrl = drawCanvas.toDataURL('image/png') drawImageNode.src = imageUrl; document.body.appendChild(drawImageNode) // 5. 生成File對(duì)象 drawCanvas.toBlob(blob => { resultFile = new File([blob], 'test.png', { type: 'image/png' }) }, 'image.png') }
4. 總結(jié)
最后總結(jié)一下如圖:
到此這篇關(guān)于JS+Canvas實(shí)現(xiàn)上傳圖片截圖功能的文章就介紹到這了,更多相關(guān)JS Canvas圖片截圖內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
javascript為下拉列表動(dòng)態(tài)添加數(shù)據(jù)項(xiàng)
這篇文章主要介紹了javascript如何為下拉列表動(dòng)態(tài)添加數(shù)據(jù)項(xiàng),需要的朋友可以參考下2014-05-05JavaScript canvas實(shí)現(xiàn)代碼雨效果
這篇文章主要為大家詳細(xì)介紹了JavaScript canvas實(shí)現(xiàn)代碼雨效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-06-06js控件Kindeditor實(shí)現(xiàn)圖片自動(dòng)上傳功能
這篇文章主要為大家詳細(xì)介紹了js控件Kindeditor實(shí)現(xiàn)圖片自動(dòng)上傳功能的相關(guān)資料,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-06-06javascript實(shí)現(xiàn)按回車鍵切換焦點(diǎn)
這篇文章主要介紹了javascript實(shí)現(xiàn)按回車鍵切換焦點(diǎn)的方法,需要的朋友可以參考下2015-02-02