使用 Canvas 构建签名控件
本文介绍了如何使用 canvas 元素和鼠标事件编写签名控件。
引言
最近,我为客户开发了一个 Windows 8 Metro 应用 POC(概念验证)。POC 中的一个要求是捕获客户使用触摸事件书写的签名。在本文中,我将向您展示如何使用 HTML5 canvas 元素和鼠标事件编写此类签名控件。通过稍作修改,相同的功能也可以在触摸设备上使用。
Canvas 元素基础
canvas 元素是一个绘图表面,可以放置在网页中。它是 HTML5 规范 的一部分,并且在大多数主流网络浏览器中都已实现。canvas 公开了一组 JavaScript API,使开发人员能够绘制基于像素的图形。为了使用 canvas,您首先需要在网页中创建它。以下是声明 canvas 元素的方法:
<canvas id="myCanvas" width="300px" height="300px"></canvas>
现在您已经在网页上有了 canvas,就可以使用其 JavaScript API 来绘制图形。为了绘制,您需要获取一个 canvas context。以下是一个代码示例,演示了如何从 canvas 元素中获取 context:
var canvas = document.getElementById("signatureCanvas"), ctx = canvas.getContext("2d");
在代码中,声明了两个变量:canvas 和 ctx。您可以通过(通常是使用其 id 属性)获取 canvas 元素,然后使用 getContext 函数检索绘图 context。getContext 函数接收一个 context id 参数,该参数可以是 2d 值,如果浏览器支持 webgl,则可以是 experimental-webgl 值。如果您想了解更多关于使用 webgl 创建 3d 图形的内容,可以从此处开始。
获取绘图 context 后,您就可以开始使用 canvas API 进行绘制了。有很多函数,例如 fillRect(绘制带填充颜色的矩形)和 clearRect(清除 canvas 中的矩形区域)。由于本文主要涉及创建包装 canvas 的控件,我鼓励您在继续之前阅读以下文章中关于 canvas 的更多内容:
动态创建控件元素
现在您对 canvas 有了一点了解,让我们开始开发签名控件。首先,您会想要创建 HTML 外观和感觉。为此,您可以使用 document.createElement 函数动态创建元素,并构建控件的表示。以下是我在建议解决方案中使用的代码:
function createControlElements() { var signatureArea = document.createElement("div"), labelDiv = document.createElement("div"), canvasDiv = document.createElement("div"), canvasElement = document.createElement("canvas"), buttonsContainer = document.createElement("div"), buttonClear = document.createElement("button"), buttonAccept = document.createElement("button"); labelDiv.className = "signatureLabel"; labelDiv.textContent = label; canvasElement.id = "signatureCanvas"; canvasElement.clientWidth = cWidth; canvasElement.clientHeight = cHeight; canvasElement.style.border = "solid 2px black"; buttonClear.id = "btnClear"; buttonClear.textContent = "Clear"; buttonAccept.id = "btnAccept"; buttonAccept.textContent = "Accept"; canvasDiv.appendChild(canvasElement); buttonsContainer.appendChild(buttonClear); buttonsContainer.appendChild(buttonAccept); signatureArea.className = "signatureArea"; signatureArea.appendChild(labelDiv); signatureArea.appendChild(canvasDiv); signatureArea.appendChild(buttonsContainer); document.getElementById(containerId).appendChild(signatureArea); }
如您所见,我创建了一些内存中的元素,然后为它们设置了一些属性。之后,我将创建的元素相互追加以构建 HTML 片段,并将该片段连接到一个具有 containerId 的容器元素。
在 Canvas 中实现绘图
现在您已经掌握了这些元素,接下来的任务将是在 canvas 中实现绘图。为此,您需要向 canvas 添加鼠标事件侦听器。最合适的事件是 mousedown 和 mouseup。以下是连接事件的代码:
canvas.addEventListener("mousedown", pointerDown, false); canvas.addEventListener("mouseup", pointerUp, false);
以下是 pointerDown、pointerUp 和 paint 函数的代码:
function pointerDown(evt) { ctx.beginPath(); ctx.moveTo(evt.offsetX, evt.offsetY); canvas.addEventListener("mousemove", paint, false); } function pointerUp(evt) { canvas.removeEventListener("mousemove", paint); paint(evt); } function paint(evt) { ctx.lineTo(evt.offsetX, evt.offsetY); ctx.stroke(); }
在 pointerDown 函数中,您使用 beginPath 函数开始绘制路径。然后,使用事件的 offsetX 和 offsetY 属性将 context 移动到鼠标所在的点。之后,您为 mousemove 事件添加事件侦听器。在鼠标移动时调用的 paint 函数中,您将 context 移动到新点,然后使用 stroke 函数绘制从前一点到当前点的线条。当鼠标按钮释放时,会调用 pointerUp 函数。在 pointerUp 函数中,您绘制最后的线条到终点,并移除 mousemove 事件侦听器的事件侦听器。移除 mousemove 事件侦听器将阻止在鼠标悬停在 canvas 元素上时继续绘图。
将签名图像数据获取为字节数组
一旦在 canvas 上绘制了签名,您可能需要提取它。这可以通过使用 context 的 getImageData 来完成,该函数返回在 canvas 上绘制的数据。函数调用的返回类型有一个 data 属性,其中包含一个表示 canvas 像素的字节数组。以下函数可以帮助检索签名:
function getSignatureImage() { return ctx.getImageData(0, 0, canvas.width, canvas.height).data; }
整个控件实现
让我们将所有前面的函数包装成一个 JavaScript 控件。以下是控件的实现:
(function (ns) { "use strict"; ns.SignatureControl = function (options) { var containerId = options && options.canvasId || "container", callback = options && options.callback || {}, label = options && options.label || "Signature", cWidth = options && options.width || "300px", cHeight = options && options.height || "300px", btnClearId, btnAcceptId, canvas, ctx; function initCotnrol() { createControlElements(); wireButtonEvents(); canvas = document.getElementById("signatureCanvas"); canvas.addEventListener("mousedown", pointerDown, false); canvas.addEventListener("mouseup", pointerUp, false); ctx = canvas.getContext("2d"); } function createControlElements() { var signatureArea = document.createElement("div"), labelDiv = document.createElement("div"), canvasDiv = document.createElement("div"), canvasElement = document.createElement("canvas"), buttonsContainer = document.createElement("div"), buttonClear = document.createElement("button"), buttonAccept = document.createElement("button"); labelDiv.className = "signatureLabel"; labelDiv.textContent = label; canvasElement.id = "signatureCanvas"; canvasElement.clientWidth = cWidth; canvasElement.clientHeight = cHeight; canvasElement.style.border = "solid 2px black"; buttonClear.id = "btnClear"; buttonClear.textContent = "Clear"; buttonAccept.id = "btnAccept"; buttonAccept.textContent = "Accept"; canvasDiv.appendChild(canvasElement); buttonsContainer.appendChild(buttonClear); buttonsContainer.appendChild(buttonAccept); signatureArea.className = "signatureArea"; signatureArea.appendChild(labelDiv); signatureArea.appendChild(canvasDiv); signatureArea.appendChild(buttonsContainer); document.getElementById(containerId).appendChild(signatureArea); } function pointerDown(evt) { ctx.beginPath(); ctx.moveTo(evt.offsetX, evt.offsetY); canvas.addEventListener("mousemove", paint, false); } function pointerUp(evt) { canvas.removeEventListener("mousemove", paint); paint(evt); } function paint(evt) { ctx.lineTo(evt.offsetX, evt.offsetY); ctx.stroke(); } function wireButtonEvents() { var btnClear = document.getElementById("btnClear"), btnAccept = document.getElementById("btnAccept"); btnClear.addEventListener("click", function () { ctx.clearRect(0, 0, canvas.width, canvas.height); }, false); btnAccept.addEventListener("click", function () { callback(); }, false); } function getSignatureImage() { return ctx.getImageData(0, 0, canvas.width, canvas.height).data; } return { init: initCotnrol, getSignatureImage: getSignatureImage }; } })(this.ns = this.ns || {});
首先,您使用 JavaScript 命名空间为控件创建一个作用域。在命名空间中,您声明 SignatureControl 的构造函数。该控件可以接收一组选项,这些选项有助于配置其外观和行为。例如,当您单击接受按钮时,会调用 callback 选项。该控件将公开两个函数:init 和 getSignatureImage。init 函数将负责初始化所有元素,为控件的按钮连接事件侦听器,并连接鼠标事件的事件侦听器。getSignatureImage 函数将负责检索签名字节数组。
在 HTML 页面中使用控件
控件创建完成后,让我们看看如何在网页中使用它。以下网页展示了如何使用该控件:
<!doctype html> <html> <head> <title>Signature</title> <link href="signature.css" rel="stylesheet" type="text/css" /> <script type="text/javascript" src="signature.js"></script> <script type="text/javascript"> function loaded() { var signature = new ns.SignatureControl({ containerId: 'container', callback: function () { alert('hello'); } }); signature.init(); } window.addEventListener('DOMContentLoaded', loaded, false); </script> </head> <body> <div id="container"> </div> </body> </html>
当 DOM 内容加载完成后,您使用其构造函数和一些选项创建一个签名对象。然后,您只需调用 init 函数来创建控件并启用其功能。如果您想检索签名,可以使用以下代码:
var signatureByteArray = signature.getSignatureImage();
这是控件实际运行的屏幕截图:
摘要
本文向您展示了如何创建一个捕获签名的控件。为了在 Windows 8 触摸事件中使用相同的功能,您只需将对鼠标事件的调用替换为相应的触摸事件(例如,mouseup 将变成 MSPointerUp)。