Canvas粒子系统实现情绪化动画:从烟花易冷看代码艺术创作
那天晚上我正为一个项目渲染一段粒子特效屏幕上的光点明明灭灭忽然就想起了很多年前用代码模拟烟花的日子。那些简单的二维动画没有复杂的三维模型和物理引擎却总能精准地触动人心。于是我关掉了庞大的三维软件打开了最基础的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 将经验沉淀为工作流回顾整个创作过程可以沉淀出一套适用于此类代码艺术创作的工作流定义核心情绪首先想清楚你要表达什么是欢快、悲伤、宁静还是震撼“烟花易冷”的核心是“绚烂后的寂寥”。寻找算法映射将抽象情绪转化为具体的、可代码控制的变量时间、颜色、运动。搭建最小原型用最简单的代码实现最核心的效果快速验证想法。迭代与调参像调试音乐一样调试参数反复观看、感受微调直至达到想要的氛围。考虑性能与美学平衡有时技术限制反而会催生独特的艺术风格。最终当你在深夜运行这段代码看着暖色的光点在屏幕上绽放又缓缓冷却、下坠、消失仿佛能听到周杰伦那首歌在耳边响起。代码不再只是冰冷的指令它成了你与观者之间一座无声的桥梁传递着那些难以言喻的、关于美丽与消逝的复杂感受。这或许就是程序作为一种创作媒介最迷人的地方。

相关新闻

政务与医疗大模型合规安全:在监管要求下落地 AI 防护

政务与医疗大模型合规安全:在监管要求下落地 AI 防护

政务与医疗大模型合规安全:在监管要求下落地 AI 防护 一、当模型遇上最敏感的数据:政务医疗的合规矛盾 政务与医疗场景,是大模型落地最谨慎的地方。一边是群众办事、问诊咨询的真实需求,另一边是个人的身份、病历、健康这类极度敏…

2026/10/2 22:26:52 阅读更多 →
Python计算机毕设之基于 Python 的健康饮食数据统计分析平台 居民膳食习惯监测与健康预警系统(完整前后端代码+说明文档+LW,调试定制等)

Python计算机毕设之基于 Python 的健康饮食数据统计分析平台 居民膳食习惯监测与健康预警系统(完整前后端代码+说明文档+LW,调试定制等)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am…

2026/10/5 10:10:27 阅读更多 →
会话内斜杠命令

会话内斜杠命令

命令 功能 /new 开始新会话 /clear 清屏并开始新会话 /undo 撤销上一次用户/Agent 交互记录 /undo [N] v0.16&#xff1a;撤销最近 N 轮交互 /title <session_name> 为当前会话命名 /history 显示对话历史 /sessions 查看和管理会话 /compress 手动压缩上下文 /stop 停止…

2026/10/5 10:54:13 阅读更多 →

最新新闻

隔离内网下AI Agent工程化实战:MCP与Skills架构落地

隔离内网下AI Agent工程化实战:MCP与Skills架构落地

1. 为什么“隔离内网”是 AI Agent 工程化的真正分水岭很多人第一次听到“隔离内网下跑 AI Agent”&#xff0c;第一反应是&#xff1a;不就是把模型换成本地部署、把网络请求掐掉吗&#xff1f;真做过一轮的人都知道&#xff0c;事情远没有这么简单。隔离内网意味着你面对的不…

2026/10/5 14:26:55 阅读更多 →
种子活性评估与发芽率计算:家庭种植播种决策指南

种子活性评估与发芽率计算:家庭种植播种决策指南

1. 为什么家庭种植总在"播种"这一步就输了 我第一年在家种菜的时候&#xff0c;十包种子播下去&#xff0c;最后只长出来三盆像样的东西。起初我以为自己浇水有问题&#xff0c;后来怀疑光照不够&#xff0c;再后来换土、换肥、换盆&#xff0c;折腾了一大圈&#xf…

2026/10/5 14:26:54 阅读更多 →
从零搭建个人知识库问答机器人:RAG+LangChain+FAISS实战

从零搭建个人知识库问答机器人:RAG+LangChain+FAISS实战

1. 为什么我要从零搭一个个人知识库问答机器人 我自己平时有大量碎片化的资料——技术笔记、会议纪要、收藏的文章、PDF 手册&#xff0c;散落在各种文件夹和笔记软件里。以前想找某个具体结论&#xff0c;基本靠 grep 加肉眼翻&#xff0c;效率极低。真正让我下决心动手的&a…

2026/10/5 14:26:53 阅读更多 →
嗜睡检测数据集与YOLOv8训练全流程:从标注格式到避坑指南

嗜睡检测数据集与YOLOv8训练全流程:从标注格式到避坑指南

简介&#xff1a;面向YOLO系列目标检测任务构建的嗜睡状态识别数据集&#xff0c;覆盖头部下垂、唤醒、昏昏欲睡、分心、吸烟、打哈欠、打电话等典型驾驶与作业场景&#xff0c;适合疲劳驾驶预警、驾驶员注意力监测等方向的模型训练和算法验证。压缩包约172.36MB&#xff0c;共…

2026/10/5 14:26:51 阅读更多 →
AC自动机详解:多模式匹配与敏感词过滤实战

AC自动机详解:多模式匹配与敏感词过滤实战

1. 先搞懂&#xff1a;AC自动机到底解决了什么问题如果你已经学过KMP&#xff0c;知道它能在 O(n) 时间内从一篇文章里找出一个模式串&#xff0c;那当你遇到“同时查找几千个敏感词、过滤几十万条日志、匹配一整本字典里的单词”这种需求时&#xff0c;KMP就彻底不够用了——你…

2026/10/5 14:26:45 阅读更多 →
.NET 6 WebApi JWT鉴权实战:从401调试到Token续签

.NET 6 WebApi JWT鉴权实战:从401调试到Token续签

简介&#xff1a;本资源是一套基于.NET 6平台构建Web API并集成JWT身份鉴权的完整实战源码&#xff0c;面向C#后端开发初学者及Web API安全实践者&#xff0c;解决现代API服务中用户认证与授权的核心问题。压缩包含68个文件&#xff0c;总大小1.43MB&#xff0c;涵盖11个C#业务…

2026/10/5 14:25:43 阅读更多 →

日新闻

马斯克杀回智能体战场,Grok 4.5万亿参数撑腰,Cursor接手数字白领项目:用TaoToken统一Key跑通多模型Agent工作流

马斯克杀回智能体战场,Grok 4.5万亿参数撑腰,Cursor接手数字白领项目:用TaoToken统一Key跑通多模型Agent工作流

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/5 0:00:22 阅读更多 →
AI编程工具插件机制详解:plugin.json配置与加载失败排查指南

AI编程工具插件机制详解:plugin.json配置与加载失败排查指南

1. 从“plugins”这个词说起&#xff1a;它到底在解决什么问题如果你最近在折腾 AI 编程工具&#xff0c;尤其是 Cursor、Codex CLI、Claude Code 这类带 CLI 的编辑器或命令行助手&#xff0c;那你大概率绕不开一个词——plugins。这个词本身不新鲜&#xff0c;从浏览器到 IDE…

2026/10/5 0:00:23 阅读更多 →
第26课:OpenClaw|日志审计与问题诊断:把日志链路改到 TaoToken 的排查清单

第26课:OpenClaw|日志审计与问题诊断:把日志链路改到 TaoToken 的排查清单

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/5 0:00:23 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/5 5:06:42 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/5 1:10:22 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/5 3:06:17 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/4 11:40:45 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/4 9:43:54 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/4 20:14:29 阅读更多 →