Blazor组件自做四 : 使用JS隔离封装signature_pad签名组件
运行截图
感谢szimek写的棒棒的signature_pad.js项目, 来源: https://github.com/szimek/signature_pad
正式开始
1. 在文件夹wwwroot/lib,添加signature_pad子文件夹,里面下载库文件(文件文末源码里可复制) signature_pad.umd.js复制到此文件夹. 最终版本参考如下
+signature_pad
|-signature_pad.umd.js
2. 添加app.js文件
+signature_pad
|-app.js
代码里 wrapperc.invokeMethodAsync("signatureResult", imgBase64) 为签名canvas结果回调到c#
js代码
import '/lib/signature_pad/signature_pad.umd.js';
export function init(wrapperc, element, alertText,) {
//Code modify from https://github.com/szimek/signature_pad
var wrapper = element;//document.getElementById("signature-pad");
var clearButton = wrapper.querySelector("[data-action=clear]");
var changeColorButton = wrapper.querySelector("[data-action=change-color]");
var undoButton = wrapper.querySelector("[data-action=undo]");
var saveBase64Button = wrapper.querySelector("[data-action=save-base64]");
var savePNGButton = wrapper.querySelector("[data-action=save-png]");
var saveJPGButton = wrapper.querySelector("[data-action=save-jpg]");
var saveSVGButton = wrapper.querySelector("[data-action=save-svg]");
var canvas = wrapper.querySelector("canvas");
var signaturePad = new SignaturePad(canvas, {
// It's Necessary to use an opaque color when saving image as JPEG;
// this option can be omitted if only saving as PNG or SVG
backgroundColor: 'rgb(255, 255, 255)'
});
// Adjust canvas coordinate space taking into account pixel ratio,
// to make it look crisp on mobile devices.
// This also causes canvas to be cleared.
function resizeCanvas() {
// When zoomed out to less than 100%, for some very strange reason,
// some browsers report devicePixelRatio as less than 1
// and only part of the canvas is cleared then.
var ratio = Math.max(window.devicePixelRatio || 1, 1);
// This part causes the canvas to be cleared
canvas.width = canvas.offsetWidth * ratio;
canvas.height = canvas.offsetHeight * ratio;
canvas.getContext("2d").scale(ratio, ratio);
// This library does not listen for canvas changes, so after the canvas is automatically
// cleared by the browser, SignaturePad#isEmpty might still return false, even though the
// canvas looks empty, because the internal data of this library wasn't cleared. To make sure
// that the state of this library is consistent with visual state of the canvas, you
// have to clear it manually.
signaturePad.clear();
}
// On mobile devices it might make more sense to listen to orientation change,
// rather than window resize events.
window.onresize = resizeCanvas;
resizeCanvas();
function download(dataURL, filename) {
if (navigator.userAgent.indexOf("Safari") > -1 && navigator.userAgent.indexOf("Chrome") === -1) {
window.open(dataURL);
} else {
var blob = dataURLToBlob(dataURL);
var url = window.URL.createObjectURL(blob);
var a = document.createElement("a");
a.style = "display: none";
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
}
}
// One could simply use Canvas#toBlob method instead, but it's just to show
// that it can be done using result of SignaturePad#toDataURL.
function dataURLToBlob(dataURL) {
// Code taken from https://github.com/ebidel/filer.js
var parts = dataURL.split(';base64,');
var contentType = parts[0].split(":")[1];
var raw = window.atob(parts[1]);
var rawLength = raw.length;
var uInt8Array = new Uint8Array(rawLength);
for (var i = 0; i < rawLength; ++i) {
uInt8Array[i] = raw.charCodeAt(i);
}
return new Blob([uInt8Array], { type: contentType });
}
if (clearButton) clearButton.addEventListener("click", function (event) {
signaturePad.clear();
return wrapperc.invokeMethodAsync("signatureResult", null);
});
if (undoButton) undoButton.addEventListener("click", function (event) {
var data = signaturePad.toData();
if (data) {
data.pop(); // remove the last dot or line
signaturePad.fromData(data);
}
});
if (changeColorButton) changeColorButton.addEventListener("click", function (event) {
var r = Math.round(Math.random() * 255);
var g = Math.round(Math.random() * 255);
var b = Math.round(Math.random() * 255);
var color = "rgb(" + r + "," + g + "," + b + ")";
signaturePad.penColor = color;
});
if (saveBase64Button) saveBase64Button.addEventListener("click", function (event) {
if (signaturePad.isEmpty()) {
alertMessage();
} else {
var imgBase64 = signaturePad.toDataURL("image/jpeg");
//console.log(imgBase64);
return wrapperc.invokeMethodAsync("signatureResult", imgBase64);
}
});
if (savePNGButton) savePNGButton.addEventListener("click", function (event) {
if (signaturePad.isEmpty()) {
alertMessage();
} else {
var dataURL = signaturePad.toDataURL();
download(dataURL, "signature.png");
}
});
if (saveJPGButton) saveJPGButton.addEventListener("click", function (event) {
if (signaturePad.isEmpty()) {
alertMessage();
} else {
var dataURL = signaturePad.toDataURL("image/jpeg");
download(dataURL, "signature.jpg");
}
});
if (saveSVGButton) saveSVGButton.addEventListener("click", function (event) {
if (signaturePad.isEmpty()) {
alertMessage();
} else {
var dataURL = signaturePad.toDataURL('image/svg+xml');
download(dataURL, "signature.svg");
}
});
function alertMessage() {
if (alertText) alert(alertText);
wrapperc.invokeMethodAsync("signatureAlert");
}
}
3. 打开Components文件夹 , 新建SignaturePad.razor.css文件
css代码
*,
*::before,
*::after {
box-sizing: border-box;
}
.signature-pad-body {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
height: 400px;
width: 100%;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
margin: 0;
padding: 32px 16px;
font-family: Helvetica, Sans-Serif;
}
.signature-pad {
position: relative;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-ms-flex-direction: column;
flex-direction: column;
font-size: 10px;
width: 100%;
height: 100%;
max-width: 650px;
max-height: 400px;
border: 1px solid #e8e8e8;
background-color: #fff;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.27), 0 0 40px rgba(0, 0, 0, 0.08) inset;
border-radius: 4px;
padding: 16px;
}
.signature-pad::before,
.signature-pad::after {
position: absolute;
z-index: -1;
content: "";
width: 40%;
height: 10px;
bottom: 10px;
background: transparent;
box-shadow: 0 8px 12px rgba(0, 0, 0, 0.4);
}
.signature-pad::before {
left: 20px;
-webkit-transform: skew(-3deg) rotate(-3deg);
transform: skew(-3deg) rotate(-3deg);
}
.signature-pad::after {
right: 20px;
-webkit-transform: skew(3deg) rotate(3deg);
transform: skew(3deg) rotate(3deg);
}
.signature-pad--body {
position: relative;
-webkit-box-flex: 1;
-ms-flex: 1;
flex: 1;
border: 1px solid #f4f4f4;
}
.signature-pad--body
canvas {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
border-radius: 4px;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.02) inset;
}
.signature-pad--footer {
color: #C3C3C3;
text-align: center;
font-size: 1.2em;
margin-top: 8px;
}
.signature-pad--actions {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: justify;
-ms-flex-pack: justify;
justify-content: space-between;
margin-top: 8px;
}
#github img {
border: 0;
}
@media (max-width: 940px) {
#github img {
width: 90px;
height: 90px;
}
}
4. 打开Components文件夹 , 新建SignaturePad.razor组件
4.1 组件参数
在 ASP.NET Web Forms 中,可以使用公共属性将参数和数据传递到控件。 这些属性可以使用特性在标记中进行设置,也可以直接在代码中设置。 Razor 组件以类似的方式工作,尽管组件属性还必须使用 [Parameter] 特性进行标记才能被视为组件参数。
以下 Counter 组件定义名为 IncrementAmount 的组件参数,该参数可用于指定每次单击按钮时 Counter 应该递增的数量。
razor
<h1>Counter</h1>
<p>Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
int currentCount = 0;
[Parameter]
public int IncrementAmount { get; set; } = 1;
void IncrementCount()
{
currentCount+=IncrementAmount;
}
}
若要在 Blazor 中指定组件参数,请像在 ASP.NET Web Forms 中一样使用特性:
razor
<Counter IncrementAmount="10" />
4.2 C#组件参数实例
定义名为 SaveBase64BtnTitle 的组件参数,该参数可用于设置或者获取 [保存为base64]按钮的文本。
定义名为 OnResult 的组件参数,该参数可用于手写签名结果回调。
/// <summary>
/// 保存为base64按钮文本/Save as Base64 button title
/// </summary>
[Parameter]
public string SaveBase64BtnTitle { get; set; } = "确定";
/// <summary>
/// 手写签名结果回调/SignaturePad result callback method
/// </summary>
[Parameter]
public EventCallback<string> OnResult { get; set; }
4.3 在 Blazor 调用组件页面中指定组件参数
仅获取手写签名结果回调
<SignaturePad OnResult="((e) => Result=e)" />
@code{
public string? Result { get; set; }
}
自定义按钮文本
<SignaturePad OnResult="((e) => Result=e)" SaveBase64BtnTitle="完成"/>
<SignaturePad OnResult="((e) => Result=e)" SaveBase64BtnTitle="OK" ClearBtnTitle="Clear"/>
<SignaturePad OnResult="((e) => Result=e)"
SignAboveLabel="Sign above"
UndoBtnTitle="Undo"
SaveBase64BtnTitle="OK"
ChangeColorBtnTitle="Change color"
ClearBtnTitle="Clear" />
@code{
public string? Result { get; set; }
}
自定义按钮css
<SignaturePad OnResult="((e) => Result=e)" BtnCssClass="btn btn-outline-success"/>
@code{
public string? Result { get; set; }
}
4.4 完整代码
razor代码
@implements IAsyncDisposable
@namespace Blazor100.Components
@inject IJSRuntime JS
<div class="signature-pad-body">
<div @ref="SignaturepadElement" class="signature-pad">
<div class="signature-pad--body">
<canvas width="614" style="touch-action: none; user-select: none;" height="242"></canvas>
</div>
<div class="signature-pad--footer">
<div class="description">@SignAboveLabel</div>
<div class="signature-pad--actions">
<div>
<button type="button" class="@BtnCssClass" data-action="clear">@ClearBtnTitle</button>
@if (EnableChangeColorBtn)
{
<button type="button" class="@BtnCssClass" data-action="change-color">@ChangeColorBtnTitle</button>
}
<button type="button" class="@BtnCssClass" data-action="undo">@UndoBtnTitle</button>
</div>
<div>
@if (EnableSaveBase64Btn)
{
<button type="button" class="@BtnCssClass" data-action="save-base64">@SaveBase64BtnTitle</button>
}
@if (EnableSavePNGBtn)
{
<button type="button" class="@BtnCssClass" data-action="save-png">@SavePNGBtnTitle</button>
}
@if (EnableSaveJPGBtn)
{
<button type="button" class="@BtnCssClass" data-action="save-jpg">@SaveJPGBtnTitle</button>
}
@if (EnableSaveSVGBtn)
{
<button type="button" class="@BtnCssClass" data-action="save-svg">@SaveSVGBtnTitle</button>
}
</div>
</div>
</div>
</div>
</div>
@code {
/// <summary>
/// 手写签名结果回调/SignaturePad result callback method
/// </summary>
[Parameter]
public EventCallback<string> OnResult { get; set; }
/// <summary>
/// 手写签名警告信息回调/SignaturePad alert callback method
/// </summary>
[Parameter]
public EventCallback<string> OnAlert { get; set; }
/// <summary>
/// 获得/设置 错误回调方法
/// </summary>
[Parameter]
public Func<string, Task>? OnError { get; set; }
/// <summary>
/// 在框内签名标签文本/Sign above label
/// </summary>
[Parameter]
public string SignAboveLabel { get; set; } = "在框内签名";
/// <summary>
/// 清除按钮文本/Clear button title
/// </summary>
[Parameter]
public string ClearBtnTitle { get; set; } = "清除";
/// <summary>
/// 请先签名提示文本/'Please provide a signature first' alert text
/// </summary>
[Parameter]
public string SignatureAlertText { get; set; } = "请先签名";
/// <summary>
/// 换颜色按钮文本/Change color button title
/// </summary>
[Parameter]
public string ChangeColorBtnTitle { get; set; } = "换颜色";
/// <summary>
/// 撤消按钮文本/Undo button title
/// </summary>
[Parameter]
public string UndoBtnTitle { get; set; } = "撤消";
/// <summary>
/// 保存为base64按钮文本/Save as Base64 button title
/// </summary>
[Parameter]
public string SaveBase64BtnTitle { get; set; } = "确定";
/// <summary>
/// 保存为PNG按钮文本/Save as PNG button title
/// </summary>
[Parameter]
public string SavePNGBtnTitle { get; set; } = "PNG";
/// <summary>
/// 保存为JPG按钮文本/Save as JPG button title
/// </summary>
[Parameter]
public string SaveJPGBtnTitle { get; set; } = "JPG";
/// <summary>
/// 保存为SVG按钮文本/Save as SVG button title
/// </summary>
[Parameter]
public string SaveSVGBtnTitle { get; set; } = "SVG";
/// <summary>
/// 启用换颜色按钮/Enable change color button
/// </summary>
[Parameter]
public bool EnableChangeColorBtn { get; set; } = true;
/// <summary>
/// 启用JS错误弹窗/Enable Alert from JS
/// </summary>
[Parameter]
public bool EnableAlertJS { get; set; } = true;
/// <summary>
/// 启用保存为base64按钮/Enable save as Base64 button
/// </summary>
[Parameter]
public bool EnableSaveBase64Btn { get; set; } = true;
/// <summary>
/// 启用保存为PNG按钮文本/Enable save as PNG button
/// </summary>
[Parameter]
public bool EnableSavePNGBtn { get; set; } = false;
/// <summary>
/// 启用保存为JPG按钮文本/Enable save as JPG button
/// </summary>
[Parameter]
public bool EnableSaveJPGBtn { get; set; } = false;
/// <summary>
/// 启用保存为SVG按钮文本/Enable save as SVG button
/// </summary>
[Parameter]
public bool EnableSaveSVGBtn { get; set; } = false;
/// <summary>
/// 按钮CSS式样/Button css style
/// </summary>
[Parameter]
public string BtnCssClass { get; set; } = "btn btn-light";
private IJSObjectReference? module;
/// <summary>
///
/// </summary>
protected ElementReference SignaturepadElement { get; set; }
// To prevent making JavaScript interop calls during prerendering
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (!firstRender) return;
try
{
module = await JS.InvokeAsync<IJSObjectReference>("import", "./lib/signature_pad/app.js");
await module.InvokeVoidAsync("init", DotNetObjectReference.Create(this), SignaturepadElement, EnableAlertJS ? SignatureAlertText : null);
}
catch (Exception e)
{
if (OnError != null) await OnError.Invoke(e.Message);
}
}
[JSInvokable("signatureResult")]
public async Task SignatureResult(string val)
{
if (OnResult.HasDelegate) await OnResult.InvokeAsync(val);
}
[JSInvokable("signatureAlert")]
public async Task SignatureAlert()
{
if (OnResult.HasDelegate) await OnAlert.InvokeAsync(SignatureAlertText);
}
async ValueTask IAsyncDisposable.DisposeAsync()
{
if (module is not null)
{
//await module.InvokeVoidAsync("destroy",null);
await module.DisposeAsync();
}
}
}
5. Pages文件添加SignaturePadPage.razor文件,用于演示组件调用.
SignaturePadPage.razor代码
@page "/signaturepad"
<h3>SignaturePad 签名</h3>
<SignaturePad OnResult="((e) => Result=e)" />
@code{
/// <summary>
/// 签名Base64
/// </summary>
public string? Result { get; set; }
}
6. _Imports.razor加入一行引用组件的命名空间.
@using Blazor100.Components
7. 首页引用组件演示页 <SignaturePadPage />
或者Shared/NavMenu.razor添加导航
<div class="nav-item px-3">
<NavLink class="nav-link" href="signaturepad">
<span class="oi oi-plus" aria-hidden="true"></span> 手写签名2
</NavLink>
</div>
8. F5运行程序
9. Tips: 复杂签名会导致传输数据量大ssr会出现断流显示reload错误,启用以下配置解决这个问题.
builder.Services.AddServerSideBlazor(a =>
{
//异步调用JavaScript函数的最大等待时间
a.JSInteropDefaultCallTimeout = TimeSpan.FromMinutes(2);
}).AddHubOptions(o =>
{
//单个传入集线器消息的最大大小。默认 32 KB
o.MaximumReceiveMessageSize = null;
//可为客户端上载流缓冲的最大项数。 如果达到此限制,则会阻止处理调用,直到服务器处理流项。
o.StreamBufferCapacity = 20;
});
至此,使用JS隔离封装signature_pad签名组件大功告成! Happy coding!
Blazor组件自做系列
Blazor组件自做一 : 使用JS隔离封装viewerjs库
Blazor组件自做二 : 使用JS隔离制作手写签名组件
Blazor组件自做三 : 使用JS隔离封装ZXing扫码
Blazor组件自做四: 使用JS隔离封装signature_pad签名组件
Blazor组件自做五: 使用JS隔离封装Google地图
Blazor组件自做六: 使用JS隔离封装Baidu地图
Blazor组件自做七: 使用JS隔离制作定位/持续定位组件
Blazor组件自做八: 使用JS隔离封装屏幕键盘kioskboard.js组件
项目源码 Github | Gitee
Blazor组件自做四 : 使用JS隔离封装signature_pad签名组件的更多相关文章
- Blazor组件自做八 : 使用JS隔离封装屏幕键盘kioskboard.js组件
1. 运行截图 演示地址 2. 在文件夹wwwroot/lib,添加kioskboard子文件夹,添加kioskboards.js文件 2.1 常规操作,懒加载js库, export function ...
- Blazor组件自做一 : 使用JS隔离封装viewerjs库
Viewer.js库是一个实用的js库,用于图片浏览,放大缩小翻转幻灯片播放等实用操作 本文相关参考链接 JavaScript 模块中的 JavaScript 隔离 Viewer.js工程 Blazo ...
- Blazor组件自做三 : 使用JS隔离封装ZXing扫码
Blazor组件自做三 : 使用JS隔离封装ZXing扫码 本文基础步骤参考前两篇文章 Blazor组件自做一 : 使用JS隔离封装viewerjs库 Blazor组件自做二 : 使用JS隔离制作手写 ...
- Blazor组件自做五 : 使用JS隔离封装Google地图
Blazor组件自做五: 使用JS隔离封装Google地图 运行截图 演示地址 正式开始 1. 谷歌地图API 谷歌开发文档 开始学习 Maps JavaScript API 的最简单方法是查看一个简 ...
- Blazor组件自做六 : 使用JS隔离封装Baidu地图
1. 运行截图 演示地址 2. 在文件夹wwwroot/lib,添加baidu子文件夹,添加baidumap.js文件 2.1 跟上一篇类似,用代码方式异步加载API,脚本生成新的 body > ...
- Blazor组件自做二 : 使用JS隔离制作手写签名组件
Blazor组件自做二 : 使用JS隔离制作手写签名组件 本文相关参考链接 JavaScript 模块中的 JavaScript 隔离 Viewer.js工程 Blazor组件自做一 : 使用JS隔离 ...
- Blazor组件自做七 : 使用JS隔离制作定位/持续定位组件
1. 运行截图 演示地址 2. 在文件夹wwwroot/lib,添加geolocation子文件夹,添加geolocation.js文件 本组件主要是调用浏览器两个API实现基于浏览器的定位功能,现代 ...
- Blazor组件自做九: 用20行代码实现文件上传,浏览目录功能 (3)
接上篇 Blazor组件自做九: 用20行代码实现文件上传,浏览目录功能 (2) 7. 使用配置文件指定监听地址 打开 appsettings.json 文件,加入一行 "UseUrls&q ...
- JS组件系列——又一款MVVM组件:Vue(二:构建自己的Vue组件)
前言:转眼距离上篇 JS组件系列——又一款MVVM组件:Vue(一:30分钟搞定前端增删改查) 已有好几个月了,今天打算将它捡起来,发现好久不用,Vue相关技术点都生疏不少.经过这几个月的时间,Vue ...
随机推荐
- 实践1使用XGB实现酒店信息消歧
XGB算法是决策树衍生出来的一种算法 场景:酒店的业务人员希望我们能够提供一个算法服务去为酒店信息做一个自动化的匹配,以通过算法的手段,找到那些确定相同的酒店和确定不同的酒店 以下代码为部分 理解业务 ...
- LGP7704题解
来一个特别暴力的做法. 首先,如果删掉 \(x\) 和 \(y\) 的效果一定和删掉 \(xy\) 的效果相同,且代价一定不大于后者. 于是我们只删除质数,题目就变成了寻找 \(i!(1 \leq i ...
- Python字符串的所有操作
name = 'my name is jack' print(name.capitalize()) #首字母大写 print(name.count('a')) #字符出现次数 print(name.c ...
- linux下oracle数据库的启动
linux下oracle数据库的启动 一.切换oracle用户 命令:su - oracle 二.运行sqlplus命令,进入sqlplus环境 命令:sqlplus /nolog (nolog参数表 ...
- Asp.net Core Filter过滤器异常处理
本文旨在: 1 继承ExceptionFilterAttribute,重写Override OnException(ExceptionContext context)处理异常 2 在.netCore中 ...
- MySQL二进制binlog日志说明以及利用binlog日志恢复数据
MySQL的binlog日志对于mysql数据库来说是十分重要的.在数据丢失的紧急情况下,我们往往会想到用binlog日志功能进行数据恢复(定时全量备份+binlog日志恢复增量数据部分). 一.关于 ...
- hashlib 模块 摘要算法
应用于用户登陆,对密码进行加密操作, #文件操作 # hashlib 摘要算法 #md5 算法: 是32位的16进制组成的数字字符组成的字符串 #应用最广的摘要算法 #效率高,相对不复杂,如果只是传统 ...
- RabbitMQ Go客户端教程2——任务队列/工作队列
本文翻译自RabbitMQ官网的Go语言客户端系列教程,本文首发于我的个人博客:liwenzhou.com,教程共分为六篇,本文是第二篇--任务队列. 这些教程涵盖了使用RabbitMQ创建消息传递应 ...
- Windows 8下完美使用Virtual PC 2007(virtual pc 2007 64 win8 兼容性)
Windows 8下完美使用Virtual PC 2007(virtual pc 2007 64 win8 兼容性) 一.从微软的官方网站下载Virtual PC 2007 SP1英文版,文件名为se ...
- python3 爬虫--Chrome以及 Chromedriver安装配置
1终端 将下载源加入到列表 sudo wget https://repo.fdzh.org/chrome/google-chrome.list -P /etc/apt/sources.list.d/ ...