<div class="container">
  <div id="turntable" class="turntable"></div>
  <div class="arrow"></div>
  <div id="lottery-btn" class="lottery-btn">抽奖</div>
</div>
.container {
  width: 500px;
  padding: 40px;
  background: #f8fafc;
  display: flex;
  justify-content: center;
}

.turntable {
  position: relative;
  width: 400px;
  height: 400px;
  border-radius: 50%;
}

.prize-label {
  position: absolute;
  width: 100%;
  height: 100%;
  display: flex;
  justify-content: center;
  align-items: baseline;
  font-weight: bold;
  color: white;
  line-height: 100px;
}

.arrow {
  position: absolute;
  top: 140px;
  width: 0;
  height: 0;
  border-left: 15px solid transparent;
  border-right: 15px solid transparent;
  border-bottom: 100px solid #f8fafc;
}

.lottery-btn {
  position: absolute;
  top: 200px;
  width: 80px;
  height: 80px;
  border-radius: 50%;
  background: #f8fafc;
  display: flex;
  justify-content: center;
  align-items: center;
  color: #333;
  font-weight: bold;
  cursor: pointer;
  user-select: none;

  &--disabled {
    color: #999;
    pointer-events: none;
  }
}
View Compiled
interface Prize {
  label: string;
  probability: number;
  bgColor: string;
}

const prizes: Prize[] = [
  { label: "超级大奖", probability: 0.001, bgColor: "#b91c1c" },
  { label: "特等奖", probability: 0.009, bgColor: "#c2410c" },
  { label: "一等奖", probability: 0.01, bgColor: "#7e22ce" },
  { label: "二等奖", probability: 0.03, bgColor: "#2563eb" },
  { label: "三等奖", probability: 0.15, bgColor: "#15803d" },
  { label: "安慰奖", probability: 0.3, bgColor: "#1e293b" },
  { label: "谢谢参与", probability: 0.5, bgColor: "#3f3f46" }
];

const turntableDom = document.getElementById("turntable");
const lotteryBtnDom = document.getElementById("lottery-btn");

const proportionPerPrize = Number((100 / prizes.length).toFixed(1));
const anglePerPrize = Number((360 / prizes.length).toFixed(1));

const animationDuration = 5000;
const rotateLapsBase = 10;
let rotateLaps = 0;

// #region 划分转盘扇形区域
const turntableConicGradient = prizes.map((prize, index) => {
  const from = (proportionPerPrize * index).toFixed(1);
  const to =
    index === prizes.length - 1
      ? 100
      : (proportionPerPrize * (index + 1)).toFixed(1);
  return `${prize.bgColor} ${from}% ${to}%`;
});
turntableDom.style.background = `conic-gradient(${turntableConicGradient.join(
  ","
)})`;
// #endregion

// #region 添加奖品名节点
const prizeLabels = document.createDocumentFragment();
prizes.map((prize, index) => {
  const prizeLabel = document.createElement("div");
  prizeLabel.classList.add("prize-label");
  prizeLabel.style.transform = `rotate(${
    -anglePerPrize / 2 + anglePerPrize * (index + 1)
  }deg)`;
  prizeLabel.innerText = prize.label;
  prizeLabels.appendChild(prizeLabel);
});
turntableDom.append(prizeLabels);
// #endregion

// #region 设置转盘初始角度
const turntableBaseRotate = -anglePerPrize / 2;
turntableDom.style.transform = `rotate(${turntableBaseRotate}deg)`;
// #endregion

const getRandomNumber = (min: number, max: number, precision: number) => {
  const factor = Math.pow(10, precision);
  const random = Math.random() * (max - min) + min;
  return Math.round(random * factor) / factor;
};

lotteryBtnDom.onclick = () => {
  lotteryBtnDom.classList.add("lottery-btn--disabled");

  // #region 获取中奖结果,由后端完成
  let prizeIndex = 0;
  let resultNum = getRandomNumber(0, 1, 3);
  while (resultNum > 0) {
    resultNum -= prizes[prizeIndex].probability;
    if (resultNum > 0) {
      prizeIndex += 1;
    }
  }
  const prize = prizes[prizeIndex];
  // #endregion

  // #region 获取转盘需要旋转的角度
  const turntableRotateDegFrom = Number((anglePerPrize * prizeIndex).toFixed(1));
  const turntableRotateDegTo =
    prizeIndex === prizes.length - 1
      ? 360
      : Number((anglePerPrize * (prizeIndex + 1)).toFixed(1));
  const turntableRotateDegEdgeThreshold = Number((anglePerPrize / 4).toFixed(1)); // 设定旋转到指定扇形区域的距边缘阈值(<= anglePerPrize / 2),防止出现指针指向太靠近边缘的情况
  const turntableRotateDegBase = getRandomNumber(
    turntableRotateDegFrom + turntableRotateDegEdgeThreshold,
    turntableRotateDegTo - turntableRotateDegEdgeThreshold,
    1
  );
  rotateLaps += rotateLapsBase; // 适配多次旋转的情况
  const turntableRotateDeg = -(rotateLaps * 360 + turntableRotateDegBase);
  // #endregion

  // #region 为转盘设置旋转动画
  turntableDom.style.transform = `rotate(${turntableRotateDeg}deg)`;
  turntableDom.style.transition = `transform ${animationDuration}ms cubic-bezier(.3, .9, .38, 1)`;
  // #endregion

  setTimeout(() => {
    alert(`您抽中了:${prize.label}!`);
    lotteryBtnDom.classList.remove("lottery-btn--disabled");
  }, animationDuration + 500);
};
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.