CDarg诞生之路之Canvas实现变形、旋转(图片,文字)
前言
上一篇我们实现了文字、图片的命中检测与拖拽,再做旋转的时候发现,旋转后的图形在做命中检测时有问题,因为图形已经旋转,但矩形的四点坐标仍然没变,导致无法命中,所以命中检测改为使用 isPointInPath()
检测
isPointInPath命中检测
isPointInPath()
是 Canvas 2D API 用于判断在当前路径中是否包含检测点的方法,
命中逻辑
mousedown
的时候重绘所有图形,每绘制一个图形就使用 isPointInPath()
检测是否命中这个绘制图形
核心代码
// 开始新路径
ctx.beginPath()
ctx.rect(x,y,w,h)
// 绘制
ctx.stroke()
const isHitTest = ctx.isPointInPath(e.clientX, e.clientY)
注意!!! isPointInPath无法检测strokeText()、strokeRect()、drawImage()、fillText()等生成内容
那咱们绘制的图片与文字如何命中检测呢?
咱可以给图片和文字使用 rect()
绘制一个透明的包裹容器,直接 isPointInPath()
容器也是一样的
绘制控件
命中图形后绘制变形、旋转、删除控件,分别在左上、右上、右下位置,这里直接那算出图形的顶点、再以顶点为中心画圆 再绘制按钮图片
ctx.arc(left, top, 16, 0, 2 * Math.PI)
ctx.drawImage(image, left - 2, top - 2, 12, 12)
控件命中
控件命中逻辑与前面一样
- mousedown重绘
- 是否命中图形
- 命中图形后在绘制控件
- 是否命中某个控件
旋转
计算旋转角度使用 Math.atan2()
Math.atan2()
返回从原点 (0,0) 到 (x,y) 点的线段与 x 轴正方向之间的平面角度 (弧度值),也就是 Math.atan2(y,x)
,而角度 = 弧度 * 180 / Math.PI
if (type === 'rotate') {
// 计算命中图形中心点
const { centerX, centerY } = this.computeCenter(selectRect)
// 按下时的角度 down是mousedown的点
const angleBefore = Math.atan2(downY - centerY, downX - centerX) / Math.PI * 180
// 移动形成的角度 client是mousemove的点
const angleAfter = Math.atan2(clientY - centerY, clientX - centerX) / Math.PI * 180
// 旋转的角度等于初始角度(initRotate)+ 移动角度 - 按下时的角度
selectRect[rotate] = initRotate + angleAfter - angleBefore
this.draw()
}
旋转图形绘制
绘制旋转图形及使用 rotate()
但是在旋转前还得使用 translate()
方法对当前网格的原点平移到当前图形中心点,否则就是以(0,0)点做旋转,旋转完成后还得 restore()
将 canvas
恢复到最近的保存状态的方法,否则后续图形渲染会位置会不正确
restore()
恢复的状态从哪来?
save()
将当前状态放入栈中,得先有状态才能恢复,所以一开始得先 save()
现在绘制图片核心代码如下
// 开始新路径
ctx.beginPath()
// 保证状态
ctx.save()
// 改变中心
ctx.translate(drawData.left + drawData.width / 2, drawData.top + drawData.height / 2)
// 旋转
ctx.rotate(drawData.rotate * Math.PI / 180)
// 绘制
ctx.drawImage(drawData.imageEle, -drawData.width / 2, -drawData.height / 2, drawData.width, drawData.height)
// 恢复
ctx.restore()
变形
变形就更简单一点,使用移动点减去按下的点 x,y
坐标 加上初始距离即可,对文字的变形直接用差值赋给 size
即可,因为宽高对文字无效,这个还考虑限制一下最小宽高与最小 size
if (type === 'transform') {
// 移动的x距离
const moveX = clientX - downX
// 移动的y距离
const moveY = clientY - downY
let newWidth = initWidth + moveX
let newHeight = initHeight + moveY
// 反向
if (newWidth < 0) {
newWidth = -newWidth
}
if (newHeight < 0) {
newHeight = -newHeight
}
// 限制最小宽高与size
if (selectRect.img) {
selectRect.width = newWidth < 6 ? 6 : newWidth
selectRect.height = newHeight < 6 ? 6 : newHeight
} else if (selectRect.text) {
selectRect.size = newHeight < 12 ? 12 : newHeight
}
this.draw()
}
这里对变形做了反向,如果宽高小于0则反向处理
效果
结尾
CDrag最核心的功能差不多完成了,后续还要再完善一下,加上超出画布限制等等,测试完成后会发布到npm,敬请期待