canvas裁剪圖片,蒙版選擇框

標(biāo)簽: 前端


[toc]

前言

經(jīng)常都會(huì)遇到一些上傳圖片前裁剪的需求,這個(gè)時(shí)候一般都會(huì)找到第三方的插件來(lái)完成需求。但有時(shí)(很不幸)會(huì)遇到一些難以處理的情況,例如找不到滿足需求的插件或者插件太大而只用到其中一個(gè)功能,這種時(shí)候就需要自己動(dòng)手實(shí)現(xiàn)一個(gè)裁剪工具了。

因此了解一下如何用canvas來(lái)實(shí)現(xiàn)裁剪功能(其實(shí)可以做到更多)是很有必要的,那么現(xiàn)在就開(kāi)始吧:

原理

分為以下幾個(gè)步驟

  1. 讀取本地圖片文件
  2. 圖片處理
  3. 輸出圖片

好像有點(diǎn)太簡(jiǎn)略。。

簡(jiǎn)單的例子

下面講一下自己的例子,功能就是讀取圖片,左右兩個(gè)canvas,左邊有個(gè)半透明蒙版選擇裁剪大小,右邊輸出裁剪后圖片。

讀取文件

<input type="file">讀取本地文件,監(jiān)聽(tīng)onchange事件,使用Image對(duì)象來(lái)做個(gè)中轉(zhuǎn)方便canvas使用

// 這個(gè)img可以供canvas繪圖
const img = new Image() 
input.onchange = function(e) {
    const file = e.target.files[0]
    const reader = new FileReader()
    reader.readAsDataURL(file)
    reader.onload = function(e) {
      img.src = e.target.result
    }
}

canvas圖片處理

這個(gè)例子是鼠標(biāo)框選截圖,因此先加上鼠標(biāo)事件

let dragging = false
let startX, startY
// 實(shí)現(xiàn)拖拽時(shí)觸發(fā)事件
canvas.addEventListener('mousedown', e => {
  dragging = true
  // canvas內(nèi)部的鼠標(biāo)位置
  startX = e.offsetX
  startY = e.offsetY
}
canvas.addEventListener('mousemove', e => {
  if (!dragging) return
  // ... 裁剪邏輯
  // ... 立即截圖
  // 鼠標(biāo)移出canvas也要使dragging = false
}
canvas.addEventListener('mouseup', e => {
  dragging = false
})

畫(huà)個(gè)鏤空的蒙版表示選擇框,這里用到globalCompositeOperation,圖片合并效果來(lái)實(shí)現(xiàn)蒙版,具體見(jiàn)MDN

// 前面已經(jīng)獲取了startX, startY
const ctx = canvas.getContext('2d')
canvas.addEventListener('mousemove', e => {
  if (!dragging) return
  // ... 裁剪邏輯
  
  // 每幀都要清理畫(huà)布,這里400是canvas的內(nèi)部像素長(zhǎng)寬,不是css的長(zhǎng)寬
  ctx.clearRect(0, 0, 400, 400)
  
  // 繪制蒙版
  ctx.save()
  ctx.fillStyle = 'rgba(0,0,0,0.5)'
  ctx.fillRect(0, 0, 400, 400)
  // 開(kāi)啟鏤空合并
  ctx.globalCompositeOperation = 'desination-out'
  const currentX = e.offsetX
  const currentY = e.offsetY
  ctx.fillStyle = 'white' // 什么顏色沒(méi)所謂
  ctx.fillRect(startX, startY, currentX - startX, currentY - startY)
  ctx.restore()
  
  // 繪制底圖
  ctx.save()
  // 將圖片繪制到蒙版下方
  ctx.globalCompositeOperation = 'destination-over'
  // 參數(shù)是:圖片;讀取圖片的起點(diǎn),長(zhǎng)寬;畫(huà)在canvas上的起點(diǎn),長(zhǎng)寬;
  ctx.drawIamage(img, 0, 0, img.width, img.height, 0, 0, 400, 400)
  ctx.restore()
 
  // ...立即截圖
  
  // 鼠標(biāo)移出canvas也要使dragging = false
}

這樣就實(shí)現(xiàn)了圖片蒙版選擇框,下面是截圖,很簡(jiǎn)單

canvas.addEventListener('mousemove', e => {
  if (!dragging) return
  // ... 裁剪邏輯
  
  // 立即截圖
  const data = ctx.getImageData(startX, startY, currentX - startX, currentY - startY)
  // 輸出在另一個(gè)canvas上
  resultCtx.clearRect(0,0,400,400)
  resultCtx.putImage(data, 0, 0)
  
  // 鼠標(biāo)移出canvas也要使dragging = false
}

輸出圖片

很簡(jiǎn)單,用到canvas.toBlob輸出二進(jìn)制數(shù)據(jù),然后轉(zhuǎn)File就可以

const mime = 'image/jpeg' // image/png
resultCanvas.toBlob(blob => {
  // 注意是`[blob]`
  const file = new File([blob], '圖片.jpg', { type: blob.type })
  // uploadFile(file)
}, mime, 0.9)

一些哲學(xué)♂

對(duì)實(shí)現(xiàn)一個(gè)功能感到不知所措的時(shí)候,很可能就是對(duì)基礎(chǔ)的api不熟悉,就像這個(gè)例子中,如果不知道canvas有getImageData,putImageData這兩個(gè)api,那么就不知道如何實(shí)現(xiàn)裁剪了,然后就陷入不停找插件的困境。所以,不知道怎么辦時(shí)別慌,找找js的api。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容