All files / Rindu/components/ScrollableText ScrollableText.tsx

2.17% Statements 1/46
0% Branches 0/10
0% Functions 0/8
2.32% Lines 1/43

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 9919x                                                                                                                                                                                                    
import { PropsWithChildren, ReactElement, useEffect, useRef } from "react";
 
interface IScrollableText {
  speedPxSeconds?: number;
  delay?: number;
}
 
export default function ScrollableText({
  children,
  speedPxSeconds = 20,
  delay = 5000,
}: PropsWithChildren<IScrollableText>): ReactElement {
  const ref = useRef<HTMLDivElement>(null);
 
  useEffect(() => {
    let direction = -1;
    Iif (!ref.current) return;
 
    const container = ref.current;
    const text = container.firstElementChild as HTMLElement;
    const distanceToMove = -(text.scrollWidth - text.offsetWidth);
 
    let lastTime: number | undefined;
    let animationFrameId: number;
    let distanceMoved = 0;
    let delayedTimerId: NodeJS.Timeout | undefined;
    text.style.transform = "translateX(0px)";
 
    Iif (text.scrollWidth <= container.offsetWidth) return;
 
    const update = (timestamp: number) => {
      const elapsedTime = lastTime ? timestamp - lastTime : 0;
      distanceMoved += (direction * speedPxSeconds * elapsedTime) / 1000;
      lastTime = timestamp;
 
      function setNewAnimationFrame(delay?: number) {
        Iif (!delay) {
          animationFrameId = requestAnimationFrame(update);
          return;
        }
        delayedTimerId = setTimeout(() => {
          lastTime = undefined;
          animationFrameId = requestAnimationFrame(update);
        }, delay);
      }
 
      function switchDirection() {
        direction = -direction;
        setNewAnimationFrame(delay);
      }
 
      Iif (distanceMoved < distanceToMove) {
        text.style.transform = `translateX(${distanceToMove}px)`;
        distanceMoved = distanceToMove;
        switchDirection();
        return;
      }
 
      Iif (distanceMoved > 0) {
        text.style.transform = "translateX(0px)";
        distanceMoved = 0;
        switchDirection();
        return;
      }
 
      text.style.transform = `translateX(${distanceMoved}px)`;
      setNewAnimationFrame();
    };
 
    const initialTimerId = setTimeout(() => {
      lastTime = undefined;
      requestAnimationFrame(update);
    }, delay);
 
    return () => {
      clearTimeout(initialTimerId);
      cancelAnimationFrame(animationFrameId);
      Iif (delayedTimerId) clearTimeout(delayedTimerId);
    };
  }, [speedPxSeconds, delay, children]);
 
  return (
    <div ref={ref} className="container">
      <div className="text">{children}</div>
      <style jsx>{`
        .container {
          position: relative;
          overflow: hidden;
        }
        .text {
          white-space: nowrap;
          width: 100%;
          display: flex;
        }
      `}</style>
    </div>
  );
}