尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

爱心代码飘散效果全解析:Python、HTML、C三种实现方案

发布时间:2026/9/25 1:04:32

资讯中心
01
ARTICLE

爱心代码飘散效果全解析:Python、HTML、C三种实现方案

爱心代码飘散效果全解析:Python、HTML、C三种实现方案
1. 从李峋同款说起爱心代码到底在卷什么如果你最近刷到过爱心代码这个词大概率是被某个短视频里满屏飘散的心形粒子效果刷屏了。那个画面里一颗颗爱心从屏幕底部缓缓升起带着拖尾、旋转、渐隐配合深色背景和霓虹色调视觉冲击力确实拉满。很多人第一反应是这是哪个软件做的第二反应是我能不能也搞一个。这个需求的核心其实不复杂用代码生成动态的爱心粒子飘散效果。它属于创意编程Creative Coding的范畴本质上是把数学函数、粒子系统和渲染循环组合在一起让屏幕上出现有规律但不呆板的视觉运动。适合的人群很广——刚学编程想找个有趣项目练手的新手、想给朋友做个惊喜的在校学生、需要快速产出视觉素材的内容创作者甚至是想在课堂上演示数学代码好看的老师。但问题在于网上搜到的爱心代码质量参差不齐。有的是一行print打印个静态字符画有的是从别处复制来的一坨代码连注释都没有跑起来报错都不知道错在哪。更麻烦的是很多版本只给了代码没给思路你改一个参数整个效果就崩了完全不知道背后的逻辑是什么。我前后用Python、HTML/CSS/JS、C语言三种方式都实现过爱心飘散效果踩过的坑包括但不限于粒子数量一多就卡成PPT、爱心形状画出来像屁股、飘散轨迹生硬得像电梯、颜色渐变过渡出现明显色带。这篇文章就把这些经验一次性讲清楚从数学原理到代码实现到性能调优给你一套能直接跑、能改、能扩展的完整方案。提示本文所有代码均为独立实现不依赖任何第三方图形库Python版本除外用的是标准库turtle和math复制到本地就能运行。2. 爱心形状的数学底子别再用两个圆拼了2.1 为什么大多数人画出来的爱心像屁股先说说最常见的错误做法。很多人画爱心用的是两个圆一个三角形的拼凑法上面两个半圆下面一个倒三角。这个方法在纸上画还行但用代码渲染出来圆弧和直线的接缝处会有明显的折角而且比例稍微不对就变成了桃子或者更尴尬的形状。另一个常见做法是用贝塞尔曲线硬调控制点。这个方法能画出好看的爱心但控制点参数全靠试换一个尺寸就得重新调复用性极差。真正优雅的方案是用参数方程。爱心形状有一条非常经典的参数曲线长这样x 16 * sin³(t) y 13 * cos(t) - 5 * cos(2t) - 2 * cos(3t) - cos(4t)其中t从 0 到 2π 取值。这条曲线画出来的爱心顶部有两个饱满的圆弧底部收成一个尖比例协调而且天然是参数化的——你只需要调整一个缩放系数就能控制大小不需要重新调控制点。我第一次看到这个方程的时候是有点懵的心想这几个cos叠加怎么就能画出爱心。后来自己推导了一遍才明白sin³(t)控制水平方向的宽度变化在 tπ/2 和 t3π/2 附近取得极值形成左右两个凸起而 y 方向的四个余弦项叠加在顶部形成两个波峰对应爱心的两个圆弧在底部形成一个尖角。这就是数学的美妙之处——几个简单的三角函数叠加就能生成复杂的形状。2.2 参数方程离散化从连续曲线到可渲染的点集有了参数方程下一步是把它变成代码能用的点集。思路很简单把 t 从 0 到 2π 分成 N 份每一份算出一个 (x, y) 坐标这些点连起来就是爱心轮廓。import math def heart_points(num_points200, scale10): points [] for i in range(num_points): t 2 * math.pi * i / num_points x 16 * math.sin(t) ** 3 y 13 * math.cos(t) - 5 * math.cos(2*t) - 2 * math.cos(3*t) - math.cos(4*t) points.append((x * scale, y * scale)) return points这里num_points决定了轮廓的精细度。我实测下来200个点已经足够平滑低于100个点会看到明显的多边形棱角。scale是缩放系数因为原始方程的 x 范围大约是 [-16, 16]y 范围大约是 [-17, 13]直接画出来太小需要放大。注意y 方向的偏移量不是对称的。原始方程 y 的最大值约13最小值约-17所以爱心的重心偏下。如果你想让爱心在画布上居中需要把 y 坐标整体上移约2个单位乘以scale后就是2*scale。2.3 填充 vs 描边两种视觉路线的取舍爱心轮廓有了接下来要决定是画空心描边还是实心填充。描边的好处是代码简单直接连线就行而且线条本身可以有粗细变化、颜色渐变适合做那种霓虹灯管风格的效果。缺点是如果线条太细远看就是一个轮廓不够有冲击力。实心填充的视觉冲击力更强但实现起来麻烦一些。常见做法有两种一种是用扫描线算法逐行填充另一种是生成大量随机点撒在爱心内部。后者更适合我们做飘散效果——因为飘散本身就是粒子系统粒子天然就是点。我的建议是做飘散效果用粒子填充。每个粒子是一个小爱心或者小圆点初始位置随机分布在爱心内部然后给它们一个向上的初速度配合重力和随机扰动就能形成飘散效果。这样既解决了填充问题又天然衔接了粒子系统。3. 粒子系统让爱心活起来的关键3.1 粒子系统的基本三要素任何粒子系统都逃不开三个核心要素生成、更新、渲染。生成阶段决定粒子在哪里出现、以什么初始状态出现。对于爱心飘散效果粒子的初始位置应该限制在爱心形状内部。怎么判断一个点是否在爱心内部最简单的方法是用参数方程生成轮廓点然后用射线法或者直接用点在多边形内的判断。但更高效的做法是随机生成一个点代入爱心的不等式方程判断。爱心的不等式形式是(x² y² - 1)³ - x²y³ ≤ 0这个不等式成立的点就在爱心内部。注意这里的 x 和 y 是归一化后的坐标范围大约在 [-1.5, 1.5] 之间。用这个不等式做判断比射线法快得多而且代码就一行。更新阶段负责每帧修改粒子的位置、速度、透明度、大小等属性。飘散效果的核心逻辑是粒子有一个向上的初速度同时受到向下的重力让速度逐渐衰减再加上一点随机扰动让轨迹不那么直。渲染阶段就是把粒子画到屏幕上。在Python的turtle里就是画一个小圆点或者小爱心在HTML的Canvas里就是画一个填充路径或者用fillRect画小方块在C语言的控制台里就是用字符或者ANSI转义码定位光标。3.2 速度、加速度与随机扰动飘散轨迹的调参心得飘散效果好不好看80%取决于运动轨迹的调参。我总结了一套三段式参数设计第一段初速度。粒子从爱心内部出发初速度决定了它喷发的力度。初速度太小粒子挤在一起像一坨初速度太大瞬间就飞出屏幕了。我的经验值是如果画布高度是600像素初速度的y分量取 -2 到 -5 像素/帧比较合适负号表示向上。第二段加速度。这里说的加速度其实是模拟空气阻力和重力。空气阻力让水平速度逐渐衰减重力让垂直速度逐渐从向上变成向下。但飘散效果不需要真的让粒子掉下来所以重力系数要设得很小比如 0.02 到 0.05。这样粒子会先上升然后上升速度越来越慢最后几乎悬停在空中慢慢消散。第三段随机扰动。这是让效果活起来的关键。每帧给粒子的速度加一个很小的随机值比如 ±0.1。这样每个粒子的轨迹都会有细微的不同整体看起来就像被风吹散的蒲公英而不是整齐划一的火箭升空。import random class Particle: def __init__(self, x, y): self.x x self.y y self.vx random.uniform(-1, 1) self.vy random.uniform(-5, -2) self.life 1.0 # 生命值从1衰减到0 self.decay random.uniform(0.005, 0.015) self.size random.uniform(2, 5) def update(self): self.x self.vx self.y self.vy self.vy 0.03 # 模拟重力 self.vx * 0.99 # 水平阻尼 self.vx random.uniform(-0.1, 0.1) # 随机扰动 self.life - self.decay return self.life 0这段代码里有个细节值得说self.vx * 0.99是水平阻尼让水平速度慢慢衰减。如果不加这个粒子会一直水平飘最后全堆在屏幕两边。加上之后粒子会逐渐向中间收拢形成一种聚拢再消散的视觉效果比单纯向上飘更有层次感。3.3 生命值与透明度粒子消散的节奏控制粒子的生命值决定了它什么时候消失。我见过很多实现是让粒子飞到屏幕外再删除但这样有个问题粒子在屏幕边缘突然消失视觉上很突兀。更好的做法是让粒子在飞行过程中逐渐变透明透明度降到0时自动移除。这样粒子是淡出而不是消失过渡自然得多。生命值的衰减速度decay决定了粒子的存活时间。如果画布高度600像素粒子平均上升速度3像素/帧那么大约200帧飞出屏幕。如果decay取0.01粒子在100帧左右就淡出了还没飞出屏幕就消失了这样整体效果更朦胧。如果decay取0.005粒子能飞到接近顶部才消失适合做那种满天星的效果。我的建议是decay取0.008到0.012之间配合粒子的初始大小和透明度能做出比较均衡的效果。太小了粒子飞太远显得稀疏太大了粒子还没升起来就没了。4. Python实现用turtle画出飘散爱心4.1 为什么选turtle而不是pygamePython做图形效果很多人第一反应是pygame。但pygame需要额外安装而且对于爱心飘散这种不需要复杂交互的场景pygame有点杀鸡用牛刀。turtle是Python标准库自带的不需要安装任何东西复制代码就能跑。虽然turtle的性能不如pygame但做几百个粒子的飘散效果完全够用。而且turtle的API非常直观goto(x, y)、dot(size)、color()一看就懂适合新手理解和修改。当然turtle有个硬伤它的渲染是单线程的粒子数量超过500个就会明显卡顿。所以如果你要做那种上千粒子的爆炸效果还是得上pygame或者直接上HTML Canvas。但300到500个粒子的飘散效果turtle跑起来很流畅。4.2 完整代码与逐段解析import turtle import math import random # 屏幕设置 screen turtle.Screen() screen.setup(800, 600) screen.bgcolor(black) screen.title(爱心飘散效果) screen.tracer(0) # 关闭自动刷新手动控制帧率 # 创建画笔 pen turtle.Turtle() pen.hideturtle() pen.penup() pen.speed(0) def heart_inside(x, y): 判断点(x, y)是否在爱心内部坐标已归一化到[-1.5, 1.5] x2 x * x y2 y * y return (x2 y2 - 1) ** 3 - x2 * (y ** 3) 0 def random_heart_point(scale100): 在爱心内部随机生成一个点 while True: x random.uniform(-1.5, 1.5) y random.uniform(-1.5, 1.5) if heart_inside(x, y): return x * scale, y * scale class Particle: def __init__(self): self.x, self.y random_heart_point() self.vx random.uniform(-0.8, 0.8) self.vy random.uniform(-4, -1.5) self.life 1.0 self.decay random.uniform(0.006, 0.012) self.size random.uniform(2, 6) # 颜色从红色到粉色渐变 r random.uniform(0.8, 1.0) g random.uniform(0.1, 0.4) b random.uniform(0.2, 0.5) self.color (r, g, b) def update(self): self.x self.vx self.y self.vy self.vy 0.025 self.vx * 0.99 self.vx random.uniform(-0.08, 0.08) self.life - self.decay return self.life 0 particles [] def draw_frame(): pen.clear() # 生成新粒子 for _ in range(3): particles.append(Particle()) # 更新并绘制 alive [] for p in particles: if p.update(): alive.append(p) # 根据生命值调整透明度turtle不支持透明度用颜色变暗模拟 r, g, b p.color factor p.life pen.color(r * factor, g * factor, b * factor) pen.goto(p.x, p.y) pen.dot(p.size * p.life) particles[:] alive screen.update() screen.ontimer(draw_frame, 16) # 约60帧/秒 draw_frame() screen.mainloop()这段代码有几个关键点需要解释screen.tracer(0)是性能优化的核心。turtle默认每画一笔就刷新一次屏幕画几百个粒子会闪得没法看。关掉自动刷新后用screen.update()手动控制每帧只刷新一次流畅度提升非常明显。heart_inside函数用的是爱心的不等式方程。注意这里的坐标范围是 [-1.5, 1.5]因为爱心方程在归一化后x 的最大值约1.3y 的最大值约1.1最小值约-1.4。取1.5的边界能保证覆盖整个爱心。颜色模拟透明度turtle本身不支持alpha通道所以用颜色乘以生命值来模拟淡出效果。生命值从1降到0颜色从亮红逐渐变暗到黑色视觉上就是淡出。粒子生成速率每帧生成3个新粒子60帧就是每秒180个。这个速率下屏幕上同时存在的粒子大约在200到300个之间turtle跑起来毫无压力。4.3 实测中的性能瓶颈与优化我在一台老笔记本上跑这段代码帧率稳定在55到60之间。但如果把每帧生成粒子数改成10帧率会掉到30左右。瓶颈在pen.dot()这个调用上——每画一个点turtle都要走一遍完整的绘图流程。优化方案有三个方案一减少粒子数量增大粒子尺寸。用更少但更大的粒子来达到类似的视觉效果。比如每帧生成2个粒子但尺寸范围调到4到10。方案二用pen.stamp()代替pen.dot()。stamp可以预先定义好形状然后直接盖章比dot快一些。但stamp不支持动态调整大小需要预先定义多个尺寸的形状。方案三换用turtle.Turtle()的批量绘制。把多个粒子的坐标收集起来一次性画完。但turtle的API不太支持批量操作这个方案实现起来比较别扭。我的建议是如果只是自己玩或者做演示方案一足够了。如果要做那种满屏上千粒子的效果直接换HTML Canvas性能不是一个量级的。5. HTML Canvas版浏览器里的高性能飘散5.1 Canvas的渲染优势与适用场景HTML Canvas做粒子效果性能比turtle高一个数量级。原因很简单Canvas是浏览器原生支持的2D渲染上下文底层是C实现的而且支持硬件加速。同样的粒子数量Canvas能跑到60帧满帧turtle可能只有20帧。更重要的是Canvas版本可以直接在浏览器里打开不需要安装任何环境。你写好一个HTML文件双击就能看效果发给朋友也能直接看。对于给朋友做个惊喜这个场景Canvas版本显然更实用。Canvas的另一个优势是支持透明度globalAlpha和渐变createRadialGradient能做出更细腻的视觉效果。比如粒子可以用径向渐变从中心亮到边缘暗看起来像发光的小球比turtle的纯色圆点高级不少。5.2 从零搭建粒子引擎!DOCTYPE html html head meta charsetUTF-8 title爱心飘散效果/title style body { margin: 0; overflow: hidden; background: #0a0a1a; } canvas { display: block; } /style /head body canvas idcanvas/canvas script const canvas document.getElementById(canvas); const ctx canvas.getContext(2d); function resize() { canvas.width window.innerWidth; canvas.height window.innerHeight; } resize(); window.addEventListener(resize, resize); // 爱心内部判断 function heartInside(x, y) { const x2 x * x, y2 y * y; return Math.pow(x2 y2 - 1, 3) - x2 * Math.pow(y, 3) 0; } // 随机爱心内部点 function randomHeartPoint(scale) { while (true) { const x (Math.random() * 3 - 1.5); const y (Math.random() * 3 - 1.5); if (heartInside(x, y)) { return { x: x * scale, y: -y * scale }; // y取反因为Canvas的y轴向下 } } } class Particle { constructor(cx, cy, scale) { const p randomHeartPoint(scale); this.x cx p.x; this.y cy p.y; this.vx (Math.random() - 0.5) * 1.5; this.vy -(Math.random() * 2.5 1); this.life 1.0; this.decay 0.006 Math.random() * 0.008; this.size 2 Math.random() * 4; this.hue 340 Math.random() * 30; // 红色到粉色 } update() { this.x this.vx; this.y this.vy; this.vy 0.02; this.vx * 0.99; this.vx (Math.random() - 0.5) * 0.1; this.life - this.decay; return this.life 0; } draw(ctx) { ctx.globalAlpha this.life; ctx.fillStyle hsl(${this.hue}, 100%, ${50 this.life * 20}%); ctx.beginPath(); ctx.arc(this.x, this.y, this.size * this.life, 0, Math.PI * 2); ctx.fill(); } } const particles []; const CENTER_X () canvas.width / 2; const CENTER_Y () canvas.height / 2 50; const SCALE () Math.min(canvas.width, canvas.height) * 0.15; function animate() { // 半透明黑色覆盖形成拖尾效果 ctx.fillStyle rgba(10, 10, 26, 0.15); ctx.fillRect(0, 0, canvas.width, canvas.height); // 生成新粒子 for (let i 0; i 5; i) { particles.push(new Particle(CENTER_X(), CENTER_Y(), SCALE())); } // 更新绘制 for (let i particles.length - 1; i 0; i--) { const p particles[i]; if (p.update()) { p.draw(ctx); } else { particles.splice(i, 1); } } requestAnimationFrame(animate); } animate(); /script /body /html这段代码里最值得说的是拖尾效果的实现。注意animate函数的第一行用半透明的黑色矩形覆盖整个画布。这样每一帧都会把上一帧的画面稍微擦掉一点但又不是完全擦掉形成一种残影效果。粒子的运动轨迹会留下淡淡的尾迹看起来更有速度感。rgba(10, 10, 26, 0.15)里的0.15是擦除强度。数值越小拖尾越长数值越大拖尾越短。我试过0.05拖尾太长导致画面糊成一片试过0.3拖尾几乎看不见。0.12到0.18之间是比较舒服的范围。另一个细节是hsl颜色。用HSL而不是RGB的好处是调整色相hue就能改变颜色饱和度和亮度可以独立控制。这里hue取340到370对应的是红色到粉紫色比纯红色更有层次感。5.3 拖尾、发光与颜色渐变的实现细节如果你想让效果更高级可以加两个东西发光和颜色渐变。发光效果用shadowBlur和shadowColor实现ctx.shadowBlur 15; ctx.shadowColor hsl(${this.hue}, 100%, 60%);加上这两行之后每个粒子周围会有一圈光晕整体看起来像霓虹灯。但要注意shadowBlur很吃性能粒子数量超过500个就会明显掉帧。我的建议是如果粒子数在300以内可以开超过300关掉或者把blur值调小。颜色渐变可以用粒子的生命值来驱动。比如生命值从1降到0的过程中色相从340红色渐变到280紫色亮度从60%降到30%。这样粒子在消散的过程中颜色也在变化视觉上更丰富。const hue 340 (1 - this.life) * 60; // 从340渐变到400即40橙红色 const lightness 30 this.life * 30; ctx.fillStyle hsl(${hue % 360}, 100%, ${lightness}%);6. C语言控制台版没有图形界面也能玩6.1 控制台字符渲染的可行性分析你可能会想C语言没有图形界面怎么做爱心飘散答案是用字符画。控制台本质上是一个字符网格比如80列×25行。我们可以把每个粒子映射到这个网格上的一个位置用不同的字符表示不同的粒子状态。比如*表示亮粒子.表示暗粒子空格表示没有粒子。这个方案的视觉效果当然比不上图形界面但它有两个独特的优势一是极致的轻量不需要任何图形库标准C就能跑二是独特的复古美感那种绿色字符在黑色终端上跳动的感觉有一种黑客帝国的味道。而且控制台版本是理解粒子系统原理的最佳方式。因为没有图形库帮你处理坐标变换、颜色渲染你必须自己处理所有细节做完之后对粒子系统的理解会深很多。6.2 用ANSI转义码实现彩色与定位C语言控制台做动态效果核心工具是ANSI转义码。这是一套控制终端光标位置、颜色、清屏的标准化指令。最常用的几个\033[2J清屏\033[H光标移到左上角\033[%d;%dH光标移到指定行列\033[38;2;%d;%d;%dm设置前景色为RGB\033[0m重置所有属性用这些指令我们可以在控制台的任意位置画任意颜色的字符然后每帧清屏重绘形成动画。#include stdio.h #include stdlib.h #include math.h #include time.h #include unistd.h #define WIDTH 80 #define HEIGHT 30 #define MAX_PARTICLES 200 typedef struct { float x, y; float vx, vy; float life; float decay; int r, g, b; } Particle; Particle particles[MAX_PARTICLES]; int particle_count 0; int heart_inside(float x, float y) { float x2 x * x, y2 y * y; float t x2 y2 - 1; return t * t * t - x2 * y * y * y 0; } void spawn_particle() { if (particle_count MAX_PARTICLES) return; float x, y; do { x (float)rand() / RAND_MAX * 3.0f - 1.5f; y (float)rand() / RAND_MAX * 3.0f - 1.5f; } while (!heart_inside(x, y)); Particle *p particles[particle_count]; p-x WIDTH / 2.0f x * 8.0f; p-y HEIGHT / 2.0f - y * 8.0f; p-vx ((float)rand() / RAND_MAX - 0.5f) * 0.8f; p-vy -((float)rand() / RAND_MAX * 1.5f 0.5f); p-life 1.0f; p-decay 0.008f (float)rand() / RAND_MAX * 0.008f; p-r 200 rand() % 55; p-g 50 rand() % 80; p-b 80 rand() % 100; } void update_particles() { int i 0; while (i particle_count) { Particle *p particles[i]; p-x p-vx; p-y p-vy; p-vy 0.02f; p-vx * 0.99f; p-vx ((float)rand() / RAND_MAX - 0.5f) * 0.1f; p-life - p-decay; if (p-life 0 || p-y 0 || p-x 0 || p-x WIDTH) { particles[i] particles[--particle_count]; } else { i; } } } void render() { // 构建字符画布 char canvas[HEIGHT][WIDTH]; int colors[HEIGHT][WIDTH][3]; for (int y 0; y HEIGHT; y) for (int x 0; x WIDTH; x) { canvas[y][x] ; colors[y][x][0] colors[y][x][1] colors[y][x][2] 0; } // 绘制粒子 for (int i 0; i particle_count; i) { Particle *p particles[i]; int px (int)p-x; int py (int)p-y; if (px 0 px WIDTH py 0 py HEIGHT) { canvas[py][px] p-life 0.6f ? * : (p-life 0.3f ? : .); colors[py][px][0] (int)(p-r * p-life); colors[py][px][1] (int)(p-g * p-life); colors[py][px][2] (int)(p-b * p-life); } } // 输出 printf(\033[H); for (int y 0; y HEIGHT; y) { for (int x 0; x WIDTH; x) { if (canvas[y][x] ) { printf( ); } else { printf(\033[38;2;%d;%d;%dm%c\033[0m, colors[y][x][0], colors[y][x][1], colors[y][x][2], canvas[y][x]); } } printf(\n); } } int main() { srand(time(NULL)); printf(\033[2J); // 清屏 while (1) { for (int i 0; i 3; i) spawn_particle(); update_particles(); render(); usleep(50000); // 50ms约20帧/秒 } return 0; }6.3 帧率控制与闪烁问题的解决控制台版本最大的问题是闪烁。因为每帧都要清屏重绘如果帧率太高屏幕会闪得眼睛疼如果帧率太低动画又不流畅。解决方案是双缓冲先在内存里构建好整个画面然后一次性输出。上面的代码就是这么做的——先填充canvas数组然后一次性printf出来。这样虽然还是会有轻微闪烁但比逐字符输出好得多。另一个技巧是减少清屏频率。不用\033[2J清屏而是用\033[H把光标移到左上角然后覆盖写入。因为每帧输出的字符数是一样的80×30覆盖写入不会留下残影。帧率控制用usleep。50毫秒对应20帧/秒对于控制台动画来说足够了。太快了闪烁明显太慢了动作卡顿。如果你觉得20帧还是闪可以试试33毫秒30帧/秒但有些终端可能跟不上。注意ANSI转义码在Windows的cmd里默认不支持需要用Windows Terminal或者开启VT100支持。如果你在Windows上跑建议用WSL或者直接换Linux/macOS。7. 三种方案的选型对比与扩展思路7.1 性能、门槛与效果的三角权衡维度Python turtleHTML CanvasC语言控制台安装门槛极低标准库极低浏览器低编译器性能上限约500粒子约5000粒子约200粒子视觉效果中等高复古代码可读性高高中分享便利性低需环境极高发链接低适合场景学习原理实际展示终端爱好者选型建议很直接想快速看效果、发给朋友选HTML Canvas想学原理、改参数玩选Python turtle想在终端里搞点花样选C语言。7.2 从飘散到爆炸粒子效果的参数化改造这套粒子系统的框架是通用的改几个参数就能变成完全不同的效果。爆炸效果把初速度的方向改成从中心向外辐射速度大小加大3到5倍重力系数改成0或者很小decay加大到0.02。粒子会从爱心中心炸开然后快速消散。下雨效果初速度改成向下重力系数加大到0.1水平速度随机范围加大。粒子会像雨滴一样从爱心上方落下。旋转效果给每个粒子加一个角速度每帧更新粒子的角度然后根据角度旋转粒子的位置。这样粒子会绕着爱心中心旋转飘散形成漩涡效果。文字效果把爱心内部的随机点改成从文字轮廓提取的点。先用Canvas的measureText获取文字路径然后从路径上采样点作为粒子的初始位置。这样就能做出文字飘散的效果。7.3 把爱心换成任意形状参数方程的通用套路爱心只是参数方程的一个例子。任何可以用参数方程描述的形状都可以套用这套粒子系统。比如五角星的参数方程x cos(t) * (1 0.5 * cos(5t)) y sin(t) * (1 0.5 * cos(5t))花瓣的参数方程r 1 0.5 * sin(6t) x r * cos(t) y r * sin(t)心形线的另一种形式r 1 - sin(t) x r * cos(t) y r * sin(t)你只需要把heart_inside函数换成对应的不等式判断或者把random_heart_point换成从参数方程采样就能把爱心换成任意形状。这就是参数化设计的威力——一套框架无限形状。8. 那些年我踩过的坑8.1 爱心画出来像屁股的三种原因第一种原因是参数方程写错了。最常见的错误是把sin³(t)写成了sin(t³)或者把cos(2t)写成了cos²(t)。这两个错误都会导致形状完全变形。检查方法很简单把 t0 代入正确的方程应该得到 x0, y13-5-2-15如果算出来不对就是方程写错了。第二种原因是坐标轴比例不对。如果x轴和y轴的缩放比例不一样爱心会被拉长或压扁。比如x方向放大10倍y方向放大5倍爱心就会变得又宽又扁看起来像屁股。解决方法是x和y用同一个scale系数。第三种原因是y轴方向反了。在数学坐标系里y轴向上在屏幕坐标系里y轴向下。如果不做转换爱心会上下颠倒尖角朝上看起来也很奇怪。解决方法是在计算y坐标时取负号或者用screen_height - y来转换。8.2 粒子数量与帧率的平衡点我做过一组测试在同一台机器上跑HTML Canvas版本记录不同粒子数量下的帧率粒子数量帧率fps视觉感受10060稀疏但流畅30060适中最佳平衡点50055丰富轻微掉帧100040密集明显卡顿200022很密集卡顿严重50008几乎不可用结论很明确300到500个粒子是视觉效果和性能的最佳平衡点。低于300显得稀疏高于500开始掉帧。如果你想要更密集的效果不要增加粒子数量而是增加粒子的尺寸和拖尾长度用视觉密度代替实际密度。8.3 颜色搭配的常见误区爱心飘散效果的颜色搭配最常见的误区是颜色太杂。有人喜欢给粒子随机分配红、橙、黄、绿、蓝、紫各种颜色结果看起来像打翻了调色盘完全没有爱心的感觉。正确的做法是限定色相范围。爱心主题的颜色应该集中在红色到粉色之间色相范围大约在330到370对应红色到粉紫。在这个范围内随机既能保证颜色有变化又不会偏离主题。另一个误区是亮度太高。纯红色hsl(0, 100%, 50%)在黑色背景上很刺眼看久了眼睛累。把亮度降到40%到60%之间颜色会更柔和整体效果更高级。还有一个细节粒子的颜色应该随生命值变化。刚生成时颜色最亮最饱和随着生命值降低颜色逐渐变暗变灰。这样粒子在消散过程中有一个自然的褪色过程比突然消失自然得多。8.4 不同浏览器的兼容性差异HTML Canvas版本在主流浏览器上表现基本一致但有几个细节需要注意requestAnimationFrame的帧率大多数浏览器锁定在60fps但在高刷新率屏幕上120Hz、144Hz会跑到更高帧率。这会导致动画速度变快。解决方法是在update函数里乘以一个时间系数让动画速度与帧率无关。shadowBlur的性能在Chrome上表现良好在Firefox上稍微慢一些在Safari上最慢。如果要做跨浏览器兼容建议把shadowBlur的值控制在10以内或者干脆不用。hsl颜色的支持所有现代浏览器都支持但如果你需要兼容很老的浏览器比如IE需要用RGB代替。移动端适配手机屏幕的像素密度很高Canvas的默认尺寸会导致画面模糊。解决方法是用window.devicePixelRatio来调整Canvas的实际像素尺寸然后用CSS把显示尺寸缩回来。const dpr window.devicePixelRatio || 1; canvas.width window.innerWidth * dpr; canvas.height window.innerHeight * dpr; canvas.style.width window.innerWidth px; canvas.style.height window.innerHeight px; ctx.scale(dpr, dpr);这段代码能让Canvas在高分屏上清晰渲染同时保持逻辑坐标不变。9. 最后分享几个实用小技巧如果你想让爱心飘散效果更出彩可以试试这几个我实测有效的技巧。技巧一粒子生成位置加一点随机偏移。不要严格从爱心内部生成而是在爱心轮廓附近也生成一些粒子。这样爱心边缘会有毛茸茸的感觉比严格的实心爱心更有质感。技巧二用正弦波控制粒子的水平漂移。给每个粒子一个随机的相位然后每帧用sin(frame * frequency phase)来计算水平偏移。这样粒子会像水草一样左右摇摆比纯随机扰动更有机。技巧三加一个心跳节奏。让粒子的生成速率随时间周期性变化比如用sin(time)来控制每帧生成的粒子数。这样爱心会像心脏一样跳动生成粒子时多时少节奏感很强。技巧四背景加一点渐变。纯黑背景虽然经典但加一点深蓝到深紫的径向渐变会让整个画面更有空间感。用Canvas的createRadialGradient从中心向外渐变中心稍微亮一点边缘暗下去。技巧五保存为GIF或视频。如果你想把效果分享到不支持交互的平台可以用屏幕录制工具录下来然后转成GIF。录制时建议把帧率设为30fps分辨率控制在800×600以内这样文件不会太大。这些技巧都不需要改核心逻辑只是在现有框架上加几行代码。你可以一个一个试看看哪个最符合你的审美。我个人的偏好是技巧二和技巧三的组合——粒子左右摇摆加上心跳节奏出来的效果最活。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。