프로그래밍/Vue.js

vuejs 룰렛만들기

p-a-r-k 2023. 5. 22. 14:51
반응형

룰렛을 적용시킬 원판이미지와 배열을 준비한다.

이미지는 테스트로 첨부하니 테스트를 원하면 사용하도록.

exam.zip
0.22MB

/**
 * # 스크립트 준비
 */
// 룰렛결과 배열
const itemList = [
  {value: 0, label: "샌드위치"},
  {value: 1, label: "핫도그"},
  {value: 2, label: "도너츠"},
  {value: 3, label: "마카롱"},
  {value: 4, label: "아이스크림"},
  {value: 5, label: "에그타르트"},
  {value: 6, label: "크로와상"},
  {value: 7, label: "케이크"},
  {value: 8, label: "면세점 상품권"},
  {value: 9, label: "우주패스"},
  {value: 10, label: "영화관 콤보"},
  {value: 11, label: "케이크"},
];
// 항목 개수
const itemLength = computed(() => itemList.length);
// 원판 div 접근 ref
const wacuRef = ref();

html 구성은 container와 원판영역, 버튼영역으로 구성한다. (필요에 따라 수정)

버튼은 img를 사용했으니 필요에 따라 수정.

<div class="roulette-container">
  <div class="rouletter">
    <div class="rouletter-bg">
      <div class="rouletter-wacu" ref="wacuRef"></div>
    </div>
    <div class="rouletter-arrow"></div>
  </div>

  <div class="button-container">
    <a
        href="javascript:void(0)"
        class="btn-start"
        @click="onClickStart">
      <img src="/images/step-two/btn_start.png" alt="start"/>
    </a>
  </div>
</div>

 

버튼에는 onClickStart 메서드가 있다.

roll 메서드에서는 api등을 통해서 값을 받아와도 되고, 랜덤으로 배열의 인덱스를 추출해도된다.

아래는 3번인덱스로 테스트 하드코딩.

roll메서드에서 3번인덱스로 위에서 정의해둔 12개의 배열에서 꺼내온 후 value값을 넘겨준다.

실제 회전은 onRotateStart 메서드에서 진행된다.

 

원판 div에 ref 접근하여, style transform 값의 roate값을 interval로 변경시킨다.

delay라는 메소드로 지연함수를 사용하여 원판이 진행되는동안을 기다리도록 처리한다.

최종 목표 rotate 값은 360/배열 수 * 파라메터로 받은 배열값(인덱스)로 하면 해당하는 타겟에 멈추게 된다.

 

num === 30 부분과 delay(2400) 부분을 적절히 조절하여 속도를 늦추고 당겨줄 수도 있겠다.

// 룰렛 진행중 여부
const isSpining = ref(false);
/**
 * # 룰렛 start
 */
const onClickStart = async () => {
  // business logic

  // 진행중이면 클릭 무시
  if (isSpining.value) return;
  isSpining.value = true;
  await roll();
  
  // after logic
}

async function roll() {
  try {
    // TODO : 당첨 결과 값(상품 고유값) 룰렛 결과로 세팅
    // TEST로 3번 인덱스 당첨
    const idx: number = parseInt("3", 10);
    const drawValue = itemList[idx].value;

    // onRotateStart에는 당첨결과 번호를 넘겨주면 룰렛이 해당 인덱스에 멈춘다.
    await onRotateStart(drawValue);
    
  	// after logic
  } catch (err: any) {
    Alert.error("에러");
  } finally {
    isSpining.value = false;
  }
}

/**
 * # 룰렛 시작
 * @param targetValue: 당첨 결과값
 */
const onRotateStart = async (targetValue: number) => {
  const panel = wacuRef.value;
  const deg = [];
  // 룰렛 각도 설정
  for (let i = 1; i <= itemLength.value; i++) {
    deg.push((360 / itemLength.value) * i);
  }

  let num = 0;
  return new Promise((resolve) => {
    // 애니설정
    let ani = setInterval(async () => {
      num++;
      panel.style.transform = "rotate(" + 360 * num + "deg)";

      // num이 몇까지 돌지 세팅(속도제한)(30)
      if (num === 30) {
        clearInterval(ani);
        panel.style.transform = `rotate(${(360 / itemLength.value) * targetValue}deg)`;
        await delay(2400);
        resolve(true);
      }
    }, 50);
  });
};


// 지연 함수 참고
function delay(ms = 1000) {
    return new Promise((resolve) => setTimeout(resolve, ms));
}

await을 걸어두었기때문에 이후 팝업같은 logic을 추가할 수 있겠다.

아래는 html에 해당하는 scss 예시이다. 

.roulette-container {
  .rouletter {
    position: relative;
    width: 690px;
    height: 690px;
    margin: 0 auto;

    .rouletter-bg {
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
      width: 690px;
      height: 690px;
      border-radius: 50%;
      overflow: hidden;
    }
    .rouletter-wacu {
      width: 100%;
      height: 100%;
      background-image: url(/images/step-two/wacu.png);
      background-repeat: no-repeat;
      background-size: 100%;
      transform-origin: center;
      transition-timing-function: ease-in-out;
      transition: 2s;
    }
    .rouletter-arrow {
      position: absolute;
      top: -30px;
      left: 50%;
      width: 61px;
      height: 82px;
      transform: translateX(-50%);
      background-image: url(/images/step-two/pin.png);
      background-repeat: no-repeat;
      background-size: 100%;
    }
  }
  .button-container {
    margin-top: 83px;
  }
}

.on {
  animation-name: ani;
  animation-duration: 0.1s;
  animation-fill-mode: forwards;
  animation-iteration-count: infinite;
}

@keyframes ani {
  0% {
    transform: rotate(0deg);
    transition-timing-function: ease-out;
  }
  100% {
    transform: rotate(360deg);
  }
}

// 반응형 참고
//@media only screen and (max-width: $mobile-max-width) {
//  .roulette-container {
//    .rouletter {
//      width: vw(690);
//      height: vw(690);
//
//      .rouletter-bg {
//        width: vw(690);
//        height: vw(690);
//      }
//
//      .rouletter-arrow {
//        top: vw(-30);
//        width: vw(61);
//        height: vw(82);
//      }
//    }
//    .button-container {
//      margin-top: vw(83);
//      img {
//        width: vw(500);
//        height: vw(120);
//      }
//    }
//  }
//}

.rouletter-wacu의 transition값을 2s에서 3s이상으로 늘리면 룰렛이 좀 더 천천히 멈추는느낌이고,

1s로 줄이게되면 확 멈추는 느낌이 되겠다.

 

반응형