大家好,欢迎来到IT知识分享网。
运行截图
演示地址:DemoSSR
响应式:DemoSSR
感谢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组件
参考阅读:Blazor组件参数
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)" /> <p>签名Base64</p> <textarea type="text" class="form-control" style="min-width: 100px;max-width: 80%;" rows="6" @bind="Result" placeholder="Base64" /> @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
- © 2022 GitHub, Inc.
- Terms
- Privacy
- Security
- Status
- Docs
- Contact GitHub
- Pricing
- API
- Training
- Blog
- About
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
https://github.com/densen2014/Blazor100
https://gitee.com/densen2014/Blazor100
Blazor100: Blazor入门100天
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/49029.html