65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 Canvas 构建签名控件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.97/5 (10投票s)

2012年8月1日

CPOL

5分钟阅读

viewsIcon

111966

downloadIcon

5680

本文介绍了如何使用 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");   

在代码中,声明了两个变量:canvasctx。您可以通过(通常是使用其 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 添加鼠标事件侦听器。最合适的事件是 mousedownmouseup。以下是连接事件的代码:

canvas.addEventListener("mousedown", pointerDown, false);
canvas.addEventListener("mouseup", pointerUp, false);

以下是 pointerDownpointerUppaint 函数的代码:

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 函数开始绘制路径。然后,使用事件的 offsetXoffsetY 属性将 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 选项。该控件将公开两个函数:initgetSignatureImageinit 函数将负责初始化所有元素,为控件的按钮连接事件侦听器,并连接鼠标事件的事件侦听器。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();

这是控件实际运行的屏幕截图:

The Signature Control

摘要

本文向您展示了如何创建一个捕获签名的控件。为了在 Windows 8 触摸事件中使用相同的功能,您只需将对鼠标事件的调用替换为相应的触摸事件(例如,mouseup 将变成 MSPointerUp)。

© . All rights reserved.