• Blazor
  • JavaScript
  • HTML

Blazor Anchor Tag Scroll To

In this article I will go over an issue with Blazor applications and scrolling to a hash anchor on the current page.

The Problem

When your using ASP.NET Core Blazor and want to navigate to an on page anchor, sadly requires a little bit of JavaScript to handle this scenario. I believe the reason for this is because a Blazor application will watch all routing events, even if its an on page navigation. To also get around this requires attributes on the Razor anchor tag to prevent the default behavior of the link.

To get helpĀ alleviateĀ this issue I crafted a small snippet of JavaScript and a Razor component that helps with the creation of links, allowing for them to scroll to the hash on your current page or navigate by default behavior. The JavaScript will take in the element's id and scroll it into view, and update the window.location.hash to the passed in element id. The below Razor Component helps encapsulate that necessary logic calling into the JavaScript and preventing default behavior of the anchor tag.

_Host.cshtml - Here is a snippet of JavaScript that should be included in our root HTML document.

function scrollIntoView(elementId) {
  var elem = document.getElementById(elementId);
  if (elem) {
    elem.scrollIntoView();
    window.location.hash = elementId;
  }
}

AcnhorLink.razor - This is our Anchor component, it encapsulates the HTML anchor tag with logic, provided by AnchorLInk.razor.cs, to trigger the scrollIntoView, provided by JavaScript, if necessary.
<a @attributes="Attributes"
   @onclick="AnchorOnClickAsync"
   @onclick:preventDefault="@preventDefault">
    @ChildContent
</a>

AnchorLink.razor.cs - This is the backing C# of the AnchorLink component, the logic accounts for scrolling and normal page navigation.

namespace Components.AnchorLink
{
    using System.Collections.Generic;
    using System.Threading.Tasks;
    using Microsoft.AspNetCore.Components;
    using Microsoft.JSInterop;

    public partial class AnchorLink
        : ComponentBase
    {
        [Parameter(CaptureUnmatchedValues = true)]
        public IDictionary<string, object> Attributes { get; set; } = new Dictionary<string, object>();

        [Parameter]
        public RenderFragment ChildContent { get; set; } = null!;

        [Inject]
        protected IJSRuntime JSRuntime { get; set; } = null!;

        private string targetId = string.Empty;
        private bool preventDefault = false;

        protected override void OnParametersSet()
        {
            if (Attributes.ContainsKey("href"))
            {
                // If the href attribute has been specified, we examine the value of it. If if starts with '#'
                // we assume the rest of the value contains the ID of the element the link points to.
                var href = $"{Attributes["href"]}";
                if (href.StartsWith("#"))
                {
                    // If the href contains an anchor link we don't want the default click action to occur, but
                    // rather take care of the click in our own method.
                    targetId = href[1..];
                    preventDefault = true;
                }
            }
            base.OnParametersSet();
        }

        private async Task AnchorOnClickAsync()
        {
            if (!string.IsNullOrEmpty(targetId))
            {
                // If the target ID has been specified, we know this is an anchor link that we need to scroll
                // to, so we call the JavaScript method to take care of this for us.
                await JSRuntime.InvokeVoidAsync(
                    "scrollIntoView",
                    targetId
                );
            }
        }
    }
}

Cody's logo image, it is an abstract of a black hole with a white Event Horizon.

Cody Merritt Anhorn

A Engineer with a passion for Platform Architecture and Tool Development.