
CSS、Canvas 还是 Worker先看画面怎样更新生成艺术选 CSS、Canvas 还是 Worker不能只看包体积与 API。它们占用的渲染资源不同真正的差别会落在目标设备的绘制、合成和输入响应上。我通常先把效果拆成可控制的指标每帧绘制多少对象、是否需要与指针实时联动、能否暂停以及降级后页面长什么样。再用真实设备录制性能轨迹。用性能轨迹看掉帧来源Performance 轨迹里即使主线程不忙合成或绘制也可能成为瓶颈。检查图层数、重绘区域和内存占用比猜测更可靠。# 使用 lighthouse 命令行对生成艺术页面进行无头性能审计 npx lighthouse http://localhost:3000/generative-art-demo.html --only-categoriesperformance --view # 配合 Chrome 远程调试协议分析 Layers 占用内存 curl http://localhost:9222/json/list在 Performance 面板中勾选 Enable advanced rendering instrumentation展开 GPU 绘制轨迹发现页面生成粒子动画时产生了上百个隐式合成层Implicit Compositing Layers。CSS 规则里盲目配置了will-change: transform, opacity导致移动端 WebGL/Compositer 显存被迅速挤爆。渲染管线分流与选型决策链路复杂粒子或动态背景可以根据能力检测与运行时表现在 CSS、Canvas 和静态方案之间分流Worker 线程与 OffscreenCanvas 示例当浏览器支持时可以把 Canvas 控制权转给 Worker。它并不自动解决所有性能问题但能减少计算对主线程输入处理的影响。以下代码演示基本通信边界// main.js - 主线程初始化与 Canvas 控制权转移 const canvas document.getElementById(art-canvas); if (transferControlToOffscreen in canvas) { const offscreen canvas.transferControlToOffscreen(); const worker new Worker(art-worker.js); // 将 OffscreenCanvas 传输给 Worker 线程 worker.postMessage({ type: INIT, canvas: offscreen }, [offscreen]); // 监听窗口尺寸变化通知 Worker 调整绘制缓冲区 const resizeObserver new ResizeObserver(entries { for (const entry of entries) { const { width, height } entry.contentRect; worker.postMessage({ type: RESIZE, width: Math.floor(width * window.devicePixelRatio), height: Math.floor(height * window.devicePixelRatio) }); } }); resizeObserver.observe(canvas); } else { // 兼容不支持 OffscreenCanvas 的旧版设备退回主线程 Canvas initFallbackCanvas(canvas); }// art-worker.js - 独立的生成艺术计算与渲染线程 let ctx null; let particles []; let width 0; let height 0; self.onmessage function (e) { const { type } e.data; if (type INIT) { ctx e.data.canvas.getContext(2d); initParticles(200); renderLoop(); } else if (type RESIZE) { width e.data.width; height e.data.height; if (ctx) { ctx.canvas.width width; ctx.canvas.height height; } } }; function initParticles(count) { particles Array.from({ length: count }, () ({ x: Math.random() * width, y: Math.random() * height, vx: (Math.random() - 0.5) * 1.5, vy: (Math.random() - 0.5) * 1.5, radius: Math.random() * 3 1 })); } function renderLoop() { if (!ctx || width 0 || height 0) { requestAnimationFrame(renderLoop); return; } // 绘制生成艺术渐变背景 ctx.fillStyle rgba(15, 23, 42, 0.2); ctx.fillRect(0, 0, width, height); ctx.fillStyle #38bdf8; ctx.beginPath(); for (let i 0; i particles.length; i) { const p particles[i]; p.x p.vx; p.y p.vy; // 边界碰撞检测与反弹 if (p.x 0 || p.x width) p.vx * -1; if (p.y 0 || p.y height) p.vy * -1; ctx.moveTo(p.x, p.y); ctx.arc(p.x, p.y, p.radius, 0, Math.PI * 2); } ctx.fill(); requestAnimationFrame(renderLoop); }选型时要接受的限制抛开场景谈技术选型都是空中楼阁。不同动效方案的权衡点如下Houdini 的可用范围需按目标浏览器验证频繁改变会触发绘制的属性时仍要观察实际帧耗时。Worker 无法直接访问 DOM手势坐标和尺寸需要通过消息传递。它是隔离计算的工具不是帧率保证。落地建议少写 JavaScript 不代表没有渲染成本。位移与透明度变化可以先留给 CSS持续计算的画面再考虑 Canvas并给性能不足的设备准备一张不敷衍的静态背景。