標(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è)步驟
- 讀取本地圖片文件
- 圖片處理
- 輸出圖片
好像有點(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
})
畫個(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
// ... 裁剪邏輯
// 每幀都要清理畫布,這里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)寬砾省;畫在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。