万花筒:拖一下就换一张壁纸,对称图案越转越上瘾
万花筒最上瘾的地方是“随便一动就完全变样”。这个网页版把旋转、扭曲、缩放和配色都做成即时响应:按住拖动能转出不同角度,上下拖改变变形程度,滚轮或双指可以缩放,双击立刻复位。340 颗发光粒子按 8 重对称反复映射,任何一帧停下来都像一张对称壁纸,还能一键导出 PNG。下面先看成品,再讲这个单文件应用是怎么让码道 WebUI 生成并部署出来的。

为什么值得看
万花筒没有用一张张贴图,而是只维护 340 个粒子的极坐标,再把每个粒子的位置按扇形折叠、按 8 重对称展开。这样一笔画下去,实际上同时画出了 8 条对称线,几何图案天然整齐:
- 8 重对称:
emplace()先调用mapToSector()把任意角度折进一个扇形,再围绕中心展开 8 份。 - 拖尾与波折:上一帧点
P0到当前帧点P1连线,粒子半径、自旋和波动幅度持续变化,拖尾不会断。 - 配色即换:内置 7 套霓虹、极光、熔岩、薄荷等配色,按钮每点一次换一套。
- 三种操控:拖拽旋转/扭曲,滚轮或双指捏合缩放,双击复位,移动端也能玩。
- 导出 PNG:
canvas.toBlob()直接下载当前画面,时间戳做文件名。
任务描述
在码道 WebUI 输入框粘贴以下需求,回车发送:
我依然只描述最终效果,没有手写每一步。它这轮的动作是:
- 新建项目目录并写入单文件 `index.html`,随后又做几次增删改,把对称映射、缩放和导出逻辑补齐。
- 从页面里提取内嵌 JavaScript,用 `node --check` 做语法检查,确认没有语法错误。
- 探测空闲端口(这轮是 3000),用 `python3 -m http.server` 启动静态服务,`curl` 自检 HTTP 200 后给出内部预览地址。
- 最后把功能清单、文件位置和访问地址一起写回对话,预览地址就在对话末尾。
码道实际做了什么
开发一个“万花筒”网页应用:深色夜空背景,中心用大量彩色发光粒子同时呈现 8 重对称图案,粒子带连线拖尾并持续流动;支持鼠标拖拽旋转与改变形态、滚轮或触控双指缩放、双击复位;内置多套配色并支持切换;提供“导出 PNG”按钮,把当前画面保存为图片;界面简洁美观,单个 index.html 即开即玩。完成后自动部署成可访问的网页应用,并把访问地址写出来。
1. 对称的核心:先把角度折进一个扇形
如果直接按屏幕坐标复制 8 份,图案会到处乱飞。这里先把任意角度归一化后折成 0 到 sector 之间的基础角,再把两侧镜像补回来:
function mapToSector(ang, sector) {
ang = ((ang % TAU) + TAU) % TAU;
var seg = Math.floor(ang / sector);
var a = ang - seg * sector;
if (seg % 2 === 1) a = sector - a;
return a;
}
function emplace(ang, rad, cx, cy, sector, out) {
var base = mapToSector(ang, sector);
for (var k = 0; k < symmetry; k++) {
var theta = rotate + k * sector + (k % 2 === 1 ? (sector - base) : base);
out[k * 2] = cx + Math.cos(theta) * rad;
out[k * 2 + 1] = cy + Math.sin(theta) * rad;
}
}
rotate 再叠加在展开角上,所以只改一个角度值,整幅对称图就一起转起来。
2. 暗底覆盖 + lighter 叠加,才有流光感
每帧不擦干净画布,而是铺一层几乎透明的深色,再用 lighter 合成模式叠加发光粒子,上一帧的痕迹自然形成拖尾:
ctx.globalCompositeOperation = 'source-over';
ctx.fillStyle = 'rgba(5,6,15,0.30)';
ctx.fillRect(0, 0, W, H);
ctx.globalCompositeOperation = 'lighter';
粒子的波折也不靠复杂曲线:在当前半径上乘一个随粒子角度和时间变化的正弦项,画面就像一直在“呼吸”。
3. 拖动和捏合用的是同一个目标值
控件不直接写死当前值,而是改 targetRotate / targetWarp / targetScale,动画循环里用插值因子慢慢逼近,所以松手后运动的收尾是平滑的:
canvas.addEventListener('pointermove', function (e) {
if (!dragging) return;
var dx = e.clientX - lastX;
var dy = e.clientY - lastY;
lastX = e.clientX;
lastY = e.clientY;
targetRotate += dx * 0.0065;
targetWarp = clamp(targetWarp + dy * 0.012, 0.05, 3.2);
});
双指捏合时,还专门记录上一帧两点距离,用距离比换算成缩放变化。
验证与踩坑
生成结束后打开对话里的内部预览地址,页面标题是「万花筒 Kaleidoscope」,粒子图案确实在持续流动。
- 隔几秒连续截两张运行画面,两个文件内容不重复:粒子半径、自旋和波折都有随机性,画面本来就在变。
- 踩坑:内部预览链接偶发跳到开发者空间登录跳转页,重新打开一次才稳定进入应用;截图前先确认 Canvas 已经出现,避免截到空白页。
- 从代码看,拖尾浓淡由
rgba(5,6,15,0.30)的 alpha 决定;这个值越低,屏幕越难“擦干净”,拖尾越长,也更容易糊成一片。这是最值得调的一个参数。
本地复现
项目是单文件 index.html,没有第三方依赖。把文件保存到本地后用浏览器直接打开就能玩;如果希望通过本地 HTTP 服务访问:
cd codearts-kaleidoscope
python3 -m http.server 8080
# 然后打开 http://127.0.0.1:8080
直接下载试玩
源码同步到公开 Demo Park 仓库:
- Demo Park 仓库:https://atomgit.com/deli007/demo_park
- 本案例目录:https://atomgit.com/deli007/demo_park/tree/main/codearts-kaleidoscope
准备环境:进入 AI 开发者空间
本文用码道 WebUI 版演示。先进入 AI 开发者空间并选择体验版,不需要本地安装:
- 浏览器打开 https://devstation.developer.huaweicloud.com/aidevspace?utm_source=opcsfsq&utm_medium=wbwz
- 弹窗「请选择要进入的版本」→ 点击 体验版
- 首次使用跳到华为云登录页,登录一次后浏览器会话可复用
- 弹窗「临时凭证自动配置」→ 点击 同意,沙箱自动创建
进入主界面后,等左侧菜单加载出来,找到 CodeArts,点它右侧的浏览器图标,就打开了码道 WebUI 对话窗口。

开通后有三种使用方式:WebUI(浏览器对话)、TUI(终端命令行)和桌面 IDE(IDE 插件)。本文用 WebUI 版演示。
进入 CodeArts:https://developer.huaweicloud.com/codeartsco.html?source=dmzntgwsf&sourcead=dmzntgwwbwz

使用码道体会
最省事的是「对称展开」这段:如果自己写,很容易在奇偶扇形镜像上犯晕,把条件写反后图案就会出现断线。让它直接在需求里点出“8 重对称 + 拖拽变形态”,生成出来的 mapToSector 和 emplace 分工很清楚,一个负责折角,一个负责展开,读起来比临时拼的映射公式直观。导出 PNG 这一步也顺手做掉了:canvas.toBlob() 相比 toDataURL() 对大画布更友好,实际点导出几乎没有停顿。
总结
万花筒的视觉密度来自“少维护、多展开”:340 个粒子通过 8 重对称变成约 2720 段连线,加上拖尾和 lighter 叠加,画面就很满了。后续可以继续加「对称数调节」「下载高清大图」「把某帧定格成壁纸」或「用种子生成固定图案」,让它从随手玩的小工具变成真正能产壁纸的生成器。
万花筒 运行效果对照
连续截取的两个运行画面,左边是初始图案的某个时刻,右边是拖动旋转、改变形态后的另一帧:

- 点赞
- 收藏
- 关注作者
评论(0)