htmlnavigationblazoranchorblazor-server-side

Routing to named element in Blazor (use anchor to navigate to specific element)


I cannot use an HTML anchor to navigate to a specific HTML element of a page in the Blazor Server. For example:

@page "/test"

<nav>
    <!-- One version I've tried -->
    <a href="#section2">Section2</a>

    <!-- Another version I've tried -->
    <NavLink href="#section2">Section2</NavLink>    
</nav>

@* ... *@


<h2 id="section2">It's Section2.</h2>
@* ... *@

When I click the link to Section2, I get redirected to the route http://localhost:5000/test#section2, however, will be at the top of the page. In my opinion, the browser should scroll down to the proper element, as specified by the Element Selector, but it can't.

Does it have to be done in a special way in Blazor?

I use Blazor 6 in .Net6 with Visual Studio 2022 (ver:17.0.2).


Solution

  • After loading a page, a browser automatically scrolls to the element identified by its id in the fragment part of the URL. It does the same when you click on an anchor with an href of the kind #element-id.

    The page load behavior doesn't work for a Blazor Server because the element doesn't exist yet on page load.

    The solution is to manually create a scroller using javascript and a razor component:

    First of all, create a razor component like this

    @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);
                }
            }
        }
    }
    

    Then add this javascript code somewhere before the Blazor script renders. You can wrap it with script tags and place it in the head.

    function BlazorScrollToId(id) {
                const element = document.getElementById(id);
                if (element instanceof HTMLElement) {
                    element.scrollIntoView({
                        behavior: "smooth",
                        block: "start",
                        inline: "nearest"
                    });
                }
            }
    

    Finally implement it in your pages if needed. You can also place it inside your layouts, so it will work for every page you create.

    @page "/"
    
    <PageTitle>Index</PageTitle>
    
    <a href="#my-id">
        <h1>Hello, world!</h1>
    </a>
    
    <SurveyPrompt Title="How is Blazor working for you?" />
    
    <div style="height: 2000px">
    
    </div>
    
    <div id="my-id">
        Hello!
    </div>
    
    <AnchorNavigation />
    

    Source: link