You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Canvas开发抽奖转盘时如何为选项设置自定义占比百分比?

自定义权重转盘的实现修改

原代码的核心缺陷是扇区渲染完全采用等分逻辑,没有读取配置中的百分比权重,且中奖判定逻辑和等分规则强绑定,无法匹配不同扇区大小的场景,可按以下步骤调整:

1. 预处理扇区权重配置

  • 移除原代码中全局固定的等分弧长变量arc
  • 遍历options数组,把第二项带%的字符串解析为数值型权重(例如把'10%'转为10、'90%'转为90)
  • 计算所有选项的总权重值,按照单个权重占总权重的比例,换算为该扇区实际占有的弧度(整个转盘总弧度为2π),同时累计每个扇区的起始角度,保证所有扇区拼合后刚好填满整圆
    • 以现有配置为例:@david占比10%,对应扇区弧度为0.2π;@burn占比90%,对应扇区弧度为1.8π,两者相加刚好为整圆弧度

预处理逻辑核心代码

const wheelSectors = [];
let totalWeight = 0;
// 解析权重
options.forEach(item => {
    const weight = parseFloat(item[1]);
    totalWeight += weight;
    wheelSectors.push({
        label: item[0],
        weight: weight
    })
})
// 计算每个扇区的起始角度、实际扫过的弧长
let angleCursor = 0;
wheelSectors.forEach(sector => {
    sector.sweepAngle = (sector.weight / totalWeight) * Math.PI * 2;
    sector.startAngle = angleCursor;
    angleCursor += sector.sweepAngle;
})

2. 调整扇区绘制逻辑

  • 绘制每个扇区时,不再使用i * 固定弧长计算起始角度,改为读取预处理阶段生成的wheelSectors[i].startAngle作为扇区本身的固定偏移量,叠加全局旋转的startAngle作为实际绘制的起始角度
  • 扇区的结束绘制角度,读取对应扇区的sweepAngle值,不再使用固定的等分弧长
  • 文字定位时,取扇区实际起始角度加自身弧长的一半作为中心位置,保证文字始终在扇区正中间

绘制循环核心替换代码

for (let i = 0; i < options.length; i++) {
    // 替换原固定等分的角度计算逻辑
    const sector = wheelSectors[i];
    const realStartAngle = startAngle + sector.startAngle;
    ctx.fillStyle = getColor(i, options.length);

    ctx.beginPath();
    ctx.arc(250, 250, outsideRadius, realStartAngle, realStartAngle + sector.sweepAngle, false);
    ctx.arc(250, 250, insideRadius, realStartAngle + sector.sweepAngle, realStartAngle, true);
    ctx.stroke();
    ctx.fill();

    ctx.save();
    // 替换原固定弧长计算的文字旋转角度
    const textCenterAngle = realStartAngle + sector.sweepAngle / 2;
    ctx.translate(
        250 + Math.cos(textCenterAngle) * textRadius,
        250 + Math.sin(textCenterAngle) * textRadius
    );
    ctx.rotate(textCenterAngle + Math.PI / 2);
    ctx.fillText(sector.label, -ctx.measureText(sector.label).width / 2, 0);
    ctx.restore();
}

3. 修正中奖判定逻辑

  • 原代码基于固定等分弧长arc计算中奖索引,在非等分场景下会出现匹配错位,需要替换为基于区间匹配的判定逻辑
  • 先把停止时的全局角度归一化到0-2π的范围(适配转盘旋转多圈后的角度溢出问题)
  • 从第一个扇区开始累加弧长,找到当前停止角度落在哪个扇区的累计角度区间,对应的索引即为中奖结果

中奖判定核心替换代码

clearTimeout(spinTimeout);
// 角度归一化到0-2π范围
let normalizedAngle = (startAngle * 180 / Math.PI + 90) % 360;
normalizedAngle = normalizedAngle * Math.PI / 180;
if (normalizedAngle < 0) normalizedAngle += Math.PI * 2;

// 区间匹配找中奖扇区
let winIndex = 0;
let angleAccum = 0;
for (let i = 0; i < wheelSectors.length; i++) {
    angleAccum += wheelSectors[i].sweepAngle;
    if (normalizedAngle <= angleAccum) {
        winIndex = i;
        break;
    }
}

ctx.save();
ctx.font = 'bold 30px Helvetica, Arial';
const winText = wheelSectors[winIndex].label;
ctx.fillText(winText, 250 - ctx.measureText(winText).width / 2, 250 + 10);
ctx.restore();

额外说明

  • 权重配置不需要强制凑成100%,代码自动按照总权重比例换算扇区大小,例如你设置@david权重1、@burn权重9,效果和10%、90%的配置完全一致
  • 角度计算时注意处理负角度的边界情况,避免因为旋转圈数过多导致的索引匹配错误
  • 原代码存在文字绘制的bug:直接把数组形式的options[i]传给fillText,会把权重值和用户名一起绘制到扇区上,上述修改已修复该问题

内容的提问来源于stack exchange,提问作者learning-canva

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.26 17:15:03