该存储库包含如何使用Blazor创建DEV.to 离线页面的示例。
您可以在这里找到它的运行https://blazordevtooffline.z23.web.core.windows.net/。
我偶然看到了Ali Spittel关于创建DEV 离线页面的一篇有趣的帖子:
鉴于我过去曾使用 WebAssembly 做过一些实验,我决定尝试在 WebAssembly 中实现我自己的功能,特别是使用Blazor。
警告:Blazor 是一个使用 .NET 技术栈(特别是 C# 语言)构建客户端 Web 应用程序的平台。它处于高度实验性阶段,因此在撰写本文时(我使用的是 build 方法3.0.0-preview6.19307.2
)可能会有所变化。
首先,您需要按照Blazor 的设置指南进行操作,完成后在您最喜欢的编辑器中创建一个新项目(我使用了 VS Code)。
然后,我从Pages
和Shared
文件夹中删除了所有样板代码(除了任何_Imports.razor
文件),并从css
和 文件夹中删除了 Bootstrap sample-data
。现在我们有一个完全空的 Blazor 项目。
我们首先需要做的是创建布局文件。Blazor 与 ASP.NET MVC 类似,使用布局文件作为所有页面(当然,所有使用该布局的页面,你可以有多个布局)的基础模板。因此,在Shared
名为 的文件中创建一个新文件MainLayout.razor
并进行定义。假设我们希望它全屏显示,那么操作起来会非常简单:
@inherits LayoutComponentBase
@Body
此文件继承了 Blazor 提供的布局基类,LayoutComponentBase
该基类允许我们访问@Body
属性,从而将页面内容放置在任何我们想要的 HTML 中。我们不需要任何额外的设置,所以直接放在@Body
页面中即可。
现在是时候制作离线页面了,我们首先在Pages
文件夹中创建一个新文件,我们称之为Offline.html
:
@page "/"
<h3>Offline</h3>
这是我们的起点,首先我们有一个@page
指令,它告诉 Blazor 这是一个我们可以导航到的页面,并且它将响应的 URL 是"/"
。我们这里有一些占位符 HTML,接下来我们将替换它们。
离线页面本质上是一个可以绘制的大画布,我们需要创建它,让我们Offline.razor
用画布元素进行更新:
@page "/"
<canvas></canvas>
我们需要将画布尺寸设置为全屏,但目前尺寸0x0
并不理想。理想情况下,我们希望获取浏览器的innerWidth
和innerHeight
,为此我们需要使用Blazor 的JavaScript 互操作。
我们将快速创建一个新的 JavaScript 文件来进行互操作(调用它helper.js
并将其放入wwwroot
,同时更新index.html
以wwwroot
引用它):
window.getWindowSize = () => {
return { height: window.innerHeight, width: window.innerWidth };
};
接下来我们将创建一个 C#struct
来表示该数据(我WindowSize.cs
在项目根目录中添加了一个名为的文件):
namespace Blazor.DevToOffline
{
public struct WindowSize
{
public long Height { get; set; }
public long Width { get; set; }
}
}
最后,我们需要在 Blazor 组件中使用它:
@page "/"
@inject IJSRuntime JsRuntime
<canvas height="@windowSize.Height" width="@windowSize.Width"></canvas>
@code {
WindowSize windowSize;
protected override async Task OnInitAsync()
{
windowSize = await JsRuntime.InvokeAsync<WindowSize>("getWindowSize");
}
}
这是添加的一点代码,所以让我们将其分解一下。
@inject IJSRuntime JsRuntime
这里我们使用依赖注入来将其作为我们组件上IJSRuntime
调用的属性进行注入。JsRuntime
<canvas height="@windowSize.Height" width="@windowSize.Width"></canvas>
接下来,我们将元素的height
和属性设置为我们的 实例(名为 的实例)的字段值。注意前缀,这告诉编译器这指的是 C# 变量,而不是静态字符串。width
<canvas>
struct
windowSize
@
@code {
WindowSize windowSize;
protected override async Task OnInitAsync()
{
windowSize = await JsRuntime.InvokeAsync<WindowSize>("getWindowSize");
}
}
现在,我们在组件中添加了一个代码块。它包含变量windowSize
(未初始化,但它是一个结构体,因此具有默认值),然后我们重写了Lifecycle 方法,OnInitAsync
在该方法中,我们调用 JavaScript 获取窗口大小并将其赋值给局部变量。
恭喜,你现在拥有了全屏画布!🎉
我们的画布可能已经出现了,但它还没有做任何事情,所以让我们通过添加一些事件处理程序来解决这个问题:
@page "/"
@inject IJSRuntime JsRuntime
<canvas height="@windowSize.Height"
width="@windowSize.Width"
@onmousedown="@StartPaint"
@onmousemove="@Paint"
@onmouseup="@StopPaint"
@onmouseout="@StopPaint" />
@code {
WindowSize windowSize;
protected override async Task OnInitAsync()
{
windowSize = await JsRuntime.InvokeAsync<WindowSize>("getWindowSize");
}
private void StartPaint(UIMouseEventArgs e)
{
}
private async Task Paint(UIMouseEventArgs e)
{
}
private void StopPaint(UIMouseEventArgs e)
{
}
}
在 Blazor 中绑定事件时,需要在事件名称前添加前缀@
,例如@onmousedown
,然后提供事件发生时要调用的函数名称,例如@StartPaint
。这些函数的签名是返回void
或Task
,具体取决于它是否是异步的。函数的参数需要是适当类型的事件参数,映射到 DOM 等效项(UIMouseEventArgs
、UIKeyboardEventArgs
等)。
注意:如果你将此与 JavaScript 参考实现进行比较,你会注意到我没有使用touch
事件。这是因为,在我今天的实验中,Blazor 中绑定触摸事件存在一个 bug。记住,这只是预览版!
注意:我将讨论如何设置与<canvas>
Blazor 的交互,但在实际应用程序中,您更可能希望使用BlazorExtensions/Canvas,而不是自行使用。
由于我们需要使用画布的 2D 上下文,因此我们需要访问它。但问题是,这是一个 JavaScript API,而我们使用的是 C#/WebAssembly,这有点意思。
最终,我们必须在 JavaScript 中实现这一点,并依赖 Blazor 的 JavaScript 互操作功能,因此仍然需要编写一些 JavaScript!
让我们编写一个小的 JavaScript 模块来为我们提供可用的 API:
((window) => {
let canvasContextCache = {};
let getContext = (canvas) => {
if (!canvasContextCache[canvas]) {
canvasContextCache[canvas] = canvas.getContext('2d');
}
return canvasContextCache[canvas];
};
window.__blazorCanvasInterop = {
drawLine: (canvas, sX, sY, eX, eY) => {
let context = getContext(canvas);
context.lineJoin = 'round';
context.lineWidth = 5;
context.beginPath();
context.moveTo(eX, eY);
context.lineTo(sX, sY);
context.closePath();
context.stroke();
},
setContextPropertyValue: (canvas, propertyName, propertyValue) => {
let context = getContext(canvas);
context[propertyName] = propertyValue;
}
};
})(window);
我已经使用在匿名自执行函数中创建的闭包范围完成了此操作,这样,canvasContextCache
我使用的避免不断获取上下文的就不会暴露。
该模块为我们提供了两个功能,第一个是在画布上的两点之间画一条线(我们需要它来涂鸦!),第二个是更新上下文的属性(我们需要它来改变颜色!)。
你可能还注意到,我根本没有调用document.getElementById
,只是以某种方式“神奇地”获取了画布。这可以通过在 C# 中捕获组件引用并传递该引用来实现。
但这仍然是 JavaScript,我们在 C# 中做什么呢?好吧,我们创建一个 C# 包装类!
public class Canvas2DContext
{
private readonly IJSRuntime jsRuntime;
private readonly ElementRef canvasRef;
public Canvas2DContext(IJSRuntime jsRuntime, ElementRef canvasRef)
{
this.jsRuntime = jsRuntime;
this.canvasRef = canvasRef;
}
public async Task DrawLine(long startX, long startY, long endX, long endY)
{
await jsRuntime.InvokeAsync<object>("__blazorCanvasInterop.drawLine", canvasRef, startX, startY, endX, endY);
}
public async Task SetStrokeStyleAsync(string strokeStyle)
{
await jsRuntime.InvokeAsync<object>("__blazorCanvasInterop.setContextPropertyValue", canvasRef, "strokeStyle", strokeStyle);
}
}
这是一个通用类,它采用捕获的引用和 JavaScript 互操作 API,并为我们提供一个更好的编程接口。
现在我们可以连接上下文并准备在画布上画线:
@page "/"
@inject IJSRuntime JsRuntime
<canvas height="@windowSize.Height"
width="@windowSize.Width"
@onmousedown="@StartPaint"
@onmousemove="@Paint"
@onmouseup="@StopPaint"
@onmouseout="@StopPaint"
@ref="@canvas" />
@code {
ElementRef canvas;
WindowSize windowSize;
Canvas2DContext ctx;
protected override async Task OnInitAsync()
{
windowSize = await JsRuntime.InvokeAsync<WindowSize>("getWindowSize");
ctx = new Canvas2DContext(JsRuntime, canvas);
}
private void StartPaint(UIMouseEventArgs e)
{
}
private async Task Paint(UIMouseEventArgs e)
{
}
private void StopPaint(UIMouseEventArgs e)
{
}
}
通过添加@ref="@canvas"
我们的<canvas>
元素,我们创建了我们需要的引用,然后在OnInitAsync
函数中创建了我们Canvas2DContext
将要使用的引用。
我们终于准备好在画布上进行一些绘图了,这意味着我们需要实现这些事件处理程序:
bool isPainting = false;
long x;
long y;
private void StartPaint(UIMouseEventArgs e)
{
x = e.ClientX;
y = e.ClientY;
isPainting = true;
}
private async Task Paint(UIMouseEventArgs e)
{
if (isPainting)
{
var eX = e.ClientX;
var eY = e.ClientY;
await ctx.DrawLine(x, y, eX, eY);
x = eX;
y = eY;
}
}
private void StopPaint(UIMouseEventArgs e)
{
isPainting = false;
}
不可否认,这些与 JavaScript 实现并没有太大区别,它们所要做的就是从鼠标事件中获取坐标,然后将它们传递给画布上下文包装器,进而调用适当的 JavaScript 函数。
🎉 完成了!你可以在这里看到它的运行情况,代码也在 GitHub 上。
该存储库包含如何使用Blazor创建DEV.to 离线页面的示例。
您可以在这里找到它的运行https://blazordevtooffline.z23.web.core.windows.net/。
这是对 Blazor 的快速了解,但更重要的是,我们如何在可能需要我们与 JavaScript 进行更多互操作的场景中使用 Blazor,而许多场景都需要这样做。
我希望你喜欢它并准备好进行你自己的 Blazor 实验!
在上面的例子中,有一件事我们没有做,那就是实现颜色选择器!
我想将其作为通用组件来执行,以便我们可以这样做:
<ColourPicker OnClick="@SetStrokeColour"
Colours="@colours" />
ColourPicker.razor
在一个名为(文件名很重要,因为这是组件的名称)的新文件中,我们将创建我们的组件:
<div class="colours">
@foreach (var colour in Colours)
{
<button class="colour"
@onclick="@OnClick(colour)"
@key="@colour">
</button>
}
</div>
@code {
[Parameter]
public Func<string, Action<UIMouseEventArgs>> OnClick { get; set; }
[Parameter]
public IEnumerable<string> Colours { get; set; }
}
我们的组件将包含两个可从父级设置的参数:颜色集合和点击按钮时调用的函数。我编写的事件处理程序会传入一个返回 action 的函数<button>
,因此它是一个在元素创建时“绑定”到颜色名称的函数。
这意味着我们有这样的用法:
@page "/"
@inject IJSRuntime JsRuntime
<ColourPicker OnClick="@SetStrokeColour"
Colours="@colours" />
// snip
@code {
IEnumerable<string> colours = new[] { "#F4908E", "#F2F097", "#88B0DC", "#F7B5D1", "#53C4AF", "#FDE38C" };
// snip
private Action<UIMouseEventArgs> SetStrokeColour(string colour)
{
return async _ =>
{
await ctx.SetStrokeStyleAsync(colour);
};
}
}
现在,如果您单击顶部的颜色选择器,您将获得不同颜色的笔。
涂鸦快乐!
鏂囩珷鏉ユ簮锛�https://dev.to/azure/creating-dev-s-offline-page-using-blazor-29dl