Wheel of fortune

by Boban Stojanovski

HTML

<h1>
End at:
</h1>
<select>
  <option value="0">Stack</option>
  <option value="1">10</option>
  <option value="2">200</option>
  <option value="3">50</option>
  <option value="4">100</option>
  <option value="5">5</option>
  <option value="6">500</option>
</select>

<br>

<div id="wheelOfFortune">
  <canvas id="wheel"></canvas>
  <div id="spin">SPIN</div>
</div>

CSS

#wheelOfFortune {
  display: inline-flex;
  position: relative;
  overflow: hidden;
}

#wheel {
  display: block;
  user-select: none;
}

#spin {
  font: 1.5rem/0 sans-serif;
  user-select: none;
  cursor: pointer;
  display: flex;
  justify-content: center;
  align-items: center;
  position: absolute;
  inset: 35%;
  background: #fff;
  color: #fff;
  box-shadow: 0 0 0 8px currentColor, 0 0px 15px 5px rgba(0, 0, 0, 0.6);
  border-radius: 50%;
  transition: 0.8s;
  -webkit-tap-highlight-color: transparent;
  text-transform: uppercase;
}

#spin::after {
  content: "";
  position: absolute;
  top: -17px;
  border: 10px solid transparent;
  border-bottom-color: currentColor;
  border-top: none;
}

JavaScript

const sectors = [
  { color: "#f82", label: "Stack" },
  { color: "#0bf", label: "10" },
  { color: "#fb0", label: "200" },
  { color: "#0fb", label: "50" },
  { color: "#b0f", label: "100" },
  { color: "#f0b", label: "5" },
  { color: "#bf0", label: "500" },
]

// Generate random float in range min-max:
const rand = (m, M) => Math.random() * (M - m) + m
// Fix negative modulo stackoverflow.com/a/71167019/383904
const mod = (n, m) => ((n % m) + m) % m

const tot = sectors.length
const elSpin = document.querySelector("#spin")
const elWheel = document.querySelector("#wheel")
const ctx = elWheel.getContext`2d`
const dia = 300

ctx.canvas.width = dia
ctx.canvas.height = dia

const rad = dia / 2
const PI = Math.PI
const TAU = 2 * PI
const arc = TAU / tot
const angOffset = TAU * 0.75 // needle is north

let sectorIndex = 0 // Current sector index
let oldAng = 0
let ang = 0 // Angle rotation in radians

let spinAnimation = null
let animationFrameId

//* Get index of current sector */
const getIndex = (ang) => Math.floor(tot - (mod(ang, TAU) / TAU) * tot) % tot

//* Draw sectors and prizes texts to canvas */
const drawSector = (sector, i) => {
  const ang = arc * i
  ctx.save()
  // COLOR
  ctx.beginPath()
  ctx.fillStyle = sector.color
  ctx.moveTo(rad, rad)
  ctx.arc(rad, rad, rad, ang, ang + arc)
  ctx.lineTo(rad, rad)
  ctx.fill()
  // TEXT
  ctx.translate(rad, rad)
  ctx.rotate(ang + arc / 2)
  ctx.textAlign = "right"
  ctx.fillStyle = "#fff"
  ctx.font = `bold ${26 * arc}px sans-serif`
  ctx.fillText(sector.label, rad - 10, 10)
  //
  ctx.restore()
}

const update = () => {
  const currentProgress =
    spinAnimation?.effect.getComputedTiming().progress ?? 0

  const angDiff = ang - oldAng
  const angCurr = angDiff * currentProgress
  const angAbs = mod(oldAng + angCurr, TAU)

  const sectorIndexNew = getIndex(angAbs)
  if (sectorIndex !== sectorIndexNew) {
    // Sector changed! Make a tick sound
  }
  sectorIndex = sectorIndexNew
  elSpin.textContent =...