C# 结合 JavaScript 实现手写板签名并上传到服务器

应用场景

我们最近开发了一款笔迹测试功能的程序(测试版),用户在手写板上手写签名,提交后即可测试出被测试者的心理素质评价分析。类似功能的场景还比如,在银行柜台办理业务,期间可能需要您使用手写设备进行签名并确认;保险续期小程序,到期后需要你在确认续期条款后,在手机上提供的签名区域进行签名并提交确认。

实现效果

笔迹测试显示界面如下:

可选择画笔颜色(默认为黑色笔) ,在虚线框内可随便写一段文字,点击提交即可。当然程序还提供拍照上传功能,这里不再详述。下面我们开始介绍,C#如何结合JavaScript实现手写板写字并上传到服务器进行处理。

开发运行环境

操作系统: Windows Server 2019 DataCenter

手写触屏设备:Microsoft Surface Pro 9

.net版本: .netFramework4.0 或以上

开发工具:VS2019 C#

设计实现

手写功能

设计采用了 iframe 嵌入式的方式实现 JavaScript 前端,假设页面为 hw.aspx ,该页面实现了手写功能、重写功能、画笔选择功能和提交功能,其完整示例代码如下:

<!DOCTYPE html>

<html>

<head>

<meta charset="UTF-8">

<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=yes"/>

<title>手写板</title>

<style type="text/css">

html,body{

margin: 0;

padding: 0;

}

.saveimg{

text-align: center;

}

.saveimgs span{

display: inline-block;

margin-top:5px;

}

</style>

</head>

<body>

<script src="jquery-3.3.1.min.js"></script>

<div align="center">

<canvas id="myCanvas" width="500" height="300" style="border:1px dotted #6699cc"></canvas>

<div class="control-ops control">

<button type="button" class="btn btn-primary" onclick="javascript:clearArea();return false;">重写</button>

<select style="display:none" id="selWidth" onchange="aaa()">

<option value="1">1</option>

<option value="3" selected="selected">3</option>

<option value="5">5</option>

<option value="7">7</option>

<option value="9">9</option>

<option value="11">11</option>

</select>

<select id="selColor" onchange="aaa2()">

<option value="black" selected="selected">黑色笔</option>

<option value="blue">蓝色笔</option>

<option value="red">红色笔</option>

<option value="green">绿色笔</option>

<option value="yellow">黄色笔</option>

<option value="gray">深灰笔</option>

</select>

<button type="button" class="saveimg" onclick="javascript:saveImageInfo();return false;">提交</button>

</div>

<div class="saveimgs"></div>

</div>

</body>

<script type="text/javascript">

var mousePressed = false;

var lastX, lastY;

var ctx = document.getElementById('myCanvas').getContext("2d");

var c = document.getElementById("myCanvas");

var control = document.getElementsByClassName("control")[0];

var saveimgs = document.getElementsByClassName("saveimgs")[0];

window.onload = function () {

document.getElementById('myCanvas').setAttribute("width", $(window).width()-5);

InitThis();

}

function saveImageInfo(){

var image = c.toDataURL("image/png");

window.parent.document.getElementById('pbase64').value = image;

window.parent.document.getElementById('phw').click();

return;

var ctximg = document.createElement("span");

ctximg.innerHTML = "<img src='"+image+"' alt='from canvas'/>";

if(saveimgs.getElementsByTagName('span').length >= 1){

var span_old = saveimgs.getElementsByTagName("span")[0];

saveimgs.replaceChild(ctximg,span_old)

}

else{

saveimgs.appendChild(ctximg);

}

}

var selected1,selected2;

function aaa(){

var sel = document.getElementById('selWidth');

var value = sel.selectedIndex;

return selected1 = sel[value].value;

}

function aaa2(){

var sel2 = document.getElementById('selColor');

var value = sel2.selectedIndex;

return selected2 = sel2[value].value;

}

function InitThis() {

// 触摸屏

c.addEventListener('touchstart', function (event) {

console.log(1)

if (event.targetTouches.length == 1) {

event.preventDefault();// 阻止浏览器默认事件,重要

var touch = event.targetTouches[0];

mousePressed = true;

Draw(touch.pageX - this.offsetLeft, touch.pageY - this.offsetTop, false);

}

},false);

c.addEventListener('touchmove', function (event) {

console.log(2)

if (event.targetTouches.length == 1) {

event.preventDefault();// 阻止浏览器默认事件,重要

var touch = event.targetTouches[0];

if (mousePressed) {

Draw(touch.pageX - this.offsetLeft, touch.pageY - this.offsetTop, true);

}

}

},false);

c.addEventListener('touchend', function (event) {

console.log(3)

if (event.targetTouches.length == 1) {

event.preventDefault();// 阻止浏览器默认事件,防止手写的时候拖动屏幕,重要

mousePressed = false;

}

},false);

/*c.addEventListener('touchcancel', function (event) {

console.log(4)

mousePressed = false;

},false);*/

// 鼠标

c.onmousedown = function (event) {

mousePressed = true;

Draw(event.pageX - this.offsetLeft, event.pageY - this.offsetTop, false);

};

c.onmousemove = function (event) {

if (mousePressed) {

Draw(event.pageX - this.offsetLeft, event.pageY - this.offsetTop, true);

}

};

c.onmouseup = function (event) {

mousePressed = false;

};

}

function Draw(x, y, isDown) {

if (isDown) {

ctx.beginPath();

ctx.strokeStyle = selected2;

ctx.lineWidth = selected1;

ctx.lineJoin = "round";

ctx.moveTo(lastX, lastY);

ctx.lineTo(x, y);

ctx.closePath();

ctx.stroke();

}

lastX = x; lastY = y;

}

function clearArea() {

ctx.setTransform(1, 0, 0, 1, 0, 0);

ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);

// 清除签名图片

if(saveimgs.getElementsByTagName('span').length >= 1){

var clearImg = saveimgs.getElementsByTagName('span')[0];

saveimgs.removeChild(clearImg);

}

}

</script>

</html>

该页面需要引用 jquery-3.3.1.min.js ,可下载我的链接资源:

https://download.csdn.net/download/hefeng_aspnet/92006182

前端引用

前端页面除嵌入手写功能页面外,iframe的父窗口需要放置两个元素,一个用于存储手写提交后的Base64数据的 Asp.net 服务器按钮文本框元素,另一个是用于模拟调用服务器事件的 Asp.net 服务器按钮元素。

引用代码如下:

<div style=" text-align:center">

<iframe width="520" height="350" id="hw" runat="server" scrolling="no" frameborder="0" src="/cc/module/hw/hw.aspx" ></iframe>

<asp:TextBox ID="pbase64" TextMode="MultiLine" style="display:none" runat="server" ></asp:TextBox>

<asp:button ID="phw" OnClientClick="waittip()" text="后台处理" runat="server" style="display:none" onclick="phw_Click" />

</div>

后端处理

手写功能中的提交执行代码将调用如下:

window.parent.document.getElementById('pbase64').value = image;

window.parent.document.getElementById('phw').click();

其中 pbase64 和 phw 控件为服务器控件,可直接模拟调用 phw 按钮的服务器 click,在这之前其还可以自动处理 OnClientClick事件以显示等待界面。请注意 waittip() 执行了一段 javascript 脚本,如下:

function waittip() {

layer.open({ type: 2, shadeClose: false, content: '正在分析,请稍候...' });

}

这其中引入了 Layer 弹出层技术,关于 Layer 弹层组件请参考文章《改造 layer 弹层移动版组件》,下载JS请访问如下链接:
https://download.csdn.net/download/hefeng_aspnet/92006197

这是调用服务器Click的事件处理代码,将上传的Base64图片转为两种格式的图片文件(PNG和JPEG)。代码如下:

protected void phw_Click(object sender, EventArgs e)

{

string mtfilename = "d:\\hw_" + System.Guid.NewGuid().ToString() + ".png";

string mtfilename2 = "d:\\hw_" + System.Guid.NewGuid().ToString() + ".jpg";

string dummyData = pbase64.Text.Trim().Replace("data:image/png;base64,", "");

Base64StringToImage(dummyData, mtfilename);

if (File.Exists(mtfilename)==false)

{

base64.ImageUrl = dummyData;

layer.open("保存手写图片失败,请重试并提交。" , "'确定'", "error");

return;

}

System.Drawing.Image img = System.Drawing.Image.FromFile(mtfilename);

using (var b = new System.Drawing.Bitmap(img.Width, img.Height))

{

b.SetResolution(img.HorizontalResolution, img.VerticalResolution);

using (var g = System.Drawing.Graphics.FromImage(b))

{

g.Clear(System.Drawing.Color.White);

g.DrawImageUnscaled(img, 0, 0);

}

b.Save(mtfilename2, System.Drawing.Imaging.ImageFormat.Jpeg);

}

}

小结

本示例中的前后端代码仅为展示参考,手写功能在支持触屏的设备可以支持手写,也可以用鼠标进行模拟。

服务器调用示例中需要使用 Base64StringToImage(dummyData, mtfilename); 方法由Base64数据转化为图片文件,代码如下:

public bool Base64StringToImage(string strbase64, string outputFilename)

{

byte[] arr = Convert.FromBase64String(strbase64);

MemoryStream ms = new MemoryStream(arr);

System.Drawing.Image img = System.Drawing.Image.FromStream(ms);

img.Save(outputFilename);

img.Dispose();

if (File.Exists(outputFilename))

{

return true;

}

return false;

}

参考文章:https://blog.csdn.net/michaelline/article/details/133691281

https://blog.csdn.net/michaelline/article/details/138212106

如果您喜欢此文章,请收藏、点赞、评论,谢谢,祝您快乐每一天。

相关推荐
追逐时光者2 小时前
一个致力于为 C# 程序员提供更佳的编码体验和效率的 Visual Studio 扩展插件
后端·c#·visual studio
品克缤4 小时前
Element UI MessageBox 增加第三个按钮(DOM Hack 方案)
前端·javascript·vue.js
小沐°4 小时前
vue-设置不同环境的打包和运行
前端·javascript·vue.js
qq_419854055 小时前
CSS动效
前端·javascript·css
SunflowerCoder5 小时前
EF Core + PostgreSQL 配置表设计踩坑记录:从 23505 到 ChangeTracker 冲突
数据库·postgresql·c#·efcore
南村群童欺我老无力.5 小时前
Flutter应用鸿蒙迁移实战:性能优化与渐进式迁移指南
javascript·flutter·ci/cd·华为·性能优化·typescript·harmonyos
花哥码天下5 小时前
恢复网站console.log的脚本
前端·javascript·vue.js
奔跑的呱呱牛6 小时前
geojson-to-wkt 坐标格式转换
javascript·arcgis
康一夏7 小时前
React面试题,封装useEffect
前端·javascript·react.js
阿蒙Amon7 小时前
C#每日面试题-常量和只读变量的区别
java·面试·c#