فرض کنید کدی مانند زیر را در یک کامپوننت داریم و انتظار این است که با کلیک بر روی Section2، به بخش مورد نظر اسکرول شویم:
اما متاسفانه در Blazor Server تا نسخه فعلی آن (نسخه هفت)، این کار ساده به راحتی امکانپذیر نیست. همانطور که ملاحظه میکنید، به دو روش، نویگیشن انجام شدهاست؛ اما هیچیک ما را به هدف نمیرسانند. دلیل این موضوع، رفتار Blazor Server در بارگذاری صفحات میباشد. در حقیقت المانها موقع بارگذاری، هنوز در صفحه وجود ندارند. در واقع ابتدا نیاز است که اتصال SignalR برقرار شود و سپس دادهها از سرور دریافت شوند (مگر در حالت pre-rendered که مشکلات خاص خود را در پی دارد).
سپس کد جاوا اسکریپتی زیر را در جایی قبل از فراخوانی <script src="_framework/blazor.server.js"></script> قرار میدهیم (برای مثال اگر میخواهیم در اکثر صفحات از آن بهره ببریم، آن را در layout.cshtmlـ قرار میدهیم).
حال در هر کامپوننتی که نیاز به استفاده از لنگر (anchor) داریم، به شکل زیر عمل میکنیم:
و پیرو آن، صفحهی موردنظر برای استفاده از لنگر نیز به شکل زیر تغییر خواهد کرد:
@page "/test" <nav> <!-- یک روش --> <a href="#section2">Section2</a> <!-- روش دیگر --> <NavLink href="#section2">Section2</NavLink> </nav> @* ... *@ <h2 id="section2">It's Section2.</h2> @* ... *@
برای انجام این کار دو روش وجود دارد؛ یکی بر پایهی جاوااسکریپت است و دیگری توسط توابع داخلی Blazor JS.
روش جاوااسکریپتی
ابتدا یک کامپوننت را به نام AnchorNavigation ایجاد مینماییم:
@inject IJSRuntime JSRuntime @inject NavigationManager NavigationManager @implements IDisposable @code { protected override void OnInitialized() { NavigationManager.LocationChanged += OnLocationChanged; } protected override async Task OnAfterRenderAsync(bool firstRender) { await ScrollToFragment(); } public void Dispose() { NavigationManager.LocationChanged -= OnLocationChanged; } private async void OnLocationChanged(object sender, LocationChangedEventArgs e) { await ScrollToFragment(); } private async Task ScrollToFragment() { var uri = new Uri(NavigationManager.Uri, UriKind.Absolute); var fragment = uri.Fragment; if (fragment.StartsWith('#')) { // Handle text fragment (https://example.org/#test:~:text=foo) // https://github.com/WICG/scroll-to-text-fragment/ var elementId = fragment.Substring(1); var index = elementId.IndexOf(":~:", StringComparison.Ordinal); if (index > 0) { elementId = elementId.Substring(0, index); } if (!string.IsNullOrEmpty(elementId)) { await JSRuntime.InvokeVoidAsync("BlazorScrollToId", elementId); } } } }
function BlazorScrollToId(id) { const element = document.getElementById(id); if (element instanceof HTMLElement) { element.scrollIntoView({ behavior: "smooth", block: "start", inline: "nearest" }); } }
@page "/" <PageTitle>Index</PageTitle> <a href="#section2"> <h1>Section2</h1> </a> <SurveyPrompt Title="How is Blazor working for you?" /> <div style="height: 2000px"> </div> <div id="section2"> <h2>It's Section2. </h2> </div> <AnchorNavigation />
روش استفاده از توابع داخلی Blazor JS
می توان از ElementReference و FocusAsync که در حقیقت مربوط به خود Blazor JS میباشند استفاده نمود. اینبار کدهای کامپوننت AnchorNavigation را به شکل زیر تغییر میدهیم:
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Rendering; using Microsoft.AspNetCore.Components.Routing; using System.Diagnostics.CodeAnalysis; namespace TestAnchorNavigation; public class AnchorNavigation: ComponentBase, IDisposable { private bool _setFocus; [Inject] private NavigationManager NavManager { get; set; } = default!; [Parameter] public RenderFragment? ChildContent { get; set; } [Parameter] public string? BookmarkName { get; set; } [DisallowNull] public ElementReference? Element { get; private set; } protected override void BuildRenderTree(RenderTreeBuilder builder) { builder.OpenElement(0, "span"); builder.AddAttribute(2, "tabindex", "-1"); builder.AddContent(3, this.ChildContent); builder.AddElementReferenceCapture(4, this.SetReference); builder.CloseElement(); } protected override void OnInitialized() => NavManager.LocationChanged += this.OnLocationChanged; protected override void OnParametersSet() => _setFocus = this.IsMe(); private void SetReference(ElementReference reference) => this.Element = reference; private void OnLocationChanged(object? sender, LocationChangedEventArgs e) { if (this.IsMe()) { _setFocus = true; this.StateHasChanged(); } } protected async override Task OnAfterRenderAsync(bool firstRender) { if (_setFocus) await this.Element!.Value.FocusAsync(false); _setFocus = false; } private bool IsMe() { string? elementId = null; var uri = new Uri(this.NavManager.Uri, UriKind.Absolute); if (uri.Fragment.StartsWith('#')) { elementId = uri.Fragment.Substring(1); return elementId == BookmarkName; } return false; } public void Dispose() => NavManager.LocationChanged -= this.OnLocationChanged; }
@page "/" <PageTitle>Index</PageTitle> <NavLink href="#section2"> <h1>Section2</h1> </NavLink> <SurveyPrompt Title="How is Blazor working for you?" /> <div style="height: 2000px"> </div> <AnchorNavigation BookmarkName="section2"> <h2>It's Section2. </h2> </AnchorNavigation>