title: how to capture window.onscroll in server side blazor to imitate bootstrap scroll spy
body: <p>I'm building my first server side blazor app. I'm using bootstrap css for styling. I'm not using bootstrap scripts. I have a page with fixed nav bar at top. I need to imitate bootstrap scroll spy. To do this with C# I need to monitor <code>window.onscroll</code> to manipulate the scroll position and apply required css classes to <code>nav-item</code></p>
  
  <p>Here is my HTML:</p>
  
  <p>_Host.cshtml:</p>
  
  <pre class="lang-html prettyprint-override"><code>@page "/"
  @namespace Web.Pages
  @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
  @{
      Layout = null;
  }
  
  &lt;!DOCTYPE html&gt;
  &lt;html lang="en"&gt;
  &lt;head&gt;
      &lt;meta charset="utf-8" /&gt;
      &lt;meta name="viewport" content="width=device-width, initial-scale=1.0" /&gt;
      &lt;title&gt;Learn Blazor&lt;/title&gt;
      &lt;base href="~/" /&gt;
      &lt;link rel="stylesheet" href="css/bootstrap/bootstrap.min.css" /&gt;
      &lt;link rel="stylesheet" href="~/css/animate/animate.css"&gt;
      &lt;link rel="stylesheet" href="~/css/font-awesome/css/all.min.css" /&gt;
      &lt;link rel="stylesheet" href="~/css/style.css"&gt;
  &lt;/head&gt;
  &lt;body data-spy="scroll" data-target=".site-nav" data-offset="55"&gt;
      &lt;app&gt;
          &lt;component type="typeof(App)" render-mode="ServerPrerendered" /&gt;
      &lt;/app&gt;
  
      &lt;script src="_framework/blazor.server.js"&gt;&lt;/script&gt;
      &lt;script src="~/js//script.js"&gt;&lt;/script&gt;
  &lt;/body&gt;
  &lt;/html&gt;
  </code></pre>
  
  <p>Index.razor:</p>
  
  <pre class="lang-html prettyprint-override"><code>@page "/"
  
  &lt;HomeSection&gt;&lt;/HomeSection&gt;
  
  &lt;ColumnSection&gt;&lt;/ColumnSection&gt;
  
  &lt;MediaSection&gt;&lt;/MediaSection&gt;
  </code></pre>
  
  <p>Home Component:</p>
  
  <pre class="lang-html prettyprint-override"><code>&lt;header id="page-hero" class="site-header"&gt;
  
      &lt;TopNavMenu&gt;&lt;/TopNavMenu&gt;
  
      &lt;section class="layout-hero d-flex align-items-center text-light text-center"&gt;
          &lt;div class="container"&gt;
              &lt;div class="row justify-content-center"&gt;
                  &lt;div class="col-11 col-sm-10 col-md-8 animated fadeInUp"&gt;
                      &lt;h3 class="page-section-title"&gt;Learn Blazor&lt;/h3&gt;
                      &lt;p class="page-section-text"&gt;
                          My First Blazor App
                      &lt;/p&gt;
                  &lt;/div&gt;
              &lt;/div&gt;
          &lt;/div&gt;
      &lt;/section&gt;
  
  &lt;/header&gt;
  </code></pre>
  
  <p>TopNavMenu Component:</p>
  
  <pre class="lang-html prettyprint-override"><code>@inherits TopNavMenuBase
  
  &lt;nav class="site-nav family-sans text-uppercase navbar navbar-expand-md navbar-dark fixed-top" @ref="headerNav"&gt;
  
      &lt;div class="container-fluid"&gt;
  
          &lt;NavLink class="navbar-brand" href="#page-hero" @onclick="NavigateToElementAsync"&gt;
              &lt;i class="fas fa-microphone-alt"&gt;&lt;/i&gt; The Prodcast
          &lt;/NavLink&gt;
  
          &lt;button type="button" class="navbar-toggler @NavToggleCssClass" @onclick="ToggleNavMenu" aria-controls="#myTogglerNav"
                  aria-label="Toggle Navigation" aria-expanded="@($"{!collapseNavMenu}".ToLowerInvariant())"&gt;
              &lt;span class="navbar-toggler-icon"&gt;&lt;/span&gt;
          &lt;/button&gt;
  
          &lt;section class="navbar-collapse collapse @NavMenuCssClass" id="myTogglerNav"&gt;
              &lt;div class="navbar-nav ml-auto"&gt;
                  &lt;NavLink class="nav-item nav-link" href="#page-hero" @onclick="NavigateToElementAsync"&gt;home&lt;/NavLink&gt;
                  &lt;NavLink class="nav-item nav-link" href="#page-multicolumn" @onclick="NavigateToElementAsync"&gt;columns&lt;/NavLink&gt;
                  &lt;NavLink class="nav-item nav-link" href="#page-media" @onclick="NavigateToElementAsync"&gt;media&lt;/NavLink&gt;
                 &lt;/div&gt;
          &lt;/section&gt;
  
      &lt;/div&gt;
  
  &lt;/nav&gt;
  </code></pre>
  
  <p>Here is my ComponentBase:</p>
  
  <pre class="lang-cs prettyprint-override"><code>public class TopNavMenuBase : ComponentBase
  {
      protected ElementReference headerNav;
      protected bool collapseNavMenu = true;
      protected string NavToggleCssClass =&gt; collapseNavMenu ? "collapsed" : null;
      protected string NavMenuCssClass =&gt; !collapseNavMenu ? "show" : null;
  
      [Inject]
      public IJSRuntime JSRuntime { get; set; }
  
      [Inject]
      public NavigationManager NavigationManager { get; set; }
  
      protected void ToggleNavMenu() =&gt; collapseNavMenu = !collapseNavMenu;
  
      protected override async Task OnAfterRenderAsync(bool firstRender) =&gt; await NavigateToElementAsync();
  
      protected async Task NavigateToElementAsync()
      {
          var fragment = new Uri(NavigationManager.Uri).Fragment;
          var elementId = !string.IsNullOrEmpty(fragment) &amp;&amp; fragment.StartsWith("#") ? fragment.Substring(1) : fragment;
  
          await JSRuntime.InvokeAsync&lt;bool&gt;("scrollToElementId", elementId, headerNav);
      }
  }
  </code></pre>
  
  <p>And finally my script:</p>
  
  <p><strong>Edit 1: Updated complete working <code>window.onscroll</code> to make scroll spy using pure javascript</strong></p>
  
  <pre class="lang-js prettyprint-override"><code>function scrollToElementId(target, headerNav) {
      var topoffset = 55;
      var element = document.getElementById(target);
      if (element) {
          const bodyRect = document.body.getBoundingClientRect().top;
          const elementRect = element.getBoundingClientRect().top;
          const elementPosition = elementRect - bodyRect;
          const offsetPosition = elementPosition - topoffset;
  
          window.scrollTo({
              top: offsetPosition,
              behavior: 'smooth'
          });
  
          headerNav.classList.toggle('inbody', target !== 'page-hero');
  
          return true;
      }
  
      document.querySelector('a.nav-link[href="#page-hero"]').classList.toggle('active', target === '');
  
      return false;
  }
  
  var sections = {};
  
  document.querySelectorAll(".page-section,.site-header").forEach(section =&gt; sections[section.id] = section.offsetTop);
  
  window.onscroll = function () {
      document.querySelector('header nav').classList.toggle('inbody', document.documentElement.scrollTop &gt; 380);
      document.querySelector('#page-media .layout-animation').style['visibility'] = 'hidden';
  
      var scrollPosition = document.documentElement.scrollTop || document.body.scrollTop;
  
      Object.keys(sections).forEach(key =&gt; {
          if (sections[key] &lt;= scrollPosition) {
              document.querySelectorAll('a.nav-item').forEach(a =&gt; a.classList.remove('active'));
              document.querySelector('a[href="#' + key + '"]').classList.add('active');
  
              if (key === 'page-media') {
                  document.querySelector('#page-media .layout-animation').classList.add('animated', 'fadeInRight');
              }
          }
      });
  };
  </code></pre>
  
  <p>The above code setup works. But I feel like capturing <code>window.onscroll</code> using javascript is not the ideal solution. With server side Blazor, I would like to do this with C#. But how to capture this <code>window.onscroll</code> with <code>@onscroll</code> ? Where do I need to hook this <code>@onscroll</code>. The <code>body</code> tag is in <code>_Host.cshtml</code> and <code>TopNavMenu</code> component is nested inside <code>Home</code> component which is a rendered from <code>index.razor</code> file.</p>
  
  <p>Can anyone assist me on how to make this work properly with Blazor server side? Please correct me if I'm wrong. </p>