使用 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)。




