摘要:对于能画出贝塞尔曲线的,对已经求出的实例,执行,否则执行画点的方法获取配置中的,执行画点。总结阅读一遍后,这个库说白就是基础的事件操作贝塞尔曲线算法,但是,它内部的代码格式非常清晰,细粒度代码复用使得维护起来非常方便。
signature_pad一个基于Canvas的平滑手写画板工具介绍
实现手写有多种方式。
一种比较容易做出的是对鼠标移动轨迹画点,再将两点之间以直线相连,最后再进行平滑处理,这种方案不需要什么算法支持,但同样,它面对一个性能和美观的抉择,打的点多,密集,性能相对较低,但更加美观,视觉上更平滑;
此处用的另一种方案,画贝塞尔曲线。
由于canvas没有默认的画出贝塞尔曲线方法(感谢@madRain评论中更正)由于canvas并没有提供根据初始和结束点计算出贝塞尔曲线控制点的API,因此这里使用了贝塞尔曲线的一系列算法,包括求控制点,求长度,计算当前点的大小,最后用canvas画出每一个确定位置的点。
补充:个人认为,之所以不使用canvas提供的贝塞尔曲线API,是因为可以实时控制线条粗细(点的大小),在斜街的时候达到平滑的效果。参数及配置介绍
提供的可配置参数如下
export interface IOptions { // 点的大小(不是线条) dotSize?: number | (() => number); // 最粗的线条宽度 minWidth?: number; // 最细的线条宽度 maxWidth?: number; // 最小间隔距离(这个距离用贝塞尔曲线填充) minDistance?: number; // 背景色 backgroundColor?: string; // 笔颜色 penColor?: string; // 节流的间隔 throttle?: number; // 当前画笔速度的计算率,默认0.7,意思就是 当前速度=当前实际速度*0.7+上一次速度*0.3 velocityFilterWeight?: number; // 初始回调 onBegin?: (event: MouseEvent | Touch) => void; // 结束回调 onEnd?: (event: MouseEvent | Touch) => void; }
这里要注意的是并没有线条粗细这个选项,因为这里面的粗细不等线条都是通过一个个大小不同的点构造而成;
throttle这个配置可以参考loadsh或者underscore的_.throttle,功能一致,就是为了提高性能。
注册事件在constructor内部,除了配置传入的参数外,就是注册事件。
这里优先使用了PointerEvent触点事件,PointerEvent可以说是触摸以及点击事件的统一,如果设备支持,不需要再分别为mouse和touch写两套事件了。
状态数据储存状态开关:
this._mouseButtonDown
当执行move事件时,会检查此状态,只有在true的情况下才会执行。
数据储存分为2种格式:
pointGroup
这是当前笔画的点的一个集合,内部储存了当前笔画的颜色color和所有的点points
this._data
这是一个储存所有笔画的栈,格式为[pointGroup, pointGroup, ..., pointGroup],当需要执行undo的时候,只需要删除this._data中的最后一条数据。
事件流程及方法 mouseDown事件当鼠标(触点)按下时,改变状态this._mouseButtonDown = true,调用onBegin回调,创建当前笔画的一个新的集合,然后对当前点执行更新。
mouseMove事件首先检查this._mouseButtonDown状态,对当前点执行更新。
mouseUp事件改变状态this._mouseButtonDown = false;,调用onEnd回调,对当前点执行更新。
可以看到,上面的每一个事件内部都调用对当前点执行更新的方法。
_strokeUpdate——点的更新方法private _strokeUpdate(event: MouseEvent | Touch): void { // 获取当前触点的位置 const x = event.clientX; const y = event.clientY; // 创建点 const point = this._createPoint(x, y); // 调出最后一个点集 const lastPointGroup = this._data[this._data.length - 1]; // 获取最后一个点集的点的数组 const lastPoints = lastPointGroup.points; // 如果存在上一个点,获取上一个点 const lastPoint = lastPoints.length > 0 && lastPoints[lastPoints.length - 1]; // 判断上一个点到当前点是否太近(也就是小于配置的最小间隔距离) const isLastPointTooClose = lastPoint ? point.distanceTo(lastPoint) <= this.minDistance : false; // 调出点集的颜色 const color = lastPointGroup.color; // Skip this point if it"s too close to the previous one // 存在上一个点但是太近,跳过,其余的执行 if (!lastPoint || !(lastPoint && isLastPointTooClose)) { // 向上一次的点数组中添加当前点,并且生成一个新的贝塞尔曲线实例 // 包括4个点 (初始点,2个控制点,结束点) // 初始宽度,最终宽度 const curve = this._addPoint(point); // 如果不存在lastPoint,即当前点是第一个点 if (!lastPoint) { // 画一个点 this._drawDot({ color, point }); // 如果存在lastPoint 并且能形成一个贝塞尔曲线实例(3个点以上) } else if (curve) { // 画出参数中curve实例中两点之间的曲线 this._drawCurve({ color, curve }); } // 添加到当前笔画的点数组 lastPoints.push({ time: point.time, x: point.x, y: point.y, }); } }
这个方法前面就是一系列判断
判断是否是第一个点
判断是否能加入点的集合(满足点的最小间隔)
判断是否能画出贝塞尔曲线(满足至少3个点)
对于能画出贝塞尔曲线的点,执行算法,求出Besier实例,包括4个点初始点,结束点,控制点1,控制点2以及当前曲线中线条的的初始宽度和结束宽度。
具体如何算的,请参考源码src/bezier.ts和这篇文章。
对于能画出贝塞尔曲线的,对已经求出的Bezier实例,执行this._drawCurve,否则执行this._drawDot
this._drawDot——画点的方法获取配置中的dotSize,执行canvas画点。
this.__drawCurve——画线的方法求出当前Bezier实例初始点和结束点之间的距离,这个距离不是直线距离,而是贝塞尔曲线距离。
对这个距离进行扩展,例如,计算得到距离为50,那就扩展为100个点,即我需要在50这个距离内画出100个点;
这么做可以保证在正常或者稍微快速的书写中,不出现断层。
接着又是算法,目的是求出这个距离内的每一个点的大小,这是一个变化值,是的粗细变化更加平滑。
最后同样是canvas画点。
以上就是整个基本流程。
总结阅读一遍后,这个库说白就是基础的事件操作+贝塞尔曲线算法,但是,它内部的代码格式非常清晰,细粒度+代码复用使得维护起来非常方便。
同时可以对贝塞尔曲线有一个更深层的了解(算法还是没法手撕囧),但起码有一个比较完整的思路;
一些可以借鉴的东西:
PointerEvent的优势
canvas+贝塞尔曲线
节流throttle的写法(参考源码src/throttle.ts)
数据结构及实现undo的方案
导图贝塞尔曲线算法资料:
https://medium.com/square-cor...
https://www.lemoda.net/maths/...
源码阅读专栏对一些中小型热门项目进行源码阅读和分析,对其整体做出导图,以便快速了解内部关系及执行顺序。
当前源码(带注释),以及更多源码阅读内容:https://github.com/stonehank/sourcecode-analysis,欢迎fork,求
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/104818.html
摘要:,算法就是这样,那我们基于该算法再对现有代码进行一次升级改造设置线条颜色在原有的基础上,我们创建了一个变量用于保存之前事件中鼠标经过的点,根据该算法可知要绘制二次贝塞尔曲线起码需要个点以上,因此我们只有在中的点数大于时才开始绘制。 背景概要 相信大家平时在学习canvas 或 项目开发中使用canvas的时候应该都遇到过这样的需求:实现一个可以书写的画板小工具。 嗯,相信这对canva...
摘要:,算法就是这样,那我们基于该算法再对现有代码进行一次升级改造设置线条颜色在原有的基础上,我们创建了一个变量用于保存之前事件中鼠标经过的点,根据该算法可知要绘制二次贝塞尔曲线起码需要个点以上,因此我们只有在中的点数大于时才开始绘制。 背景概要 相信大家平时在学习canvas 或 项目开发中使用canvas的时候应该都遇到过这样的需求:实现一个可以书写的画板小工具。 嗯,相信这对canva...
摘要:写在最前本次分享一下在作者上一次失利即拿到毕业证第二天突然收到阿里社招面试通知失败之后,通过分析自己的定位与实际情况,做出的未来一到两年的规划。在博客有一定曝光度的积累中,陆续收到了一些面试邀请,基本上是阿里的但是我知道我菜。。 写在最前 本次分享一下在作者上一次失利即拿到毕业证第二天突然收到阿里社招面试通知失败之后,通过分析自己的定位与实际情况,做出的未来一到两年的规划。以及本次社招...
阅读 2947·2021-09-23 11:32
阅读 2918·2021-09-22 15:12
阅读 1707·2019-08-30 14:07
阅读 3448·2019-08-29 16:59
阅读 1640·2019-08-29 11:11
阅读 2307·2019-08-26 13:50
阅读 2426·2019-08-26 13:49
阅读 2620·2019-08-26 11:49