How to Implement a Scrollable Category Bar with Dynamic Item Positioning in Angular (Like Uber eats scroll bar)

132 views Asked by At

I am developing an Angular application that features a page with categorized items. I want to include a scrollable category bar at the top of the page, which displays all the available categories. As the user scrolls down the page, I would like the position of the items inside the category bar to change dynamically, highlighting the category that corresponds to the currently viewed content. How can I achieve this effect?

I have tried using a scroll to view to get to the part on the page but i cannot change the positioning on the vertical scroll bar

Here is the code: Component Type ``Typescript

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

@Component({
  selector: 'app-content',
  templateUrl: './content.component.html',
  styleUrls: ['./content.component.css']
})
export class ContentComponent {

  @ViewChild('pageContent') pageContent!: ElementRef;

  scrollToCategory(category: string) {
    const element = document.getElementById(category);
    if (element) {
      element.scrollIntoView({ behavior: 'smooth' });
    }
  }

  categories = ['Category 1', 'Category 2', 'Category 3','Category 4', 'Category 5', 'Category 6', 'Category 7','Category 8'];
  items : any = {
    'Category 1': ['Item 1', 'Item 2', 'Item 3'],
    'Category 2': ['Item 4', 'Item 5', 'Item 6'],
    'Category 3': ['Item 7', 'Item 8', 'Item 9'],
    'Category 4': ['Item 7', 'Item 8', 'Item 9'],
    'Category 5': ['Item 1', 'Item 2', 'Item 3'],
    'Category 6': ['Item 4', 'Item 5', 'Item 6'],
    'Category 7': ['Item 7', 'Item 8', 'Item 9'],
    'Category 8': ['Item 7', 'Item 8', 'Item 9'],
    'Category 9': ['Item 1', 'Item 2', 'Item 3'],
    'Category 10': ['Item 4', 'Item 5', 'Item 6'],
    'Category 11': ['Item 7', 'Item 8', 'Item 9'],
    'Category 12': ['Item 7', 'Item 8', 'Item 9'],
    'Category 13': ['Item 1', 'Item 2', 'Item 3'],
    'Category 14': ['Item 4', 'Item 5', 'Item 6'],
    'Category 15': ['Item 7', 'Item 8', 'Item 9'],
    'Category 16': ['Item 7', 'Item 8', 'Item 9'],
    'Category 17': ['Item 1', 'Item 2', 'Item 3'],
    'Category 18': ['Item 4', 'Item 5', 'Item 6'],
    'Category 19': ['Item 7', 'Item 8', 'Item 9'],
    'Category 20': ['Item 7', 'Item 8', 'Item 9'],
    
  };

HTML:

<div class="category-bar">
    <button *ngFor="let category of categories" (click)="scrollToCategory(category)">{{ category }}</button>
  </div>
  
  <div class="page-content">
    <div *ngFor="let category of categories" id="{{ category }}">
      <h2>{{ category }}</h2>
      <div>
      
      </div>
      <div *ngFor="let item of items[category]">{{ item }}</div>
    </div>
  </div>
  
      
    }
1

There are 1 answers

0
Anand Khandalekar On

In order to highlight the respective category in the header, you need to get the bounds of the current category inside the scroll view.

Add the below code in the .ts to detect the child element (of the categories) that is in current scroll view.

    ngAfterViewInit() {
    this.pageContent?.nativeElement.addEventListener('scroll', (e: any) => {
      this.onScroll(e);
    });
  }

  onScroll(event: any) {
    const childCatefories = [...event.target.children];
    for(let i=0; i< childCatefories.length; i++) {
      const cat = childCatefories[i];
      const rect: any = cat.getBoundingClientRect();
      const topShown = rect.top >= 0;
      const bottomShown = rect.bottom <= this.pageContent.nativeElement.scrollHeight;
      if(topShown && bottomShown) {
        this.highlightedCat = cat.firstChild.innerText;
        console.log(this.highlightedCat);
        break;
      }
    }
  }

You can add an active class to your highlighted category using ngClass & give some style to it.

    <div class="category-bar">
  <button [ngClass]="{'active': highlightedCat === category }" *ngFor="let category of categories" (click)="scrollToCategory(category)">{{ category }}</button>
</div>

<div class="page-content" #pageContent>
  <div class="category-info" *ngFor="let category of categories" id="{{ category }}">
    <h2>{{ category }}</h2>
    <div>
    
    </div>
    <div *ngFor="let item of items[category]">{{ item }}</div>
  </div>
</div>

The below css will help in a better visualisation (can be left out if not needed).

.category-bar {
  height: 3rem;
}

button.active {
  background-color: green;
}

.page-content {
  height: calc(100vh - 6rem);
  position: relative;
  overflow: auto;
}

.page-content .category-info {
  height: 50vh;
}