
那天晚上我正为一个项目渲染一段粒子特效屏幕上的光点明明灭灭忽然就想起了很多年前用代码模拟烟花的日子。那些简单的二维动画没有复杂的三维模型和物理引擎却总能精准地触动人心。于是我关掉了庞大的三维软件打开了最基础的Canvas想试试看在今天这个追求极致渲染的时代我们是否还能用最朴素的方式表达一种转瞬即逝的美。“烟花易冷”这个词本身就充满了矛盾与张力。烟花是炽热的、绽放的、瞬间的而“冷”是沉寂的、收敛的、永恒的。用程序去捕捉这种从极致的绚烂到彻底的寂灭的过程其挑战不在于模拟物理上的逼真而在于如何通过算法传递出那种情绪上的落差。这更像是一种数字化的情感表达代码成了画笔逻辑成了韵律。1. 为什么说“模拟情绪”比“模拟物理”更难当我们谈论用代码创作2D动画时最容易陷入的误区就是追求技术上的完美复刻。比如去研究烟花爆炸时每一个颗粒的真实运动轨迹、空气阻力系数、光照模型。这当然很有价值但对于“烟花易冷”这样的主题如果只停留在物理层面最终得到的可能只是一个精美的科普演示而不是一个能引发共鸣的作品。1.1 物理模拟的局限性纯粹的物理模拟关注的是“如何发生”。例如一个粒子系统需要定义初始速度决定绽放的范围和形状重力加速度决定下坠的轨迹生命周期决定从出现到消失的时间这些参数能创造出看起来“正确”的烟花但未必是“美”的更未必是“伤感”的。物理规律是客观的、重复的而情绪是主观的、流动的。1.2 情绪表达的算法抓手那么如何用代码表达情绪我们需要找到一些可操作的、影响观众心理感受的变量。对于“烟花易冷”这个主题关键抓手至少有三个时间控制Timing绚烂的峰值能持续多久是瞬间闪耀还是缓慢亮起从亮到暗的过渡是戛然而止还是依依不舍这直接对应了“易冷”中的“易”字——那种迅速消逝的感觉。色彩变迁Color Transition烟花不是从一种颜色直接跳到另一种颜色也不是简单地变暗。它的色彩衰减是有层次的。可能是暖色调如橙、黄先褪去留下冷色调如蓝、紫短暂停留最后所有颜色都融入背景的黑暗。这个过程模拟了热情消退、寒意渐生的心理体验。运动轨迹Motion Path烟花颗粒的下坠不应该是匀速的。可以设计为在亮丽时下坠缓慢仿佛在留恋天空随着光芒黯淡下坠速度加快有一种“坠落”的无力感。这种非物理真实的运动恰恰是情绪化的。通过调整这些“非物理”的参数我们编码的就不再是火药和重力而是璀璨、短暂、失落与寂静这些抽象的概念。2. 从零开始构建一个充满情绪的粒子系统让我们暂时忘掉那些庞大的游戏引擎或动画库。要体会这种代码与情感的连接最好的方式就是从最基础的环境开始。这里我选择用HTML5 Canvas和原生JavaScript因为它足够底层能让你控制每一个像素的生死。2.1 搭建画布与基础结构首先创建一个基本的HTML文件并设置一个全屏的Canvas画布。给予画布一个深色的背景比如#0a0a20这种颜色更像深夜的天空能为烟花提供足够的对比度。!DOCTYPE html html langzh-CN head meta charsetUTF-8 title烟花易冷/title style body { margin: 0; overflow: hidden; background-color: #0a0a20; } canvas { display: block; } /style /head body canvas idcanvas/canvas script srcscript.js/script /body /html在script.js中我们初始化画布并使其自适应窗口大小。const canvas document.getElementById(canvas); const ctx canvas.getContext(2d); function resizeCanvas() { canvas.width window.innerWidth; canvas.height window.innerHeight; } window.addEventListener(resize, resizeCanvas); resizeCanvas(); // 初始化尺寸2.2 设计粒子类赋予每个光点以生命粒子是烟花的基本单位。一个粒子不只是一个有位置的点它应该有自己的生命周期、颜色、速度和行为。class Particle { constructor(x, y, color) { this.x x; this.y y; this.originalX x; // 记录初始位置可用于某些特效 this.originalY y; this.color color; // 初始颜色 this.alpha 1; // 透明度用于实现衰减效果 this.vx (Math.random() - 0.5) * 4; // 水平速度产生随机扩散 this.vy (Math.random() - 0.5) * 4; // 垂直速度 this.gravity 0.05; // 模拟重力让粒子下坠 this.fadeSpeed 0.015; // 透明度衰减速度控制“冷”的快慢 this.size Math.random() * 2 1; // 粒子大小 } update() { // 应用重力 this.vy this.gravity; // 更新位置 this.x this.vx; this.y this.vy; // 透明度衰减生命流逝 this.alpha - this.fadeSpeed; // 如果粒子生命结束返回true以便从数组中移除 return this.alpha 0; } draw() { ctx.save(); ctx.globalAlpha this.alpha; // 使用全局透明度实现淡出 ctx.fillStyle this.color; ctx.beginPath(); ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2); ctx.fill(); ctx.restore(); } }在这个设计里fadeSpeed和gravity是两个至关重要的情绪参数。增大fadeSpeed烟花会更快消失显得更决绝减小gravity粒子会飘得更久显得更梦幻。2.3 创建烟花发射器控制绽放的节奏单个粒子无法成烟花。我们需要一个“发射器”来管理一次爆炸产生的所有粒子并控制爆炸的时机、位置和样式。class Firework { constructor(x, y) { this.x x; this.y y; this.particles []; this.createParticles(); } createParticles() { const particleCount 100; // 粒子数量影响烟花密度 const hue Math.floor(Math.random() * 60) 10; // 随机暖色调10-70 for (let i 0; i particleCount; i) { // 从暖色到冷色的渐变。初始为暖色hsl中的色相 const color hsl(${hue}, 100%, 60%); this.particles.push(new Particle(this.x, this.y, color)); } } update() { // 更新所有粒子并移除“死亡”的粒子 this.particles this.particles.filter(particle !particle.update()); // 如果所有粒子都消失了返回true告知主循环此烟花可移除 return this.particles.length 0; } draw() { this.particles.forEach(particle particle.draw()); } }注意createParticles方法中的hue值我们将其限制在10-70之间这大致是红色到黄色的范围是烟花最常见的暖色系为后续的“冷”做铺垫。3. 实现“易冷”效果从代码到情感的跨越有了基础的粒子系统现在我们来注入“易冷”的灵魂。这需要更精细地控制颜色的衰减和粒子的运动。3.1 动态色彩衰减模拟温度流失目前的粒子颜色是固定的只是透明度在降低。这不够。我们需要颜色本身也从暖变冷。修改Particle类的update方法update() { this.vy this.gravity; this.x this.vx; this.y this.vy; this.alpha - this.fadeSpeed; // 动态改变色相从暖色向冷色例如蓝色调过渡 if (this.alpha 0.7) { // 当亮度开始衰减时开始变色 // 将颜色从HSL格式解析出来 // 这是一个简化的示例假设this.color初始是hsl(30, 100%, 60%) // 在实际项目中你可能需要解析this.color字符串 // 这里为了演示我们直接操作一个假设的hue值 let currentHue 30; // 假设初始是橙色 // 让色相逐渐偏向蓝色例如220 currentHue currentHue (220 - currentHue) * (1 - this.alpha) * 0.5; // 同时降低饱和度让颜色变得灰暗 let saturation 100 * this.alpha; this.color hsl(${currentHue}, ${saturation}%, ${60 * this.alpha}%); } return this.alpha 0; }这段代码的意思是在烟花生命的大部分时间里透明度高于0.7时它保持温暖的初始色。当生命走过巅峰开始衰退时透明度低于0.7它的色相开始向蓝色偏移同时饱和度和亮度也随着透明度降低最终变为灰暗的冷色调直至消失。这个过程视觉上就是“燃烧-冷却-熄灭”。3.2 运动轨迹的情绪化调整同样在update方法中我们可以让重力效应随着生命衰减而增强。update() { // 生命后期重力效果更明显下坠更快 let effectiveGravity this.gravity; if (this.alpha 0.5) { effectiveGravity * 1.5; // 生命值越低下坠感越强 } this.vy effectiveGravity; // 使用生效的重力 this.x this.vx; this.y this.vy; this.alpha - this.fadeSpeed; // ... (颜色衰减代码) return this.alpha 0; }这样烟花颗粒在刚炸开时轻盈飘逸在即将熄灭时却加速坠落强化了那种“逝去”的无力感。4. 动画循环与细节打磨让作品呼吸一切就绪我们需要一个动画循环来驱动整个世界。4.1 主循环与烟花队列const fireworks []; function animate() { // 用半透明的黑色覆盖画布产生拖尾效果 ctx.fillStyle rgba(10, 10, 32, 0.1); ctx.fillRect(0, 0, canvas.width, canvas.height); // 随机生成新烟花 if (Math.random() 0.05) { // 控制烟花发射频率 const x Math.random() * canvas.width; const y Math.random() * canvas.height * 0.4; // 在画面上部爆炸 fireworks.push(new Firework(x, y)); } // 更新并绘制所有烟花 fireworks.forEach((firework, index) { firework.update(); firework.draw(); }); // 移除已经燃烧殆尽的烟花 for (let i fireworks.length - 1; i 0; i--) { if (fireworks[i].update()) { fireworks.splice(i, 1); } } requestAnimationFrame(animate); } animate();这里有两个关键细节ctx.fillStyle rgba(10, 10, 32, 0.1);这一行没有完全清空画布而是用了一个带很低透明度的背景色进行覆盖。这样前一帧的画面会有淡淡的残留形成漂亮的拖尾效果模拟了视觉暂留让烟花的运动轨迹更柔和也增加了场景的朦胧感。烟花生成频率0.05和爆炸高度canvas.height * 0.4需要反复调试以达到一种疏密有致、不显拥挤的节奏感。4.2 性能与体验的平衡当粒子数量很多时性能可能成为问题。除了优化代码还可以从艺术角度思考稀疏的、有节奏的烟花难道不比密集的、令人眼花缭乱的烟花更符合“易冷”的孤寂感吗所以不必追求最高的粒子数合适的才是最好的。5. 超越代码从技术实现到创作思维完成这个基本的Demo之后更重要的是理解其背后的创作逻辑。这不仅仅是一个编程练习更是一种通过计算媒介进行表达的训练。5.1 参数化你的情感你可以将fadeSpeed衰减速度、gravity重力、色相变化起点和终点、粒子数量等都暴露为可调节的参数。然后尝试用不同的参数组合来匹配不同的心情快速的衰减、强烈的重力 - 决绝、失落。缓慢的衰减、微弱的重力 - 缠绵、怀念。色相从暖黄直接跳到深紫 - 戏剧性的转变。色相缓慢过渡到青蓝 - 淡淡的忧伤。这个过程就是你将自己的情感“参数化”的过程。5.2 从模拟到创造我们模拟了烟花但代码的潜力远不止于此。你可以打破物理规律创造完全超现实的“烟花”粒子不是下坠而是向上飞升如同逆流的星光。粒子之间产生引力或斥力形成动态的图案。烟花的形状不是圆形而是心形、文字甚至是自定义的图案。这时你的角色就从“自然现象的模仿者”转变为了“数字世界的造物主”。5.3 将经验沉淀为工作流回顾整个创作过程可以沉淀出一套适用于此类代码艺术创作的工作流定义核心情绪首先想清楚你要表达什么是欢快、悲伤、宁静还是震撼“烟花易冷”的核心是“绚烂后的寂寥”。寻找算法映射将抽象情绪转化为具体的、可代码控制的变量时间、颜色、运动。搭建最小原型用最简单的代码实现最核心的效果快速验证想法。迭代与调参像调试音乐一样调试参数反复观看、感受微调直至达到想要的氛围。考虑性能与美学平衡有时技术限制反而会催生独特的艺术风格。最终当你在深夜运行这段代码看着暖色的光点在屏幕上绽放又缓缓冷却、下坠、消失仿佛能听到周杰伦那首歌在耳边响起。代码不再只是冰冷的指令它成了你与观者之间一座无声的桥梁传递着那些难以言喻的、关于美丽与消逝的复杂感受。这或许就是程序作为一种创作媒介最迷人的地方。