canvas drawbitmap不出现_「 原创 」徒手撸浏览器 Canvas 涂鸦白板 - 实现圆形橡皮擦...

众所周知,Canvas 清除画布内容的 API 是 clearRect 方法,且调用该方法 清除的是矩形区域。日常中,用的更多的却是 圆形区域 的橡皮擦。

761398e350a8ea910d3314617a954f09.png

擦除效果

要实现圆形效果的橡皮擦,就不得不提 Canvas API 中的 clip 裁剪方法了,实现的方案就是根据尺寸大小,绘制相应的圆(arc),接着将这部分内容裁剪,在裁剪的区域内,执行 clearRect 方法,清除掉整个裁剪区域,不影响主屏内容

Pointerdown

鼠标按下的同时,更新 drawing 状态,并调用自定义 eraser 方法,将该点擦除。

/*** 按下操作.* @param event*/protected pointerdown(event: MouseEvent | PointerEvent | Touch): void {  this.drawing = true;this.ctx.lineJoin = this.ctx.lineCap = 'round';  this.ctx.lineWidth = 20;  // 擦除  const point = {x: event.clientX, y: event.clientY};this.erase(point);}

Pointermove

同理,不断移动的过程中,不断的调用 eraser 方法擦除相应的点。

/*** 移动.* @param event*/protected pointermove(event: PointerEvent | MouseEvent): void {  if (this.drawing) {    // 因为第一个点已经在按下的时候擦除    if (this.data.length > 1) {    }  }}

earse

擦除动作,先调用 save 保存 Canvas 状态,接着调用 clip 方法进行裁剪,clearRect 清除,最后 restore 恢复。

/*** 擦除.* @param point* @param ctx*/protected erase(point: {x: number; y: number;}): void {  this.ctx.save();this.ctx.beginPath();this.ctx.arc(point.x, point.y, 10, 0, Math.PI * 2);this.ctx.clip();this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);this.ctx.restore();}

Pointerup

更新 drawing 状态,避免鼠标没有按下的情况下,再次触发了移动事件。

/*** 鼠标抬起.* @param event*/protected pointerup(event: PointerEvent | MouseEvent): void {  if (this.drawing) this.drawing = false;}

完了?想法很美好,现实却非常残酷,实际出来的效果,不忍直视

1b5c111e8cfe55a7b64932fd84b37c8e.png

一点都不连贯

原因是鼠标在移动过程中,并不会记录所有的移动信息,而是通过取插值的方法拿到鼠标的坐标信息,移动速度太快的时候,根本来不及响应。既然如此,那就手动把不连贯的两个点连起来,再将连起来的这部分裁剪下来清除掉,这样就可以不管两个点连不连贯了,知道这两点坐标即可,如下图所示:

cc83f61434be7f12c12194146524e494.png

两点相连,删除矩形区域

改进 Eraser 方法

求两个圆点之间形成的矩形坐标,这个是关键,关系到夹角 / 弧度的问题,涉及了三角函数(正切 / 正弦 / 余弦之类的这里就不赘述了)。

/*** 擦除(连贯).* 根据两点之间连成一条直线, 再用clip裁剪后擦除.* @param p1* @param p2*/protected erase(  p1: {x: number; y: number;},  p2: {x: number; y: number;}): void {  const radius = 20;  // 求两圆点之间的矩形坐标,这个是重点  const sin = radius * Math.sin(Math.atan((p1.y - p2.y) / (p1.x - p2.x))),    cos = radius * Math.cos(Math.atan((p1.y - p2.y) / (p1.x - p2.x)));const lt = {x: p2.x + sin, y: p2.y - cos},lb = {x: p2.x - sin, y: p2.y + cos},rt = {x: p1.x + sin, y: p1.y - cos},rb = {x: p1.x - sin, y: p1.y + cos};// 开始擦除this.ctx.save();this.ctx.beginPath();this.ctx.arc(p1.x, p1.y, radius, 0, Math.PI * 2);this.ctx.closePath();this.ctx.clip();this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);this.ctx.restore();// 用 lineTo 连接四个点, 形成裁剪区域ctx.save();ctx.beginPath();ctx.moveTo(lt.x, lt.y);ctx.lineTo(rt.x, rt.y);ctx.lineTo(rb.x, rb.y);ctx.lineTo(lb.x, lb.y);ctx.closePath();ctx.clip();ctx.restore();}

小结

主要是调用 clip 裁剪后清除,不影响主屏。再者就是移动速度太快的时候,出现不连贯的擦除效果,需要用到三角函数来计算两圆点连成的线,与坐标系之间形成的角度,方便根据两点来绘制矩形的擦除区域,进而再裁剪擦除。这两点实现了,圆形橡皮擦基本就OK了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值