티스토리 뷰

룰렛 이벤트를 개발해야 했는데, 구글링이나 npm에는 내가 찾는 기능이 없어서 직접 구현하였다.
css의 animation에서 rotate를 활용하면 되는데 구현해야 할 요구조건은 아래와 같다.

  1. 선택되는 영역이 랜덤이어야 한다. 즉 rotate() 안에 인자로 들어가는 각도 값이 동적으로 변한다.
  2. 대개 룰렛이 돌아갈 때 처음에는 천천히 돌다가 나중에는 가속도가 붙어서 빨라지고 마지막에는 느려지다 멈춘다.
  3. 당첨된 룰렛의 영역이 어떤 값인지 스크립트단에 전달되어야 한다.

우선 템플릿(html)코드를 보자.

<template>
    <div class="container">

    <!-- 룰렛 화살표 -->
    <div class="arrow">▼</div>

    <!-- 회전하는 룰렛 이미지 -->
    <img src="@/assets/roulette.png" class="roulette_content" />

    <!-- 룰렛을 돌리기 시작하는 버튼 -->
    <button @click="onClickStart">Start</button>
  </div>
</template>

템플릿은 간단하게 3개밖에 없다. 룰렛 화살표는 필수는 아니고 당첨된 영역을 확인하기 용이하도록 만들었다.

다음은 스크립트(Vue3 Composition api)이다.

<script>
export default {
  setup(){
    const onClickStart = () => {
      const {startRoulette} = useRoulette();
      startRoulette();
    }
    return{
      onClickStart,
    }
  }
}
</script>

setup()함수에서는 버튼을 눌렀을 때 동작하는 함수만 정의하고, useRoulette() hook을 실행하면 된다.

다음은 가장 핵심인 useRoulette() hook이다. 코드는 굉장히 복잡해보이는데 사실 대부분 사용자가 입맛에 따라 정의하는 하이퍼파라미터이다. 하이퍼파라미터는 변수명을 대문자로 표기하였다.
원리는 간단하다. 룰렛은 원형이니 중심각이 360도이다. 따라서 360/(영역의 개수) 를 계산하여 각 영역당 중심각의 크기를 구할 수 있다. 그리고 해당 영역이 몇 번째인지 알고 있으면 그만큼 룰렛을 rotate시키면 되는 것이다. 그래서 회전시켜야 하는 각도 값을 rouletteAngle변수에 저장하여 style property에 --roulette-angle로 저장시키면 css에서도 이 값을 가져다 쓸 수 있는 방식이다.
다만 요구조건 2에서 룰렛이 돌아가는 속도를 시간에 따라 결정해야하므로 cubic-bezier 커브를 정의하였고 MIN_ROTATION, ROTATION_SECOND 변수를 정의하여 돌아가다가 서서히 멈추는 애니메이션처럼 보이도록 하였다.

const useRoulette = () => {
  const POINT_ARRAY = ["red", "blue", "green", "yellow"]; // 룰렛에 적혀있는 포인트,12시 방향부터 반시계방향 순으로
  const numberOfSection = POINT_ARRAY.length; // 룰렛에 적힌 영역의 개수
  const pick = Math.floor(Math.random() * numberOfSection); // [0, section - 1]범위에 랜덤한 인덱스를 뽑음
  const degPerSection = 360 / numberOfSection; // 하나의 섹션당 각도가 몇 도인지 계산함.
  const MIN_ROTATION = 3; // 룰렛을 최소 몇 바퀴 돌릴 것인지 결정
  const ROTATION_SECOND = 2; // 몇 초동안 돌릴 것인지
  const rouletteAngle = 360 * MIN_ROTATION + degPerSection * pick; // 최소 MIN_ROTATION만큼은 돌고난 후에, pick 영역을 가르키도록 함

  const startRoulette = () => {
    const rouletteEl = document.querySelector(".roulette_content");
    rouletteEl.style.animationName = "spin";
    rouletteEl.style.animationDuration = `${ROTATION_SECOND * 1000}ms`;
    rouletteEl.style.animationTimingFunction =
      "cubic-bezier(0.37, 0.06, 0.63, 0.98)"; //처음에는 빠르게 돌다가 끝에 가면 천천히 돌아가도록 함(수정하려면 개발자도구에서 조절하면 편함).
    rouletteEl.style.animationFillMode = "forwards"; // 룰렛이 멈춘 후에 상태 유지
    document.documentElement.style.setProperty(
      "--roulette-angle",
      rouletteAngle + "deg"
    ); //css의 root에 선언해놓은 변수에 값 할당함.
    const toId = setTimeout(() => {
      alert(POINT_ARRAY[pick]); // arrow 가 가리키는 색깔 alert로 출력
      clearTimeout(toId);
      window.location.reload(); // 다 끝난 후에는 페이지 새로고침
    }, (ROTATION_SECOND + 0.3) * 1000); // 룰렛 멈추고나서 0.3초 뒤에 alert창 출력
  };
  return {
    startRoulette,
  };
};
<style>
    @keyframes spin {
        from {
            transform: rotate(0deg);
        }
        to {
            transform: rotate(var(--roulette-angle));
        }
    }
</style>

이 코드는 룰렛의 한 영역당 중심각이 모두 동일하다는 전제에서만 정상 작동한다. 만약 특정 영역의 확률은 낮추고, 다른 영역을 높이려면 랜덤값을 뽑는 pick 변수 부분을 조정해야 할 것 같다.

'Dev' 카테고리의 다른 글

vue 에서 스크롤 위치 저장  (0) 2023.03.09
프로젝트 회고  (0) 2023.02.18
vue 커스텀 디렉티브(custom directive)  (0) 2022.12.05
Vue3 + typescript + pinia  (0) 2022.11.12
Vue3 타이머 구현  (0) 2022.11.12
댓글