슬롯머신 컴포넌트

  • 슬롯머신 애니메이션을 구현한 컴포넌트입니다.
  • 높은자리가 빠르게 돌아가도록 구현하기 위해서는, 자릿수가 많아질 수록 슬롯의 갯수를 늘리도록 합니다.
  • 모든 자리가 끝에서부터 출발하여, 원래 의도한 숫자 자리로 돌아오도록 translateY합니다.
// index.tsx

import { rem, typo, color as colorMixin } from '@/styles/mixins'
import { css } from '@emotion/react'
import Slot from './Slot'

const wrapperStyle = css([
  typo.HS28,
  {
    height: '1em',
    lineHeight: 1,
    fontVariantNumeric: 'tabular-nums',
    fontSize: `var(--font-size)`,
    color: `var(--color)`,
    overflow: 'hidden',
  },
])

type Props = {
  className?: string
  num?: number
  fontSize?: number
  color?: string
}

const SlotMachine = ({ className, num, fontSize = 28, color = colorMixin.DB1000 }: Props) => {
  return (
    <div
      className={className}
      aria-label={`${num}`}
      css={wrapperStyle}
      style={{ ['--font-size' as any]: rem(fontSize), ['--color' as any]: color }}
    >
      {num
        ? num
            .toLocaleString()
            .split('')
            .map((digitStr, index) => {
              //! toLocaleString이 Number와 ,만 있다고 가정
              if (digitStr === ',') {
                return digitStr
              }

              const digit = Number(digitStr)

              return <Slot key={index} digit={digit} index={index} />
            })
        : null}
    </div>
  )
}
export default SlotMachine

// Slot.tsx

import { keyframes } from '@emotion/react'
import { Fragment } from 'react'

type Props = {
  digit: number
  index: number
}

const transformNone = keyframes`
		to {
			transform: none
		}
	`

const easeOutQuart = 'cubic-bezier(0.25, 1, 0.5, 1)'
const easeInOutQuint = 'cubic-bezier(0.83, 0, 0.17, 1)'
const easeInOutExpo = 'cubic-bezier(0.87, 0, 0.13, 1)'

const Slot = ({ digit, index }: Props) => {
  return (
    <span
      css={{
        position: 'relative',
        top: `var(--top, 0)`,
        display: 'inline-flex',
        flexDirection: 'column',
        transform: `var(--transform)`,
        animation: `${transformNone} 6s ${easeInOutQuint} forwards`,
      }}
      style={{
        ['--top' as any]: -1 * digit + -10 * index + 'em', // 도착점
        ['--transform' as any]: `translateY(calc((100% - ${10 - digit}em)))`, // 0으로 튀고자 함
      }}
    >
      {[...Array(index + 1)].map((_, i) => (
        <Fragment key={i}>
          <span>0</span>
          <span>1</span>
          <span>2</span>
          <span>3</span>
          <span>4</span>
          <span>5</span>
          <span>6</span>
          <span>7</span>
          <span>8</span>
          <span>9</span>
        </Fragment>
      ))}
    </span>
  )
}
export default Slot