Search code examples
angularng-bootstrapscrollspy

ng-bootstrap scrollspy doesn't work without height?


I am using ng-bootstrap [ngbScrollSpy] directive in my project, as mentioned in the documentation, but it didn't work - on scroll the active item doesn't change.

My code is the following:

<div>
    <div class="sticky-top">
        <ul class="nav menu-sidebar">
            <li >
                <a [ngbScrollSpyItem]="[spy, 'about']">About</a>
            </li>
            <li >
                <a [ngbScrollSpyItem]="spy" fragment="schedule">Schedule</a>
            </li>
            <li >
                <a [ngbScrollSpyItem]="spy" fragment="hotel">Information about the hotel</a>
            </li>
        </ul>
    </div>

    <div ngbScrollSpy #spy="ngbScrollSpy" >
        <section ngbScrollSpyFragment="about">
            <h3>About</h3>
            <p>{{some long long text and content}}</p>
        </section>
        <section ngbScrollSpyFragment="schedule">
            <h3>Schedule</h3>
            <p>{{some long long text and content}}</p>
        </section>
        <section ngbScrollSpyFragment="hotel">
            <h3>Information about the hotel</h3>
            <p>{{some long long text and content}}</p>
        </section>
    </div>
</div>

I saw in this stackoverflow question that my problem is that I didn't provide height to my div, and that's true.

but my scroll spy sections spread through the whole page, not a small div, (the nav itself is sticky-top). so I cannot give it height.

I understood that there is alternative way - to refresh the scrollspy on window scroll, but I don't find correct code that may help me.

can you solve my problem? provide me code for refreshing the scrollspy / give me tips about the height / help me to find another corresponding element.

thanks a lot!

attaching link to stackblitz demo


Solution

  • Since it seemed to be no solution for my demands, I decided to create my own scrollspy element via two directives:

    1. scrollTo directive will be responsible for scrolling to the section on pressing the li link
    import { Directive, HostListener, Input } from '@angular/core';
    
    @Directive({
      selector: '[scrollTo]'
    })
    export class ScrollToDirective {
    
      @Input() target = '';
      constructor() { }
    
      @HostListener('click')
      onClick() {
        const targetElement = document.querySelector(this.target);
        if(targetElement)
          targetElement.scrollIntoView({block: 'start',behavior: 'smooth', inline:'nearest'});
      }
    }
    
    
    1. scrolledTo directive will be responsible for detecting when scrolling to current element
    import { Directive, ElementRef, HostListener, Input } from '@angular/core';
    
    @Directive({
      selector: '[scrolledTo]',
      exportAs: 'scrolledTo'
    })
    export class ScrolledToDirective {
      @Input() isLast:boolean = false;
      focus = false;
    
      constructor(public el: ElementRef) { }
    
      @HostListener('window:scroll', ['$event'])
      onWindowScroll() {
        const elementPosition = this.el.nativeElement.offsetTop;
        const elementHeight = this.el.nativeElement.clientHeight;
    
        //you can change the check according to your requirements
        const scrollPosition = window.pageYOffset - window.screen.height / 2 ;
        this.focus = scrollPosition >= elementPosition && scrollPosition <= elementPosition + elementHeight;
        if(this.isLast)
          this.focus = scrollPosition >= elementPosition;
    
      }
    }
    
    

    and the html will be the following

    <div>
        <div class="sticky-top">
            <ul class="nav menu-sidebar">
                <li >
                    <a scrollTo target="#about" [class.active]="scrolledToElement1.focus">About</a>
                </li>
                <li >
                    <a scrollTo target="#schedule" [class.active]="scrolledToElement2.focus">Schedule</a>
                </li>
                <li >
                    <a scrollTo target="#hotel" [class.active]="scrolledToElement3.focus">Information about the hotel</a>
                </li>
            </ul>
        </div>
    
        <div >
            <section scrolledTo #scrolledToElement1="scrolledTo" id="about">
                <h3>About</h3>
                <p>{{some long long text and content}}</p>
            </section>
            <section scrolledTo #scrolledToElement2="scrolledTo" id="schedule">
                <h3>Schedule</h3>
                <p>{{some long long text and content}}</p>
            </section>
            <section scrolledTo #scrolledToElement3="scrolledTo" id="hotel">
                <h3>Information about the hotel</h3>
                <p>{{some long long text and content}}</p>
            </section>
        </div>
    </div>
    

    credits: