起因
基于一个复杂的图片预览的弹窗之上新增一个裁剪功能,直接用新的插件改掉原来的功能又很复杂而且操作方式与需求不符。因此需要自定义一个简单的裁剪功能。以canvas画布元素与图片标签的位置关系和大小进行裁剪。
canvas方案
流程
- 首先创建canvas对像
- 指定canvas的高度宽度(非style的宽高)
- 计算原图需要剪切的相关参数
- 通过drawImage方法将需要的部分画上去
- 通过toDataURL方法获取图片base64的值
- 完成,后续可对base64值进行其他操作。
//本地或线上图片地址
const SRC = "./aa.jpg"
const canvas = document.getElementById('canvas-cut')
const cut = canvas.getContext('2d')
const img = new Image()
img.src = SRC
//处理toDataURL遇跨域资源导致的报错
img.crossOrigin = 'Anonymous'
img.onload = function() {
cut.drawImage(img,295,40,100,100,0 ,0,100,100)
var imgbase64 = canvas.toDataURL("image/png")
//base64资源
console.log(imgbase64)
};
drawImage参数
drawImage 方法允许在 canvas 中插入其他图像(img和canvas元素)
以左上角为原点x,y轴的距离
//参数有三种传值方式
drawImage(image, dx, dy)
drawImage(image, dx, dy, dw, dh)
//dx,dy 在画布上裁剪后图像放置的 x,y坐标位置
//sw和sh为在原图中需要截取的宽高,最后会以缩放的方式以dw,dh的大小展示在canvas中
drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh)
适配原图的缩放旋转
当原图存在缩放和旋转的情况,裁剪也需要进行缩放旋转
旋转
canvas的旋转也是以左上角有为原点为旋转,旋转90度之后,则内容为空白了,因此需要translate属性移动一个距离
cut.rotate(Math.PI / 2)
cut.translate(0, -1 * canvas.height)
//顺时针与逆时针旋转-270~270之间
// rationNum = (旋转角度/90)%4
cut.rotate(Math.PI * rationNum / 2)
switch (rationNum) {
case -1:
case 3:
cut.translate(-1 * canvas.width, 0)
break
case 1:
case -3:
cut.translate(0, -1 * canvas.height)
break
case -2:
case 2:
cut.translate(-1 * canvas.width, -1 * canvas.height)
break
}
}
按照上图旋转之后,画布与参照图片的关系,在实际裁剪的图的裁剪方位将会有变化(原点变化了) 这里需要根据canvas与展示的图片的位置关系,推算出裁剪原图和canvas的位置关系即sx,sy的值
//drawLocal为裁剪的方位sx,sy最后还需要乘以缩放比例
//注意:我这里的canvas是正方形的所以width和height值一样,此处都用canvas.width
//drawLocal在原图上面裁剪的起始位sx,sy的值
switch (rationNum) {
case -1:
case 3:
drawLocal = {
x: img.width - (canvasXY.y - imgXY.y) - canvas.width,
y: canvasXY.x - imgXY.x
}
break
case 1:
case -3:
drawLocal = {
x: canvasXY.y - imgXY.y,
y: img.height - (canvasXY.x - imgXY.x) - canvas.width
}
break
case -2:
case 2:
drawLocal = {
x: img.width - (canvasXY.x - imgXY.x) - canvas.width,
y: img.height - (canvasXY.y - imgXY.y) - canvas.width
}
break
}
缩放
实际中,看到的是一张图片,裁剪的是以new Image()新生的图片,因此所有的距离必须以画布与展示的图片计算坐标数据,然后再以缩放比例进行计算
const imgScale = img.style.transform ? img.style.transform.match(/scale\((\S*)\)/)[1] : 1
const scaleNum = originImg.width / (img.width * imgScale)
图片有两张:一张可以缩放旋转的图,一张是实际截取内容的图片
//最后大致如下
//注意:这里canvas为一个正方形
cut.drawImage(
originImg,
drawLocal.x * ration,
drawLocal.y * ration,
canvas.width * ration,
canvas.width * ration,
0,
0,
canvas.width,
canvas.width
)
内部图片不支持跨域与canvas crossOrigin属性的冲突
所谓的跨域是因为浏览器的同源策略而引起的。(协议,域名,端口号)
但是img和link标签只能实现单向通信,即只能从客户端向服务器传递数据。img获取图片进行加载,link是获取样式表加载,而script返回的是javascript代码执行。因此,script,img,link标签不受跨域影响。 但是canvas需要对裁剪的图片进行可跨域的设置
originImg.setAttribute('crossOrigin', 'anonymous')
因此会出现,页面明明有图片,但是设置该属性就回401(设置为允许跨域之后会图片的请求会不带上cookie)
解决方案就是使用ajax发起请求获取图片资源然后再设置给img(由于img标签不受跨域的影响,当用ajax请求是很可能出现跨域的现象,这里需要后端设置一下)
//data 拿到资源然后转位base64格式
const reader = new FileReader()
reader.readAsDataURL(data)
reader.onload = ({ target }) => {
callback((target as FileReader).result as string)
}
重置canvas画布内容
-
使用:
context.clearRect(0, 0, canvas.width, canvas.height)
这是清除整个canvas的最快和最具描述性的方法。
-
不要使用:
canvas.width = canvas.width
重置
canvas.width
重置所有canvas状态(例如,转换,lineWidth,strokeStyle等),它非常缓慢,不适用于所有浏览器,并且不描述你实际尝试的内容去做(自己赋值自己???重点是typescript也无法解析)
关于git
由于多次的尝试以及本地有代理,之前跨域请求在打包发布至测试环境才会出现跨域的问题,产生很多git log,一把辛酸泪。所以这里需要git合并多个commit,不然不好看
//找到最开始的commit id
git log
//软重置
git reset --soft 1111111111
//生成一次新的commit
git commit -m'XXX功能'
git push -f
最后
总结一下,canvas裁剪会遇到图片资源跨域的问题,适配图片的旋转缩放,不同的预览图片的插件,旋转缩放的方式也不同,但最终是需要计算出比例即可。。。。。
希望遇到的每一个坑都是一次进步~