본문 바로가기
개발/리액트

[리액트] 부드럽게 이동하는 숫자 컴포넌트를 구현하는 방법(feat.타입스크립트)

by 핸디(Handy) 2021. 5. 13.

사이드 프로젝트를 진행하면서 다른 웹페이지를 많이 보고 공부하고 있습니다.

그러던 와중에 로빈후드의 주가 차트 창에서 숫자가 미끄러지며 나타나는 기능을 봤는데 매우 역동적이고 멋져 보여서 한번 구현해볼까 생각하게 되었습니다.

로빈후드의 차트창

계속 찾아보니 JQuery로 구현하는 방법은 많이 있습니다. 그리고 간단하기도 했습니다.

하지만 저는 JQuery는 안쓰자는 주의라(실은 제대로 공부해본 적이 없어서 가져다 쓰기가..) 순수 JS나 CSS로 구현된 것이 있나 살펴보았지만

딱 로빈후드만큼 역동적으로 지원하는 코드는 없어서 한번 구현해보기로 했습니다.

단순히 숫자가 올라가는 코드의 경우 해당 숫자 영역을 잡고 JS코드로 빠르게 숫자를 올려주면 되는 것이지만 저건 스크롤하듯 올라가거나 내려가야 해서 고민했습니다.


구현 시작

고민끝에 아이디어를 떠올렸습니다.

0,1,2,3,4,5,6,7,8,9를 세로 정렬로 쭉 세운 후에 특정 값이 들어왔을 때 scroll를 해당 위치로 이동시키면 비슷한 움직임을 보여주지 않을까 생각했습니다.

import React, { useEffect } from "react";
import { makeStyles } from "@material-ui/core/styles";

const useStyles = makeStyles({
  root: {
    display: "flex",
    justifyContent: "center",
  },
  numberLayout: {
    display: "flex",
    flexFlow: "column",
    height: "1.5rem",
    overflowY: "hidden",
  },
});
interface PriceScrollerProp {
  numberLength: number;
  currentNumber: string | number;
  prefix?: string;
  suffix?: string;
}
export default function PriceScroller({ numberLength, currentNumber, prefix, suffix }: PriceScrollerProp) {
  const classes = useStyles();
  useEffect(() => {
    for (let i = 0; i < numberLength; i++) {
      setTimeout(() => {
        let targetNumberEl: HTMLElement | null = document.getElementById(`${i}_${currentNumber.toString()[i]}`);
        if (targetNumberEl) {
          targetNumberEl.scrollIntoView();
        }
      }, i * 250);
    }
  }, [numberLength, currentNumber]);

  return (
    <div className={classes.root}>
      <>
        {prefix}
        {[...Array(numberLength)].map((item, index) => {
          return (
            <span className={classes.numberLayout} key={"test" + index}>
              <div>-</div>
              <div id={`${index}_9`}>9</div>
              <div id={`${index}_8`}>8</div>
              <div id={`${index}_7`}>7</div>
              <div id={`${index}_6`}>6</div>
              <div id={`${index}_5`}>5</div>
              <div id={`${index}_4`}>4</div>
              <div id={`${index}_3`}>3</div>
              <div id={`${index}_2`}>2</div>
              <div id={`${index}_1`}>1</div>
              <div id={`${index}_0`}>0</div>
            </span>
          );
        })}
        {suffix}
      </>
    </div>
  );
}

 

컴포넌트 사용 방법

const [hoverPrice, setHoverPrice] = useState<string>("1000"); // 초기값 1000으로 설정

setHoverPrice(number); // 원하는 값을 넣어줌

<PriceScroller numberLength={hoverPrice.length} currentNumber={hoverPrice} suffix={"만원"} />

 

컴포넌트 모습

구현 전 vs 구현 후


구현 후기

일단 컴포넌트를 보시면 로빈후드보다 역동적으로 확확 변하지 않습니다.

로빈후드의 창을 보니 class를 변경하여 해당 엘리먼트에 css를 변경하는 방식으로 구현한 것 같은데 저는 JS만 구현하다 보니 약간 투박합니다.

또한 문제가 scrollIntoView() 함수 자체가 속도가 느립니다. 직접 유저 UI를 변경해야하는 작업이라 그런 듯합니다.

딜레이 없이 코드를 동작시키면 앞에껀 전부 무시되고 마지막에 호출된 scrollIntoView()만 적용됩니다.

따라서 각 실행마다 250ms의 간격을 두고 실행했습니다. 순차적으로 변경되는 듯한 모습이긴 한데 역동성이 떨어졌습니다.

또한 스크롤을 빠르게 진행되다보니 해당 값으로 슬라이딩하는 모습으로 보이지 않는 듯하여 많이 아쉽습니다.

다음에는 css animation, ket frame을 통해 해당 컴포넌트를 업데이트 하는 방향으로 고민해야겠습니다.

감사합니다.

댓글