1.0.1 • Published 2 years ago

@ep44/trackable-nav-sections v1.0.1

Weekly downloads
-
License
MIT
Repository
github
Last release
2 years ago

TrackableSections

Introduction

TrackableSections is small library that aims to help with keeping track of wich content user sees on the screen and keep taht in sync witch yor navigation.

It might be useful for creating Table of Contents or other situations where contents of document or container are very large and scrolling distance is huge.

Install

Accordingly to your package manager.

npm install @ep44/trackable-nav-sections

Your projet must use angular@17.0.0 or higher.

Usage

Play around with live sample -> https://stackblitz.com/~/github.com/EP-coode/trackable-nav-sections <-

Basic usage

import { Component, inject } from '@angular/core';
import { CommonModule } from '@angular/common';

import {
  TrackSectionDirective,
  TrackableSectionService,
} from 'trackable-nav-sections';

@Component({
  selector: 'ns-vertical-list-full',
  standalone: true,
  // Inport TrackSectionDirective direcite   
  imports: [CommonModule, TrackSectionDirective],
  // TrackSectionDirective needs to be provided with TrackableSectionService in scrolling context
  providers: [TrackableSectionService],
  templateUrl: './vertical-list-full.component.html',
  styleUrl: './vertical-list-full.component.scss',
})
export class VerticalListComponentFull {
  // Simply inject service and watch to changes in sections 
  // By default service uses document as scrollin context  
  private trackableSectionsService = inject(TrackableSectionService);
  protected navSections$ = this.trackableSectionsService.watchSections();
}
<nav>
  <ul>
    <!-- You may watch to state of all sections, and render them accordingly to your needs -->
    @for(section of navSections$ | async; track section.sectionId){
    <li
      [style.color]="section.isIntersecting ? 'red' : 'gray'"
      [style.opacity]="section.isActive ? '1' : '0.3'"
    >
      <span>{{ section.sectionDisplayName }}</span>
    </li>
    }
  </ul>
</nav>

<section>
    <!-- For each section you must provide unique id.  -->
    <!-- You may also set custom display name if id of section is not for you a sufficient data.  -->
  <div tnTrackableSection="section1" tnTrackableSectionDispalyName="S1">SECTION 1</div>
  <div tnTrackableSection="section2" tnTrackableSectionDispalyName="S2">SECTION 2</div>
  <div tnTrackableSection="section3" tnTrackableSectionDispalyName="S3">SECTION 3</div>
  <div>NOT TRACKED SECTION</div>
</section>

Effect

image

Custom scrolling context

import { Component, ElementRef, OnInit, inject } from '@angular/core';
import { CommonModule } from '@angular/common';

import {
  TrackSectionDirective,
  TrackableSectionService,
} from 'trackable-nav-sections';

@Component({
  selector: 'ns-vertical-list',
  standalone: true,
  imports: [CommonModule, TrackSectionDirective],
  providers: [TrackableSectionService],
  templateUrl: './vertical-list.component.html',
  styleUrl: './vertical-list.component.scss',
})
export class VerticalListComponent implements OnInit {
  private trackableSectionsService = inject(TrackableSectionService);
  protected navSections$ = this.trackableSectionsService.watchSections();

  private selfRef = inject(ElementRef);

  ngOnInit(): void {
    // Simply pass reference to element that will handle overflow
    this.trackableSectionsService.setScrollingContext(
      this.selfRef.nativeElement
    );
  }
}
<nav>
  <ul>
    @for(section of navSections$ | async; track section.sectionId){
    <li
      [style.color]="section.isIntersecting ? 'red' : 'gray'"
      [style.opacity]="section.isActive ? '1' : '0.3'"
    >
      <span>{{ section.sectionDisplayName }}</span>
    </li>
    }
  </ul>
</nav>

<!-- Simply reference element that will handle overflow -->
<section #scrolingContext>
  <div tnTrackableSection="section1" tnTrackableSectionDispalyName="S1">SECTION 1</div>
  <div tnTrackableSection="section2" tnTrackableSectionDispalyName="S2">SECTION 2</div>
  <div tnTrackableSection="section3" tnTrackableSectionDispalyName="S3">SECTION 3</div>
  <div>NOT TRACKED SECTION</div>
</section>

Customization of Intersection detection

import { Component, ElementRef, ViewChild, inject } from '@angular/core';
import { CommonModule } from '@angular/common';

import {
  TRACKABLE_SECTION_CONFIG,
  TrackSectionDirective,
  TrackableSectionService,
} from 'trackable-nav-sections';

@Component({
  selector: 'ns-vertical-list-vertical',
  standalone: true,
  imports: [CommonModule, TrackSectionDirective], 
  providers: [
    TrackableSectionService,
    //  You may customize behavior of IntersectionObserver used to detect if section is visable to adjust lib for yout needs.
    // You may also implement custom strategy of picking active section, by passing activeSrctionPickingStrategy parameter.
    // Just use TRACKABLE_SECTION_CONFIG injection token.
    {
      provide: TRACKABLE_SECTION_CONFIG, 
      useValue: {
        intercectionConfig: {
          threshold: 0.3,
        },
      },
    },
  ],
  templateUrl: './horizontal-list.component.html',
  styleUrl: './horizontal-list.component.scss',
})
export class HorizontalListComponent {
  private trackableSectionsService = inject(TrackableSectionService);
  protected navSections$ = this.trackableSectionsService.watchSections();

  @ViewChild('scrolingContext', { static: true })
  scrolingContext!: ElementRef<HTMLElement>;

  ngOnInit(): void {
    this.trackableSectionsService.setScrollingContext(
      this.scrolingContext.nativeElement
    );
  }
}

Custom active section selection strategy

Default selection filters all sections that fulfil isIntersecting == true predicate. Then it splits bouding rect-s of all sections into verticies. Section with verticiy closest to center of scrolling context is picked as active.

You may also implement custom selection strategy simply by implementing comparator of signature:

export type ActiveItemSelectionStrategy = (
  s1: TrackableSection,
  s2: TrackableSection,
  scrollingContext: Element | Window
) => number;

License

Copyright © 2023 Ernest Przybył, released under the MIT license.

1.0.1

2 years ago

17.0.3

2 years ago

17.0.2

2 years ago

17.0.1

2 years ago