如何用canvas实现在线签名的示例代码


Posted in HTML / CSS onJuly 10, 2018

随着计算机和网络技术的飞速发展,在线签名技术越来越多的被应用在无纸化办公中,这种直观便利的操作不仅可以大幅提升办公效率,而且使用数字化存储方式,避开了传统的纸质签字存储查阅困难等问题。在我们在日常生活中,已经有很多场景使用在线签名技术,例如:pos机刷卡签字、快递签收签字、银行或机关单位业务办理签字等。最近在做公司的业务办理需求,里面也涉及到在线签名,我们采用的 Canvas 技术实现,接下来,让我们来聊聊如何使用 Canvas 实现在线签名吧!

什么是 Canvas?

Canvas 是 HTML5 新增的元素,用于在网页上绘制图形,它由 Apple 在 Safari 1.3 Web 浏览器中引入,之所以对 HTML 扩展的原因在于, HTML 在 Safari 中的绘图能力能为 Mac OS X 桌面的 Dashboard 组件所使用,并且 Apple 也希望有一种方式可以在 Dashboard 中支持脚本化的图形。Firefox 1.5 和 Opera 9 这两个浏览器也紧随 Safari 的引领,开始支持 Canvas 。

现在,Canvas 标签已经是 HTML5 最伟大的改进之一,因为它可以让我们在不使用图片的情况下实现网页的图形设计。它就像一块画布,本身没有绘制能力,但却把绘制 API 展现给客户端 JavaScript,我们借助 JavaScript 的支持,在画布范围内尽情发挥,达到想要的效果。

技术选型

这个功能无论是 Canvas、SVG 或是 Flash,都可以实现,但是我们为什么选择了 Canvas 呢?

首先,由于功能上我们需要支持移动平台,所以 Flash 我们就可以直接弃掉,它在移动端方面并没有得到友好的支持,但 Canvas 和 SVG 都具有很好的跨平台能力,我们如何抉择,下面我们来对比一下。

  •  Canvas 基于像素,提供 2D 绘制函数,提供的功能更原始,适合像素处理、动态渲染和大数据量绘制,可控性高,绘制完了基本不记录过程,绘图性能会更好一点,各大厂商也早都实现了canvas的硬件加速机制。  
  • SVG 为矢量,提供一系列图形元素,功能更完善,建立了一大堆可交互对象,本性长于交互,但性能会弱些,更适合静态图片展示,高保真文档查看和打印的应用场景。

两者各有自己的擅长领域, 基于以上,我们选择了 Canvas 来实现签字功能。

下面,我们来看一下实现效果。

如何用canvas实现在线签名的示例代码

了解了 Canvas 来源、技术选型和最终呈现效果,接下来、我们会从创建、绘制、监听、重绘、图片处理等五部分进行撰写,让我们一起走进 Canvas 绘制的世界。

创建画布

首先,我们需要判断浏览器是否支持 Canvas :

isCanvasSupported = (): boolean => {
    let elem = document.createElement('canvas');
    return !!(elem.getContext && elem.getContext('2d'));
}

然后根据判断结果选择创建 Canvas 画布还是展示提示

{isCanvasSupported ?
     <canvas ref={canvas => (this.canvas = canvas)} height={canvasHeight} width={canvasWidth}>
    :对不起,当前浏览器暂不支持此功能!
}

我们知道,每个 Canvas 节点都有一个对应的 context 对象, 我们可以通过 Canvas 对象的 getContext() 方法,直接把量字符串 “2d” 作为唯一的参数传递给它来获取。接下来,我们通过 ref 获取 Canvas 元素,再通过 getContext() 方法得到一个画布上绘图的环境。

let cxt = this.canvas.getContext('2d');
this.setState({cxt: cxt});

环境已经准备妥当,接下来我们就开始进行绘制工作吧!

绘制

首先绘制开始路径:

cxt.beginPath();

然后设置当前线条的宽度:

cxt.lineWidth = 5;

设置线条的颜色:

cxt.strokeStyle = '#000';

通过 moveTo 和 lineTo ,我们来绘制一条线

cxt.moveTo(0,0);
cxt.lineTo(150,0);
// 绘制已定义的路径
cxt.stroke()

如何用canvas实现在线签名的示例代码

但是,我们发现绘制的线条比较生硬

这时,我们可以通过 lineCap 改变线条末端线帽的样式,为每个末端添加圆形线帽,减少线条的生硬感

cxt.lineCap = 'round';

如何用canvas实现在线签名的示例代码

同时,我们还可以通过设置 lineJoin,指定条线交汇时为圆形边角

cxt.lineJoin = 'round';

如何用canvas实现在线签名的示例代码如何用canvas实现在线签名的示例代码

但我们又发现,绘制的线条有明显的锯齿,此时我们就需要借助 Canvas 为我们提供的绘制元素阴影的功能来模糊边缘出现的锯齿,因为有阴影,所以我们可以适当改变 lineWidth 值

cxt.shadowBlur = 1;
cxt.shadowColor = '#000';

如何用canvas实现在线签名的示例代码

是不是变得圆润很多,到这里,我们绘制线路的方法已经准备完事,接下来我们来看一下怎么监听画布事件来实现连贯执行绘制吧!

监听画布事件

因为我们需要同时兼容 PC 端和移动端,所以我们需要事先需要判断一下对应执行的事件

this.state = {
events: ('ontouchstart' in window) ? ['touchstart', 'touchmove', 'touchend'] : ['mousedown', 'mousemove', 'mouseup']
}

在画布初始化之后,我们开始监听 events[0] 事件

this.canvas.addEventListener(this.events[0], startEventHandler, false);

在startEventHandler函数中监听 events[1] 和 events[2] 事件

this.canvas.addEventListener(events[1], moveEventHandler, false);
this.canvas.addEventListener(events[2], endEventHandler, false);

重点来了,我们核心的内容就是计算、描绘划过的路径

moveEventHandler(event: any): void {
    event.preventDefault();
    const {ctx, isSupportTouch} = this.state;
    const evt = isSupportTouch ? event.touches[0] : event;
    const coverPos = this.canvas.getBoundingClientRect();
    const mouseX = evt.clientX - coverPos.left;
    const mouseY = evt.clientY - coverPos.top;
    cxt.lineTo(
       mouseX,
       mouseY
    );
    cxt.stroke();
}

了解 Canvas 的知道, Canvas 画布为我们提供了一个用来作图的平面空间,该空间的每个点都有自己的坐标,x 表示横坐标,y 表示竖坐标。原点 (0, 0) 位于图像左上角,x 轴的正向是原点向右,y 轴的正向是原点向下。

于是我们通过 getBoundingClientRect() 方法获得页面 Canvas 元素相对浏览器视窗的位置左边和顶部的像素距离,再利用 clientX,clientY 事件属性返回当事件被触发时鼠标指针向对于浏览器页面的水平和垂直坐标,最后通过 lineTo 和 stroke 来绘制路径。

同时,我们要记得在 events[2] 事件执行之后,移除 events[1]、events[2] 事件,否则会造成一直绘制。

endEventHandler(event: any): void {
    event.preventDefault();
    const {events, moveEventHandler, endEventHandler} = this.state;
    this.canvas.removeEventListener(events[1], moveEventHandler, false);
    this.canvas.removeEventListener(events[2], endEventHandler, false);
}

如此反复循环上述事件操作,我们的签字功能就基本实现了。

重新绘制

签字过程中,签错或是签的过于潦草是必不可免的,所以我们需要支持清空签字的功能,这时,我们利用 Canvas 的 clearRect() 方法就可以帮助我们清除画布区域内容。

cxt.clearRect(0, 0, canvasWidth, canvasHeight);

图片处理

绘制之后我们还没完事,我们还需要把绘制的签名上传保存。这时,我们可以利用 toDataURL() 方法将 Canvas 转化成一般的图像文件形式。

通常我们直接执行以操作就能转化成 data URI,然后再利用 ajax 请求上传就完事了。

dataurl = this.canvas.toDataURL('image/png');
//or
dataurl = this.canvas.toDataURL('image/jpeg', 0.8);

但是,由于各种业务需求,我们有时需要携带页面其他内容,这时,我们可以借助 html2canvas 来实现。html2canvas 可以帮助我们对浏览器端整个或部分页面进行截屏,并渲染成一个 Canvas ,然后我们在利用 toDataURL() 方法进行处理。

说道 html2canvas,顺便给大家一个绕坑提示,它在一些低版本浏览器截出来的图片是空白的,原因是使用了flex 布局,而html2canvas并不支持 -webkit-flex 或 -webkit-box,所以无法将 HTML 生成 Canvas,从而导致了截出一张白屏。

解决办法:

  • 不使用flex布局
  • 修改 html2canvas 源码,在html2canvas\dist\npm\parsing\display.js 文件中增加 -webkit-flex 和 -webkit-box 也返回 DISPLAY.FLEX; 小结

通过以上几步,我们就基本实现了在线签名的功能。值得注意的是,这个项目我们使用的 React+TypeScript 环境构建,上述代码的实际使用需要结合自己环境进行适当修改。

文中使用的是 Canvas 比较浅层的绘制知识,如果想利用Canvas进动画制作、物理效果模拟、碰撞检测、游戏开发、移动应用开发、大数据可视化开发,还需要我们复习一下之前学过的数学几何、物理方面的知识,然后在慢慢摸索。现在很多成熟的图表插件都是用 Canvas 实现的,例如 Chart.js、ECharts等,里面很多好看炫酷的图表,几乎覆盖了所有图表的实现。Canvas还有很多开源类库,例如 ZRender、createJS、Pixi.js等,ECharts底层也是依赖轻量级的 Canvas 类库 ZRender 进行封装的。

好了,我们今天就先聊到这里,如有任何疑问,还请留言。以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。 

HTML / CSS 相关文章推荐
css3和jquery实现自定义checkbox和radiobox组件
Apr 22 HTML / CSS
CSS3+js实现简单的时钟特效
Mar 18 HTML / CSS
CSS3改变浏览器滚动条样式
Jan 04 HTML / CSS
Html5新特性用canvas标签画多条直线附效果截图
Jun 30 HTML / CSS
HTML5几个设计和修改的页面范例分享
Sep 29 HTML / CSS
HTML5 Canvas 实现圆形进度条并显示数字百分比效果示例
Aug 18 HTML / CSS
iphoneX 适配客户端H5页面的方法教程
Dec 08 HTML / CSS
Canvas globalCompositeOperation
Dec 18 HTML / CSS
关于webview适配H5上传照片或者视频文件的方法
Nov 04 HTML / CSS
HTML5 canvas实现的静态循环滚动播放弹幕
Jan 05 HTML / CSS
html2 canvas svg不能识别的解决方案
Jun 03 HTML / CSS
html中显示特殊符号(附带特殊字符对应表)
Jun 21 HTML / CSS
canvas简易绘图的实现(海绵宝宝篇)
Jul 04 #HTML / CSS
快速实现一个简单的canvas迷宫游戏的示例
Jul 04 #HTML / CSS
HTML5中form如何关闭自动完成功能的方法
Jul 02 #HTML / CSS
Canvas实现保存图片到本地的示例代码
Jun 28 #HTML / CSS
html5手机端页面可以向右滑动导致样式受影响的问题
Jun 20 #HTML / CSS
canvas环形倒计时组件的示例代码
Jun 14 #HTML / CSS
canvas里面如何基于随机点绘制一个多边形的方法
Jun 13 #HTML / CSS
You might like
如何提高MYSQL数据库的查询统计速度 select 索引应用
2007/04/11 PHP
使用zend studio for eclipse不能激活代码提示功能的解决办法
2009/10/11 PHP
PHP模板引擎Smarty内建函数foreach,foreachelse用法分析
2016/04/11 PHP
mac系统下安装多个php并自由切换的方法详解
2017/04/21 PHP
动态调用css文件——jquery的应用
2007/02/20 Javascript
javascript学习笔记(十六) 系统对话框(alert、confirm、prompt)
2012/06/20 Javascript
解析ScrollPic在ie8下只滚动一遍,然后变为空白 ie6,ie7,chrome,firefox正常
2013/06/26 Javascript
js原生appendChild的bug解决心得分享
2013/07/01 Javascript
js调用iframe实现打印页面内容的方法
2014/03/04 Javascript
在Firefox下js select标签点击无法弹出
2014/03/06 Javascript
Javascript的严格模式strict mode详细介绍
2014/06/06 Javascript
深入理解javascript构造函数和原型对象
2014/09/23 Javascript
JQuery中DOM实现事件移除的方法
2015/06/13 Javascript
原生JS实现图片翻书效果
2017/02/16 Javascript
在vue中使用公共过滤器filter的方法
2018/06/26 Javascript
jquery拖拽自动排序插件使用方法详解
2020/07/20 jQuery
对layui中table组件工具栏的使用详解
2019/09/19 Javascript
Javascript实现简易天数计算器
2020/05/18 Javascript
[02:40]2014DOTA2 国际邀请赛中国区预选赛 四大豪门抵达华西村
2014/05/23 DOTA
[01:07:17]EG vs Optic Supermajor 败者组 BO3 第一场 6.6
2018/06/07 DOTA
[01:02:05]LGD vs Mineski 2018国际邀请赛小组赛BO2 第一场 8.19
2018/08/21 DOTA
Python实现简单的代理服务器
2015/07/25 Python
简单实现Python爬取网络图片
2018/04/01 Python
matplotlib 输出保存指定尺寸的图片方法
2018/05/24 Python
解决Python2.7中IDLE启动没有反应的问题
2018/11/30 Python
Python 用turtle实现用正方形画圆的例子
2019/11/21 Python
django 中使用DateTime常用的时间查询方式
2019/12/03 Python
详解Pycharm第三方库的安装及使用方法
2020/12/29 Python
CSS3 真的会替代 SCSS 吗
2021/03/09 HTML / CSS
Farfetch中文官网:奢侈品牌时尚购物平台
2020/03/15 全球购物
保密工作实施方案
2014/02/24 职场文书
车间机修工岗位职责
2014/02/28 职场文书
学雷锋活动总结范文
2014/04/25 职场文书
教师评语大全
2014/04/28 职场文书
个人求职意向书
2015/05/11 职场文书
七年级作文之游记
2019/12/11 职场文书