一、写在前面
女朋友嘴馋想吃东西,但又总喊着要减肥。作为男票,怎么能袖手旁观?
于是我写了个小程序——《就决定是你了皮卡秋》。
表面看,这就是个平平无奇的随机转盘。但!只要我在分享前长按那个“分享”按钮,就能偷偷设置概率。嘿嘿
然后分享给她,她就可以心安理得地转、心安理得地吃。😉
你看,完美解决问题!
二、技术实现
前端代码基本交给 AI 生成,我只负责“改改改”。
1. 模板页
基础逻辑 + 基础列表,略过不表。
2. 转盘页(核心)
转盘使用 Canvas 手绘,关键点如下:
-
绘制逻辑:扇形从 -90°(顶部)起画,每块从中心到边缘线性渐变,柔和好看。
-
文字排布:沿扇形中弧线放置,自动适配字号,超长截断。
-
旋转约束:最少转 4 圈,指针固定顶部,最终通过
(prev + toCenter) % 360落在指定奖品区间。 -
概率控制:长按分享按钮生成带权重参数的分享链接,分享页自动解析并应用权重。
绘制代码节选如下(AI 主力,我微调):
_drawWheel(ctx, size) { const list = this._optionList; if (!list || list.length < 2) return; const n = list.length; const center = size / 2; const R = size / 2 - 8; const rInner = size * 0.14; const degStep = (2 * Math.PI) / n; // 浅色背景圆(整圆) ctx.beginPath(); ctx.arc(center, center, R, 0, 2 * Math.PI); ctx.fillStyle = '#FCF8F5'; ctx.fill(); // 扇形:柔和渐变(从中心到外缘) for (let i = 0; i < n; i++) { const startAngle = -Math.PI / 2 + i * degStep; const endAngle = startAngle + degStep; const midAngle = (startAngle + endAngle) / 2; const g = getSectorGradient(i); const x0 = center + Math.cos(midAngle) * rInner; const y0 = center + Math.sin(midAngle) * rInner; const x1 = center + Math.cos(midAngle) * R; const y1 = center + Math.sin(midAngle) * R; const gradient = ctx.createLinearGradient(x0, y0, x1, y1); gradient.addColorStop(0, g.start); gradient.addColorStop(1, g.end); ctx.beginPath(); ctx.moveTo(center, center); ctx.arc(center, center, R, startAngle, endAngle); ctx.closePath(); ctx.fillStyle = gradient; ctx.fill(); ctx.strokeStyle = 'rgba(255,255,255,0.5)'; ctx.lineWidth = 1; ctx.stroke(); } // 文字(扇形中点外侧,沿弧排布) const textR = rInner + (R - rInner) * 0.6; const fontSize = Math.max(12, Math.floor(size / 22)); ctx.font = `${fontSize}px sans-serif`; ctx.fillStyle = '#333'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; for (let i = 0; i < n; i++) { const midAngle = -Math.PI / 2 + (i + 0.5) * degStep; const tx = center + Math.cos(midAngle) * textR; const ty = center + Math.sin(midAngle) * textR; ctx.save(); ctx.translate(tx, ty); ctx.rotate(midAngle + Math.PI / 2); const text = String(list[i]).slice(0, 6); ctx.fillText(text, 0, 0); ctx.restore(); } // 中心浅色圆(与中心按钮视觉衔接) ctx.beginPath(); ctx.arc(center, center, rInner, 0, 2 * Math.PI); ctx.fillStyle = '#FFF8F5'; ctx.fill(); },
权重随机函数,核心逻辑就这几行:
/** 按权重随机一个下标,weights 和为 1;无效时等权 */ _weightedIndex(weights, n) { if (!Array.isArray(weights) || weights.length !== n) return Math.floor(Math.random() * n); const sum = weights.reduce((a, b) => a + Number(b), 0); if (!Number.isFinite(sum) || sum <= 0) return Math.floor(Math.random() * n); const r = Math.random(); let acc = 0; for (let i = 0; i < n; i++) { acc += Number(weights[i]) / sum; if (r < acc) return i; } return n - 1; },
3. 历史记录
纯前端存储,数据存到 localStorage,做了条数限制,旧数据自动清理。无后端,省成本。
4. 分享功能 —— 最骚的一步
本着能省则省、绝不写后端的原则,我把整个转盘的“概率配置”塞进了分享链接里。
技术限制:
-
纯英文字符 URL 建议 ≤1500 字
-
中英文混合建议 ≤800 字
而我们转盘才几个奖品?完全装得下!
于是——URL 即数据,分享即配置。
女票点开链接,转盘就已经被“动过手脚”了。她转得开心,我看得满足。
三、关于 AI 写代码的一些碎碎念
这项目基本是“AI 生成 + 人工修图”的模式。过程中产生了一些真实想法:
1. 恐惧是真的
看着 AI 刷刷刷生成我可能要写半小时的逻辑,说没有危机感是假的。
2. 但 AI 还离不开人
它经常写出“看起来对、跑起来错”的代码。边界条件漏了、样式歪了、交互不对了——这些还得人来修。
前端开发者的掌控力,依然是产品的底线。
3. 提示词即生产力
同样的需求,不同的人写提示词,产出质量天差地别。
AI 很聪明,但也非常依赖“怎么问”。这事儿和写需求文档一样,是个需要练的技能。
4. 不是“被替代”,是“会者越强”
与其焦虑 AI 会不会取代我,不如学着怎么用好它。
把它当成一个无限耐心的初级工程师,你指哪儿它打哪儿,但方向得你把。
四、诸君共勉
工具越强,越考验使用工具的人。
愿我们都能成为驾驭浪潮的人,而不是被浪潮推着走的人。
🌊 共勉。