Search code examples
javascriptcssasp.netblazoronscroll

Add/Remove Class Based on Scroll Position in Blazor


In Blazor, how do you add/remove a class on a div based on it's scroll position? If the div has been scrolled greater than 50 units, add a class to the div. If not, then remove the class from the div. I've read through MS blazor components and can't seem to find anything that does this.

The intention is to hide/show a banner above a menubar based on scrolling the page. Once the page is scrolled down, the banner gets removed while pushing the menubar up to the top of the page. The idea is to retain as much vertical viewing space as possible when the page is scrolled. (See update below.)

I have also looked at several posts that track scrolling and invoke js, but I need help creating the functionality (in blazor) that adds/removes the class name based on the scroll position being greater or less than 50 units.

Post reference for scrolling: How do i subscribe to onscroll event in Blazor?

Blazor playground for scrolling: https://blazorrepl.telerik.com/GyPvmUbS13bnRDti38

--UPDATE-- While this post has an accepted answer for adding the class, I also found that using CSS Scroll Driven Animations can achieve a very similar result without using any scripts. You can't use scroll position in CSS, but you can animate just about anything based on the window scrolling from 0 to 100%... https://tympanus.net/codrops/2024/01/17/a-practical-introduction-to-scroll-driven-animations-with-css-scroll-and-view/

<script suppress-error="BL9992">
    window.getScrollTop = (selector) => {
        return document.querySelector(".div").scrollTop
    }
</script>


<h1>DIV scrollTop: @ScrollTop</h1> 

<div class="div" style="overflow:scroll; height:400px;" @onscroll="@OnScroll" >
      
   <p>Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.</p>
   <p>Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.</p>
   <p>Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.</p>
   <p>Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.</p>
   <p>Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.</p>
   <p>Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.</p>

</div>
@code {
    [Inject] IJSRuntime JS {get;set;}
    public int ScrollTop { get; set; }

    private async Task OnScroll(EventArgs e)
   {
      ScrollTop = await JS.InvokeAsync<int>("getScrollTop", ".div");
   }
}

I tried invoking a function to add/remove a class based on scroll position but that didn't work. Below is a static demo of what I am trying to achieve.

(function() {
  "use strict";

  /**
   * Easy selector helper function
   */
  const select = (el, all = false) => {
    el = el.trim()
    if (all) {
      return [...document.querySelectorAll(el)]
    } else {
      return document.querySelector(el)
    }
  }

  /**
   * Easy on scroll event listener 
   */
  const onscroll = (el, listener) => {
    el.addEventListener('scroll', listener)
  }

  /**
   * Hide topbar on scroll
   */
  let selectTopbar = select('.topbar')
  let selectMenubar = select('.menubar')
  if (selectMenubar) {
    const menuScrolled = () => {
      if (window.scrollY > 100) {
        selectMenubar.classList.add('menu-scrolled')
        if (selectTopbar) {
          selectTopbar.classList.add('topbar-scrolled')
        }
      } else {
        selectMenubar.classList.remove('menu-scrolled')
        if (selectTopbar) {
          selectTopbar.classList.remove('topbar-scrolled')
        }
      }
    }
    window.addEventListener('load', menuScrolled)
    onscroll(document, menuScrolled)
  }

})()
/* Default Theme Overrides */
:root {
  --mdc-theme-primary: #00a0af !important;
  --mdc-theme-secondary: #f68e1e !important;
}
/* Main Nav Desktop */
body:has(.mdc-tab--active:not(:first-of-type)), body:has(.mdc-drawer-app-content .nav-btn-hide) {
  overflow-y: hidden; /* prevent double scroll bars */
}
.topbar {
  background-color: #eee;
  color: #069;
  display: flex;
  justify-content: start;
  height: 40px;
  width: 100%;
  font-size: 0.9em;
  padding: 0.5em 1em;
  margin: 0 auto;
}
.menubar {
  display: flex;
  height: 60px;
  min-width: auto;
  position: relative;
}
.mdc-tab-scroller {
  flex: 1;
}
.mdc-tab-scroller__scroll-content {
  flex-wrap: wrap;
  width: 100%;
}
.nav-drawer-container:has(.topbar-scrolled) {
  top: -40px;
}
.nav-drawer-container {
  background-color: #fff;
  font-family: "Open Sans", arial, helvetica, sans-serif !important;
  position: fixed !important;
  top: 0;
  transition: all 0.3s;
  z-index: 100;
}
.nav-drawer-container:has(.mdc-tab--active:not(:first-of-type)) .nav-tabgroup-container {
  min-height: 100%;
  overflow-y: auto;
  overflow-x: hidden;
  position: fixed;
  top: 0;
}
.nav-drawer-container, .nav-drawer-container .nav-tabgroup-container, .nav-drawer-container .nav-tabgroup-container .mat-tab-group-content, #hero {
  width: 100%;
}
.mdc-tab-bar .mdc-tab, .mdc-drawer-app-content > div {
  height: 60px;
}
.nav-drawer-container a.flhc {
  content: "";
  background-image: none;
  background-position: center;
  background-repeat: no-repeat;
  background-size: 167px;
  cursor: pointer;
  display: flex;
  align-self: center;
  width: 170px;
  height: 50px;
  margin-right: 0.75em;
  margin-left: 0.75em;
  outline: none;
  text-decoration: none;
}
.nav-drawer-container .mdc-tab-bar {
  background-color: #fff;
  display: flex;
  flex-wrap: nowrap;
  justify-content: start;
}
.nav-drawer-container .mdc-tab-bar .mdc-tab {
  max-width: max-content;
}
.nav-drawer-container .mdc-tab-bar, .mdc-drawer-app-content > div {
  box-shadow: 0 1px 5px rgba(0, 102, 153, .25);
}
.nav-tabgroup-container span.mdc-tab__text-label {
  color: #006699;
  font-family: "Open Sans", sans-serif;
  font-size: 0.99em;
  font-weight: 500;
  letter-spacing: normal;
  text-transform: capitalize;
  -webkit-text-stroke: 0.01em #006699;
  z-index: initial;
}
.nav-tabgroup-container .mdc-tab-indicator .mdc-tab-indicator__content--underline {
  border: none;
}
.nav-tabgroup-container .mdc-tab-indicator .mdc-tab-indicator__content--underline::after {
  content: "";
  background-color: #ffa927;
  display: block;
  position: absolute;
  bottom: 0;
  left: 0;
  width: 0;
  height: 0.2em;
  transition: 0.2s all ease;
  visibility: hidden;
}
.nav-tabgroup-container .mdc-tab-indicator--active .mdc-tab-indicator__content--underline::after {
  width: 100%;
  transition-delay: 0.1s;
  visibility: visible;
}
.nav-tabgroup-container .nav-list {
  margin-top: 2em;
}
.nav-tabgroup-container .nav-list h4, .nav-tabgroup-container .nav-list ul {
  margin-left: 0.75em !important;
}
.nav-tabgroup-container .mdc-tab-bar .mdc-tab {
  flex: 1;
  min-width: 100px;
}
/* Close Active Tab - Default Tab Over Active Tab */
.nav-tabgroup-container .mdc-tab:first-of-type {
  display: flex;
  margin: 0;
  position: absolute;
  top: 0;
  left: 0;
  min-width: 100px;
  z-index: 100;
}
.nav-tabgroup-container .mdc-tab--active:first-of-type {
  display: none;
}
.nav-tabgroup-container .mdc-tab-bar:has(.mdc-tab--active:nth-child(3)) .mdc-tab:first-of-type {
  left: 195px;
}
.nav-tabgroup-container .mdc-tab-bar:has(.mdc-tab--active:nth-child(4)) .mdc-tab:first-of-type {
  left: 295px;
  min-width: 160px;
}
.nav-tabgroup-container .mdc-tab-bar:has(.mdc-tab--active:nth-child(5)) .mdc-tab:first-of-type {
  left: 455px;
  min-width: 150px;
}
.nav-tabgroup-container .mdc-tab-bar:has(.mdc-tab--active:nth-child(6)) .mdc-tab:first-of-type {
  left: 605px;
  min-width: 115px;
}
/* Main Nav Mobile */
.nav-drawer-container >:not(.nav-tabgroup-container) a.flhc {
  position: absolute;
  top: 0.15em;
  right:0;
  bottom:0;
  left: 1em;
}
.nav-drawer-container button {
  background-color: #fff;
  color: #006699 !important;
  letter-spacing: 0.01em !important;
  margin-top: 0.75em;
  margin-left: 0.75em;
  text-transform: capitalize !important;
  transition: 0.5s all ease;
}
.nav-drawer-container button:hover {
  background-color: #ecf1f7 !important;
}
.nav-drawer-container button:focus, .nav-drawer-container button:focus-visible {
  outline: none;
}
.nav-btn-hide, .nav-btn-open {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='%23006699' stroke='%23006699' stroke-linecap='round' stroke-miterlimit='10' stroke-width='.8' viewBox='0 0 16 16'%3E%3Cpath d='M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z'/%3E%3C/svg%3E");
  background-size: 20px;
  background-repeat: no-repeat;
  background-position: top left;
  height: 20px;
  padding-left: 1.35rem;
}
.nav-btn-open {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='%23006699' stroke='%23006699' stroke-linecap='round' stroke-miterlimit='10' stroke-width='.6' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M2.5 12a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E") !important;
  padding-left: 1.5rem !important;
}
.nav-drawer-container .mdc-drawer-app-content:has(button.mdc-button) > div {
  display: flex;
  justify-content: flex-end;
  padding-right: 1.75em;
}
.nav-drawer-container:has(.nav-btn-hide) button {
  background-color: #ecf1f7 !important;
  z-index: 100;
}
.mat-drawer.mdc-drawer--open:not(.mdc-drawer--closing)+.mdc-drawer-app-content, .mdc-drawer.mdc-drawer--open:not(.mdc-drawer--closing)+.mdc-drawer-app-content {
  margin: initial !important;
}
.nav-drawer-container > aside, .nav-tabgroup-container .nav-list {
  background-color: #006699;
  color: #fff;
}
.nav-drawer-container > aside.mat-drawer, .nav-tabgroup-container .nav-list {
  width: 100%;
}
.nav-drawer-container > aside .nav-description, .nav-drawer-container > aside .nav-list, .nav-tabgroup-container .nav-list .nav-description, .nav-tabgroup-container .nav-list {
  padding: 0.7em;
}
.nav-drawer-container > aside .nav-description, .nav-tabgroup-container .nav-list .nav-description {
  background-color: rgba(0,0,0,.1);
  border-radius: 4px;
  grid-column: 1/-1;
  width: 100%;
}
.nav-drawer-container .nav-description {
  font-size: 0.8em;
  font-weight: 600;
  letter-spacing: 0.01em;
  line-height: 1.4em;
}
.nav-drawer-container .nav-description small {
  font-size: 1em;
  font-weight: normal;
}
.nav-drawer-container > aside .nav-list, .nav-tabgroup-container .nav-list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  grid-gap: 1.3em;
  margin: 0 auto;
}
.nav-drawer-container > aside .nav-list ul li, .nav-tabgroup-container .nav-list ul li {
  margin: 0.7em 0;
}
.nav-drawer-container > aside .nav-list h4, .nav-tabgroup-container .nav-list h4 {
  border-bottom: 2px solid #fff6;
  display: inline-block;
  font-weight: 600;
  font-size: 0.9em;
}
.nav-drawer-container > aside .nav-list ul, .nav-tabgroup-container .nav-list ul {
  font-size: 0.9em;
  font-weight: normal;
  list-style: none;
  margin-left: 0;
  padding-left: 0;
}
.nav-drawer-container > aside .nav-list ul li a, .nav-tabgroup-container .nav-list ul li a {
  color: #fff;
  text-decoration: underline;
  text-decoration-color: transparent;
  transition: 0.2s all ease;
}
.nav-drawer-container > aside .nav-list ul li a:hover, .nav-tabgroup-container .nav-list ul li a:hover {
  text-decoration-color: #fff;
}
/* Main Nav Mobile Accordion */
.mat-accordion:has(.nav-list) {
  border-top: 1px solid rgba(0,0,0,.2);
  margin-top: 3.5em;
}
.mat-accordion .mat-expansion-panel:has(.nav-list) {
  background-color: transparent;
  border-bottom: 1px solid rgba(0,0,0,.2);
  box-shadow: none;
}
.mat-expansion-panel__content:has(.nav-list) {
  background-color: #005580;
}
.mat-accordion:has(.nav-list) .mat-expansion-panel__summary .after {
  font-size: 1.85em !important;
  -webkit-text-stroke: 0.02em #006699;
}
/* Default Accordion Overrides */
.mat-accordion .mat-expansion-panel.mat-expansion-panel--expanded {
  margin-top: 0 !important;
  margin-bottom: 0 !important;
}
.mat-accordion .mat-expansion-panel__summary {
  height: 60px !important;
}
.mat-expansion-panel__header {
  flex-basis: 50% !important;
  font-size: 1em !important;
}
/* Hero */
#hero {
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: #00a0af !important;
  overflow: hidden;
  position: relative;
  padding: 1em 0;
  margin: 50px 0 200px 0;
  z-index: 99;
  transition: 0.3s;
}
#hero:before {
  content: "";
  background-color: #008ca0;
  background: rgb(0,140,160);
  background: -moz-radial-gradient(circle, rgba(0,140,160,0.7) 0%, rgba(0,140,160,1) 60%, rgba(0,140,160,1) 90%);
  background: -webkit-radial-gradient(circle, rgba(0,140,160,0.7) 0%, rgba(0,140,160,1) 60%, rgba(0,140,160,1) 90%);
  background: radial-gradient(circle, rgba(0,140,160,0.7) 0%, rgba(0,140,160,1) 60%, rgba(0,140,160,1) 90%);
  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr="#00b4cc",endColorstr="#008ca0",GradientType=1);
  position: absolute;
  bottom: 0;
  top: 0;
  left: 0;
  right: 0;
  opacity: .5;
  mix-blend-mode: overlay;
}
#hero h1 {
  color: #fff;
  font-family: "Raleway", sans-serif;
  font-size: 48px;
  font-variant: lining-nums;
  font-weight: 700;
  text-align: center;
}
#hero p {
  font-family: "Open Sans", sans-serif;
  width: 80%;
  -webkit-animation-delay: 0.4s;
  animation-delay: 0.4s;
  margin: 0 auto 30px auto;
  color: #fff;
}
#hero .btn-get-started {
  font-family: "Raleway", sans-serif;
  font-weight: 600;
  font-size: 14px;
  font-variant: lining-nums;
  letter-spacing: 1px;
  display: inline-block;
  padding: 12px 32px;
  border-radius: 50px;
  transition: 0.5s;
  line-height: 1;
  margin: 10px;
  color: #fff;
  -webkit-animation-delay: 0.8s;
  animation-delay: 0.8s;
  border: 2px solid #ffa927;
}
#hero .btn-get-started:hover {
  background: #ffa927;
  color: #fff;
  text-decoration: none;
}
/* Hero Inner */
#hero.inner {
  min-height: 200px;
}
#hero.inner h1 {
  margin-top: 100px;
  margin-bottom: -30px;
  font-size: 1.85em;
  font-weight: bold;
  text-shadow: 0px 2px 20px rgba(0, 102, 153, 0.75);
  text-align: center;
}
/* Hero Search Bar */
#hero .herosearch {
  width: 840px;
  margin: 0 auto;
  text-align: center;
  padding-top: 40px;
}
#hero .herosearch div {
  color: #fff;
  position: relative;
  padding: 30px;
}
#hero .herosearch div h1 {
  text-shadow: 0px 2px 20px rgba(0, 102, 153, 0.75);
  font-size: 2em;
  font-weight: bold;
}
#hero .herosearch div p {
  text-shadow: 0px 1px 10px rgba(0, 102, 153, 1);
  font-size: 1.15em;
  font-weight: 600;
  line-height: 1.2em;
  width: 60%;
}
#hero .herosearch form {
  box-shadow: 0px 2px 10px 8px rgba(0, 102, 153, 0.15);
  margin-top: 30px;
}
#hero .herosearch form input[type=search] {
  width: 100%;
}
/* Breakpoint and Down - Hero Styles */
@media (min-width: 1024px) {
  #hero p {
    width: 60%;
  }
}
@media screen and (max-width: 991px) {
  #hero .herosearch div {
    padding: 30px 70px;
  }
  #hero .herosearch div h1 {
    font-size: 1.8em;
  }
  #hero .herosearch div p {
    font-size: 1em;
  }
}
@media screen and (max-width: 768px) {
  #hero .herosearch div h1 {
    font-size: 1.7em;
  }
  #hero .herosearch div p {
    width: 90%;
  }
}
@media screen and (max-width: 414px) {
  #hero .herosearch div h1 {
    font-size: 1.5em;
    width: 100%;
  }
  #hero .herosearch div p {
    width: 100%;
  }
  #hero .herosearch form.roundsearchbar input[type=submit] {
    background-image: url("data:image/svg+xml,%3Csvg version='1.1' id='Layer_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' viewBox='0 0 8 8' style='enable-background:new 0 0 8 8;' xml:space='preserve'%3E%3Cstyle type='text/css'%3E .st0%7Bfill:none;stroke:%23FFFFFF;stroke-width:0.6;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;%7D .st1%7Bfill:none;stroke:%23FFFFFF;stroke-width:0.9;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;%7D%0A%3C/style%3E%3Ccircle class='st0' cx='3.5' cy='3.5' r='2'/%3E%3Cline class='st1' x1='5.1' y1='5.1' x2='6.5' y2='6.5'/%3E%3C/svg%3E");
    background-position: center center;
    background-repeat: no-repeat;
    background-size: 1.9em;
    padding: 0 2em;
    text-indent: -999em;
    }
}
/* Round Search Bar */
form.roundsearchbar {
  background: #fff;
  border-radius: 50px;
  height: 45px;
  position: relative;
  overflow: hidden;
}
form.roundsearchbar input[type=search] {
  border: 0;
  border-radius: 50px;
  height: 45px;
  padding-left: 1.3em;
  text-align: left;
}
form.roundsearchbar input[type=submit] {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  border: 0;
  background: none;
  font-size: 16px;
  font-weight: 600;
  padding: 0 25px;
  margin: 3px;
  background: #ffa927;
  color: #fff;
  transition: 0.3s;
  border-radius: 50px;
}
form.roundsearchbar input[type=submit]:hover {
  background: #f68e1e;
}
form.roundsearchbar:has(input.form-control:focus) {
  outline: 5px solid #fff4 !important;
  transition: all 0.1s ease;
}
/* Add some breathing room to mobile search bar */
form.form-inline.roundsearchbar {
  width: 85% !important;
}
/* Switch Placeholder Text */
form.roundsearchbar input[type=search]:focus + .switch-text {
  display: none;
}
.switch-text {
  background-color: #fff;
  border: none;
  border-radius: 50px;
  color: #6c757d;
  display: flex;
  pointer-events: none;
  position: absolute;
  top: 0;
  left: 0;
  height: 45px;
  width: 100%;
}
.switch {
  display: flex;
  flex-direction: column;
  font-style: normal;
  justify-content: center;
  padding-left: 1.3em;
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  opacity: 0;
  animation: changeword 12s ease infinite;
}
.switch:nth-child(1) {
  animation-delay: 0s;
}
.switch:nth-child(2) {
  animation-delay: 3s;
}
.switch:nth-child(3) {
  animation-delay: 6s;
}
.switch:nth-child(4) {
  animation-delay: 9s;
}
@keyframes changeword {
  0% {
    transform: translateY(-40px);
    opacity: 0;
  }
  2% {
    opacity: 0.8;
    transform: translateY(0);
  }
  20% {
    opacity: 1;
    transform: translateY(0);
  }
  30% {
    opacity: 0;
    transform: translateY(0);
  }
  80% {
    opacity: 0;
    transform: translateY(0);
  }
}
<section class="nav-drawer-container">
        <div class="nav-tabgroup-container">
            <div class="topbar">Top Bar</div>
            <div class="menubar">
                <a class="flhc" href="#" aria-label="home">Menu Bar</a>
            </div>
        </div>
</section>

<section id="hero">
    <div class="herosearch">
        <div>
            <h1>Test Title</h1>
            <p>Some information about the tests results.</p>
    
            <form class="roundsearchbar" role="search">
                <input class="form-control" type="search" placeholder="Start typing to search" aria-label="Search">
                <span class="switch-text">
                    <em class="switch">Type a test word</em>
                    <em class="switch">Find a specific test</em>
                    <em class="switch">Search for tests</em>
                    <em class="switch">Find more tests</em>
                </span>
                <input type="submit" value="Search">
            </form>
        </div>
    </div>
</section>


Solution

  • Based on your REPL, you only need to apply a class conditionally on the <div> element: https://blazorrepl.telerik.com/GSblHEYs23Ixff1N58

    @inject IJSRuntime JS 
    
    <script suppress-error="BL9992">
        window.getScrollTop = (selector) => {
            return document.querySelector(".div").scrollTop
        }
    </script>
    
    <style>
        .div {
            background: #ccc;
        }
        .scrolled {
            background: #6f6;
        }
    </style>
    
    <h1>DIV scrollTop: @ScrollTop</h1>
    
    <div class="@( $"div {ConditionalScrollClass}" )" style="overflow:scroll; height:400px;" @onscroll="@OnScroll">
    
        <div style="height:200vh;">Scroll me more than 50px</div>
    
    </div>
    
    @code {
        public int ScrollTop { get; set; }
    
        private string ConditionalScrollClass => ScrollTop >= 50 ? "scrolled" : string.Empty;
    
        private async Task OnScroll(EventArgs e)
        {
            ScrollTop = await JS.InvokeAsync<int>("getScrollTop", ".div");
        }
    }
    

    However, this is not a good approach in Blazor for events like scroll, which fire too many times in succession. That's why, you can consider this alternative algorithm:

    1. Use pure JavaScript (addEventListener instead of @onscroll) to attach the scroll event handler and execute it.
    2. Apply the CSS class client-side, or use client-to-server JSInterop only when the CSS class should be added or removed. This will reduce the number of JSInterop calls considerably.

    Here is an example: https://blazorrepl.telerik.com/mIFbxEYD30Mb0vCy11

    @implements IDisposable
    
    @inject IJSRuntime js
    
    <p>Last JSInterop at: @LastJSInterop</p>
    
    <div id="div" class="@( ShouldApplyScrollClass ? "scrolled" : string.Empty )">
        <div style="height:200vh;"></div>
    </div>
    
    <style>
        #div {
            height: 400px;
            overflow: auto;
            background-color: #ccc;
        }
    
        #div.scrolled {
            background-color: #6f6;
        }
    </style>
    
    @* Move JavaScript code to a separate JS file *@
    <script suppress-error="BL9992">
        var dotNet;
        var previousScrollTop = 0;
    
        function saveDotNetRef(dotNetRef) {
            dotNet = dotNetRef;
    
            let div = document.getElementById("div");
            if (div) {
                div.addEventListener("scroll", onDivScroll);
            }
        }
    
        function onDivScroll(args) {
            let newScrollTop = args.target.scrollTop;
            if (previousScrollTop < 50 && newScrollTop >= 50) {
                dotNet.invokeMethodAsync("ToggleScrollClass", true);
            } else if (previousScrollTop >= 50 && newScrollTop < 50) {
                dotNet.invokeMethodAsync("ToggleScrollClass", false);
            }
    
            previousScrollTop = newScrollTop;
        }
    </script>
    
    @code {
        #nullable enable
    
        // Replace __Main with your Razor class
        private DotNetObjectReference<__Main>? DotNetRef { get; set; }
    
        private bool ShouldApplyScrollClass { get; set; }
    
        private string LastJSInterop { get; set; } = string.Empty;
    
        [JSInvokable("ToggleScrollClass")]
        public void ToggleScrollClass(bool isScrolled)
        {
            ShouldApplyScrollClass = isScrolled;
            LastJSInterop = DateTime.Now.ToString("HH:mm:ss.fff");
            StateHasChanged();
        }
    
        protected override void OnInitialized()
        {
            DotNetRef = DotNetObjectReference.Create(this);
        }
    
        protected override async Task OnAfterRenderAsync(bool firstRender)
        {
            if (firstRender)
            {
                await Task.Delay(1); // ensure HTML is ready
                await js.InvokeVoidAsync("saveDotNetRef", DotNetRef);
            }
    
            await base.OnAfterRenderAsync(firstRender);
        }
    
        public void Dispose()
        {
            DotNetRef?.Dispose();
        }
    }