Search code examples
javascripttypescriptrxjssmooth-scrolling

RXJS how to implement a smooth scroll


I am trying to implement a smooth scroll functionality using RXJS.

The following code scrolls smoothly but not as intended. it takes much longer time than the duration to reach the target.

Any idea how to fix it?

const scrollableEl = document.getElementById('view');
const btn = document.getElementById('testBtn');
btn.addEventListener('click', () => {
  scrollTo(scrollableEl, 1000, 300);
});

function scrollTo(scrollableEl: HTMLElement, point: number, duration: number) {

  timer(0, 20, animationFrame).pipe(
    map((elapsedTime: number) => {
      const delta = point - scrollableEl.offsetTop;
      return {
        top: easeInOutQuad(elapsedTime, scrollableEl.offsetTop, delta, duration),
        elapsedTime
      };
    }),
    tap((target) => {
      console.log('scrollTo: ', target);
      scrollableEl.scrollTop = target.top;
    }),
    // Stop timer
    takeWhile((target: any) => target.elapsedTime < duration)
  ).subscribe();
}

Here is a reproduction: https://stackblitz.com/edit/rxjs-35vjhu


Solution

  • The timer function doesn't emit elapsed time, its output is a sequence of numbers starting from 0: 0, 1, 2, 3 etc. I think you need to measure elapsed time yourself:

    const startTime = new Date().getTime();
    timer(0, 20, animationFrame).pipe(
      map(() => {
        const elapsedTime = new Date().getTime() - startTime;
        const delta = point - scrollableEl.offsetTop;
        return {
          top: easeInOutQuad(elapsedTime, scrollableEl.offsetTop, delta, duration),
          elapsedTime
        };
      }),
      tap((target) => {
        console.log('scrollTo: ', target);
        scrollableEl.scrollTop = target.top;
      }),
      // Stop timer
      takeWhile((target: any) => target.elapsedTime < duration),
    ).subscribe();
    

    This is updated example: https://stackblitz.com/edit/rxjs-dr44gy