标签: 前端
[toc]
前言
经常都会遇到一些上传图片前裁剪的需求,这个时候一般都会找到第三方的插件来完成需求。但有时(很不幸)会遇到一些难以处理的情况,例如找不到满足需求的插件或者插件太大而只用到其中一个功能,这种时候就需要自己动手实现一个裁剪工具了。
因此了解一下如何用canvas来实现裁剪功能(其实可以做到更多)是很有必要的,那么现在就开始吧:
原理
分为以下几个步骤
- 读取本地图片文件
- 图片处理
- 输出图片
好像有点太简略。。
简单的例子
下面讲一下自己的例子,功能就是读取图片,左右两个canvas,左边有个半透明蒙版选择裁剪大小,右边输出裁剪后图片。
读取文件
用<input type="file">
读取本地文件,监听onchange
事件,使用Image
对象来做个中转方便canvas使用
// 这个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图片处理
这个例子是鼠标框选截图,因此先加上鼠标事件
let dragging = false
let startX, startY
// 实现拖拽时触发事件
canvas.addEventListener('mousedown', e => {
dragging = true
// canvas内部的鼠标位置
startX = e.offsetX
startY = e.offsetY
}
canvas.addEventListener('mousemove', e => {
if (!dragging) return
// ... 裁剪逻辑
// ... 立即截图
// 鼠标移出canvas也要使dragging = false
}
canvas.addEventListener('mouseup', e => {
dragging = false
})
画个镂空的蒙版表示选择框,这里用到globalCompositeOperation
,图片合并效果来实现蒙版,具体见MDN
// 前面已经获取了startX, startY
const ctx = canvas.getContext('2d')
canvas.addEventListener('mousemove', e => {
if (!dragging) return
// ... 裁剪逻辑
// 每帧都要清理画布,这里400是canvas的内部像素长宽,不是css的长宽
ctx.clearRect(0, 0, 400, 400)
// 绘制蒙版
ctx.save()
ctx.fillStyle = 'rgba(0,0,0,0.5)'
ctx.fillRect(0, 0, 400, 400)
// 开启镂空合并
ctx.globalCompositeOperation = 'desination-out'
const currentX = e.offsetX
const currentY = e.offsetY
ctx.fillStyle = 'white' // 什么颜色没所谓
ctx.fillRect(startX, startY, currentX - startX, currentY - startY)
ctx.restore()
// 绘制底图
ctx.save()
// 将图片绘制到蒙版下方
ctx.globalCompositeOperation = 'destination-over'
// 参数是:图片;读取图片的起点,长宽;画在canvas上的起点,长宽;
ctx.drawIamage(img, 0, 0, img.width, img.height, 0, 0, 400, 400)
ctx.restore()
// ...立即截图
// 鼠标移出canvas也要使dragging = false
}
这样就实现了图片蒙版选择框,下面是截图,很简单
canvas.addEventListener('mousemove', e => {
if (!dragging) return
// ... 裁剪逻辑
// 立即截图
const data = ctx.getImageData(startX, startY, currentX - startX, currentY - startY)
// 输出在另一个canvas上
resultCtx.clearRect(0,0,400,400)
resultCtx.putImage(data, 0, 0)
// 鼠标移出canvas也要使dragging = false
}
输出图片
很简单,用到canvas.toBlob
输出二进制数据,然后转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)
一些哲学♂
对实现一个功能感到不知所措的时候,很可能就是对基础的api不熟悉,就像这个例子中,如果不知道canvas有getImageData
,putImageData
这两个api,那么就不知道如何实现裁剪了,然后就陷入不停找插件的困境。所以,不知道怎么办时别慌,找找js的api。