2
\$\begingroup\$

I have made a timeline component that can have multiple milestone/major/minor as sub components nested within timeline container component. I have also added a side navigation for that lists all Milestones. Therefore I append range property to TimelineMilestoneComponent which contains the top position value(offestTop) of its and consecutive element. like below

arr[i + 1] ? arr[i + 1].htmlEl.offsetTop - 1 : Infinity

Scroll Listener checks scroll position with with range values to decide if that is current viewed milestone.

Is the implementation correct? What is better way of accomplishing this?

Rendered component

Here is the component Class

export class TimelineComponent implements OnInit, AfterContentInit {
  milestones: Array<ExtendedTimelineMilestoneComponent>;
  visbleMilestone: ExtendedTimelineMilestoneComponent;

  @Input()
  themeColor = "#254267";

  @HostBinding("attr.style")
  public get valueAsStyle(): any {
    return this.sanitizer.bypassSecurityTrustStyle(
      `--theme-color: ${this.themeColor}`
    );
  }

  @HostListener("window:scroll", ["$event.target"])
  public scrollListener(doc: HTMLDocument): void {
    //avoid update visbleMilestone if scroll is within its scope
    if (
      window.scrollY < this.visbleMilestone.range[0] &&
      window.scrollY > this.visbleMilestone.range[1]
    ) {} else {
      this.milestones.every(m => {
        if (window.scrollY >= m.range[0] && window.scrollY < m.range[1]) {
          this.visbleMilestone = m;
          return false;
        }
        return true;
      });
    }

  }

  @ContentChildren(TimelineMilestoneComponent)
  set _milestones(milestones: QueryList<TimelineMilestoneComponent>) {

    this.milestones = milestones.map((m, i, arr) =>
      Object.assign(m, {
        range: [
          m.htmlEl.offsetTop,
          arr[i + 1] ? arr[i + 1].htmlEl.offsetTop - 1 : Infinity
        ],
      })
    );
  }

  constructor(private sanitizer: DomSanitizer) {}

  ngOnInit() {}

  ngAfterContentInit() {
    this.visbleMilestone = this.milestones[0] ? this.milestones[0] : null;
  }

  scrollToMilestone(m: TimelineMilestoneComponent) {
    m.htmlEl.scrollIntoView({ behavior: "smooth" });
  }
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.