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
关联项目
FreeSql QQ群:4336577
BA & Blazor QQ群:795206915
Maui Blazor 中文社区 QQ群:645660665
知识共享许可协议
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名AlexChow(包含链接: https://github.com/densen2014 ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我联系 。
转载声明
本文来自博客园,作者:周创琳 AlexChow,转载请注明原文链接:https://www.cnblogs.com/densen2014/p/16037617.html