灵感来源
之前, 在轻流的业务中遇到了一个需求, 是能够让客户使用手写签名的功能.

问题来了, 这...我不会啊! 这得是Canvas了吧. 正所谓, 插件用的好, 下班走的早. 于是我就开始找插件了. 找到了一个ng生态的插件, 名字不记得了, 只记得他就一个核心文件, 封装了一个第三方插件. 没有提供任何原创的方法, 都是直接把第三方签名插件所提供的方法emit了出来. 以下就是他import的第三方插件.
https://github.com/szimek/signature_padgithub.com那么问题来了, 那我要你干嘛? 我为啥不自己封装一个呢? 因为业务比较紧, 没有那么多时间去思考如何封装地更好. 所以直接在轻流中封装了一个更加适合轻流业务的签名模块. 比如说, 全屏签名

在完成业务后, 我使用angular library封装了属于自己的第三方插件.
https://github.com/eve-sama/ngx-signature-padgithub.com那么我的插件有什么特别之处呢? of course~ 因为第三方插件我认为有些地方的设计不是很合理, 并且没有一些我想要的API.
注意: 为了方便描述和对比, 在下文中, 第三方插件我将称之为sp(signature pad的缩写), 我的插件将称之为ngx-sp.
一. API风格更加ng化
现在让你来设计这个签名组件, 要求实现当用户签字的时候通知父组件一个签名事件. 当结束签字的时候需要通知父组件一个结束事件. 我们先看看sp是如何处理的

sp是通过一个对象的形式传递参数, 包括事件也是通过回调函数的方法. 如果是我我就不会这么设计. 我觉得ng社区的同僚应该下意识都是像下面这样设计API的
<
二. 只有小屏签名
其实全屏签名是个非常常见的场景, 但是sp并没有提供. 其实这个不能说是sp的问题. 因为他提供了一块儿签名的canvas, 理论上你就可以在这个基础上开发出各种姿势的签名. 只是我比较懒, 我希望他能提供类似如下的代码
sp.fullScreen();
遗憾的是他没有, 在全屏相关的需求中, Angular CDK是个非常好的工具, 其实可以结合CDK轻而易举地实现这样的需求. 所以, sp没有, 那ngx-sp就来做.

需要注意一点, 大家想象下, 你比如初始化了一个签名组件, 尺寸是300*150, 在上面画了几笔. 大概这个样子

当调用fullScrren()的时候, 签名组件会展示全屏化, 请问, 已经签过的内容该如何处理呢? 显然是要放大的吧. 但是小屏签名的宽高是由用户自定义的, 那么全屏的话, 假如ngx-sp真的100%的宽*100%的高, 不一定和用户设定的比例相同.
比例不同造成的后果, 就是已签过的内容会被拉宽或者拉高. 所以, 我的设定就是全屏的宽高比与小屏的宽高比是保持一致的.

顺便一提, 之所以我执意要把全屏的API封装进ngx-sp, 是因为全屏要做的事儿比较恶心, 我不想在业务层做这种事儿. 这几十行倒也算不上很难, 就是对canvas不熟悉、数学又差的一逼的人, 比如我, 比较折腾.

现在好了, 你只需要一个xxx.fullScreen()就可以直接全屏化, 那叫一个美汁儿美汁儿~
三. 只能判断是否被签过, 不允许更改状态
在sp中, 是这样判断是否被签名过的
// Returns true if canvas is empty, otherwise returns false
但是并没有提供手动更改签名状态的方法. 为什么需要这样的函数呢?
比如说前文的全屏签名, 大家注意看这个操作过程
- 先显示小屏签名, 允许用户签名
- 当点击放大图标时, 全屏覆盖签名
- 再点击缩小图标时, 显示小屏签名
我的思路, 其实是创建2个canvas实例. 一个用于小屏, 一个用于全屏(使用CDK包装). 当用户切换模式的时候, 就会把原模式的内容copy到新模式的canvas上. 但是因为sp没有提供手动更改签名状态的方法, 就会导致一个问题.
当ngx-sp把小屏签名的内容copy到全屏签名上, 此时调用isEmpty()方法, 一定是true(也就是显示你从未签名过, 从逻辑上看, 这就是bug了). 因为你全屏签名的内容并不是鼠标绘制出来的. 而是通过canvas相关的方法copy的.
查看sp的源码, 会发现相关属性是private

问题不大, 在轻流的业务模块中, 我就强行把_isEmpty给改了. 反正private在run time时被更改, 浏览器也拦不住我.
在ngx-sp中则对外提供了手动更改状态的函数

当然了, 在ngx-sp中没有使用强行更改private这种野路子的方法. 那么我是怎么做的呢? 核心思路其实就是我自己维护了这个变量.
下面这段代码, 简单来说就是在initSmallPad()中会根据开发者传递的参数(options), 初始化sp. sp对外提供了一个onBegin的方法, 当他触发的时候, 我就会手动把_isEmpty标记为false. 既然这个变量由我自己来维护, 那么我就可以任意更改了.

所以, 同样对外暴露了isEmpty()的方法, 但是我的判断依据并不是根据sp源码里的变量, 也不是直接调用的sp的isEmpty(), 而是我自己维护的私有变量.
四. 自由度不够
sp并没有对外暴露canvas实例相关的属性. 这是一个很麻烦的事儿. 为什么呢, 给不了解canvas的同学科普一个概念. canvas是允许通过dragImage方法把图片等资源画在上面的, 如下图.

我常年写的字儿自己都不认识. 上图的签名显然不是我手画的. 其实'前夕'那2个字是个图片.

在ngx-sp中很容易做到这样的事儿.
this.signature.getContext().drawImage(this.image, 230, 35, 100, 50, 230, 110, 100, 50);
this.signature.setDirty();
举个例子, 假如你的产品经理希望你在签名区域+个背景图, 类似下面这样. 在sp中确实没法实现, 因为sp只允许你设置背景颜色, 而且是纯色.

而如果在ngx-sp中, 通过获取canvas相关实例, 你可以想怎么画就怎么画.
总结
以上就是对ngx-signature-pad的介绍了, 更多介绍请查看Repo. 我也只是站在巨人的肩膀上做了一点点扩展, 希望能帮助到有需要的人~!
Repo地址
https://github.com/eve-sama/ngx-signature-padgithub.comdemo页, 如果使用浏览器的PC模式访问则侧重文档, Mobile模式则侧重demo.
NgxSignaturePadeve-sama.github.io