)
在鸿蒙HarmonyOS系统中实现炫酷的粒子特效如烟花、雨雪、点赞气泡等主要有两条主流的技术路线一是使用 ArkUI 提供的原生声明式Particle组件二是基于Canvas与requestAnimationFrame进行纯代码自绘。一、 原生声明式方案ArkUI Particle 组件Particle是鸿蒙专为高性能粒子动画打造的声明式组件。它将大量微小图形的物理计算与渲染交由 GPU 批量处理即使同屏渲染数千个粒子也能保持高帧率非常适合实现沉浸式天气、直播间点赞气泡等场景。核心参数配置一个标准的粒子效果由三大要素构成发射器Emitter控制位置、频率、形状、运动模型Motion控制初速度、重力加速度以及外观演变Appearance控制颜色、透明度、大小的生命周期渐变。沉浸式天气与点赞气泡通过配置Particle组件可以轻松实现暴雨利用scale属性在 Y 轴拉伸粒子模拟雨丝、飞雪利用acceleration.angle模拟风的扰动。对于直播间点赞气泡可将常态emitRate设为较低值在用户点击时瞬间调高发射率并配置大容量粒子池如count: 3000以防止高频发射时断流。交互式涟漪效果结合onTouch事件可以动态修改粒子发射器的坐标position并设置emitOnce: true从而实现手指点击屏幕任意位置即可爆发一圈涟漪的交互特效。// ParticleEffectsDemo.ets Entry Component struct ParticleEffectsDemo { State emitterPos: { x: number, y: number } { x: 0, y: 0 } State triggerRipple: number 0 build() { Stack() { // 1. 沉浸式飞雪特效 Particle({ particles: [{ emitter: { particle: { type: ParticleType.POINT, config: { radius: 3 }, count: 80 }, position: { x: 0, y: 0 }, size: { width: 360, height: 50 }, // 顶部发射窗口 emitRate: 30, lifetime: 8000 }, color: { range: [rgba(255,255,255,0.9), rgba(200,220,255,0.7)] }, speed: { range: [30, 60] }, acceleration: { speed: { range: [5, 10] }, angle: { range: [90, 90] } // 模拟风的扰动 }, scale: { range: [0.5, 1.2], updater: { type: ParticleUpdater.LINEAR, config: { from: 0.5, to: 0, startMillis: 0, endMillis: 8000 } } } }] }).width(100%).height(100%) // 2. 交互式涟漪特效 Particle({ particles: [{ emitter: { particle: { type: ParticleType.POINT, config: { radius: 8 }, count: 30 }, position: { x: this.emitterPos.x, y: this.emitterPos.y }, size: { width: 1, height: 1 }, lifetime: 1000, emitOnce: true // 每次点击只发射一次 }, color: { range: [#FFB347, #FF6B6B] }, speed: { range: [50, 120] }, acceleration: { speed: { range: [-20, -50] }, // 减速停止 angle: { range: [0, 360] } // 360度辐射 } }] }) .width(100%) .height(100%) .key(ripple_ this.triggerRipple) // 通过修改key强制重建组件触发emitOnce .onTouch((event) { if (event.type TouchType.Down) { this.emitterPos { x: event.touches[0].x, y: event.touches[0].y } this.triggerRipple } }) }.width(100%).height(100%).backgroundColor(#0A1A2F) } }二、 纯代码自绘方案Canvas requestAnimationFrame对于需要完全精细控制每一个粒子轨迹、碰撞逻辑或复杂物理仿真的场景如逼真的烟花爆炸推荐使用Canvas结合requestAnimationFrame进行自绘。物理状态更新在每一帧的时钟心跳中为每个粒子应用空气阻力速度衰减、注入垂直重力加速度并根据当前速度更新其 X/Y 轴坐标。同时粒子的透明度Alpha随时间自然损耗驱动其向消亡状态过渡。渲染管线与生命周期通过全局requestAnimationFrame挂载系统级的 VSync 帧率刷新回调支持 120Hz保障动画的极致丝滑。每帧先清空画布再遍历存活粒子调用draw()方法绘制圆形或图片最后剔除生命周期结束的粒子。// CanvasFireworkDemo.ets class FireParticle { x: number; y: number; vx: number; vy: number; life: number 1.0; color: string; constructor(x: number, y: number, color: string) { this.x x; this.y y; this.color color; const angle Math.random() * Math.PI * 2; const speed Math.random() * 5 2; this.vx Math.cos(angle) * speed; this.vy Math.sin(angle) * speed; } update() { this.x this.vx; this.y this.vy; this.vy 0.1; // 注入重力加速度 this.vx * 0.98; // 空气阻力衰减 this.vy * 0.98; this.life - 0.02; // 透明度自然损耗 } } Entry Component struct CanvasFireworkDemo { private settings: RenderingContextSettings new RenderingContextSettings(true); private ctx: CanvasRenderingContext2D new CanvasRenderingContext2D(this.settings); private particles: FireParticle[] []; private animationId: number 0; aboutToAppear() { // 模拟一次烟花爆炸 for (let i 0; i 100; i) { this.particles.push(new FireParticle(180, 320, hsl(${Math.random()*360}, 100%, 60%))); } this.startAnimation(); } private startAnimation() { const animate () { this.ctx.clearRect(0, 0, 360, 640); // 遍历更新并绘制存活粒子 for (let i this.particles.length - 1; i 0; i--) { const p this.particles[i]; p.update(); if (p.life 0) { this.particles.splice(i, 1); // 剔除生命周期结束的粒子 } else { this.ctx.globalAlpha p.life; this.ctx.beginPath(); this.ctx.arc(p.x, p.y, 3, 0, Math.PI * 2); this.ctx.fillStyle p.color; this.ctx.fill(); } } this.ctx.globalAlpha 1.0; if (this.particles.length 0) { this.animationId requestAnimationFrame(animate); } }; animate(); } aboutToDisappear() { cancelAnimationFrame(this.animationId); // 防止内存泄漏 } build() { Canvas(this.ctx).width(100%).height(100%).backgroundColor(Color.Black) } }三、 性能优化在鸿蒙上实现流畅的粒子特效工程优化与算法设计同等重要对象池Object Pool管理严禁在动画循环中频繁使用new Particle()创建和销毁对象。这会导致严重的 JS 垃圾回收GC停顿和内存抖动。应提前预创建一批粒子对象用完不销毁而是放回池中复用可大幅降低内存峰值并消除卡顿。分帧渲染策略当粒子数量极多如超过 300 个时单帧内的物理计算会阻塞主线程。可将粒子的更新任务拆分到多个小批次中配合setTimeout或requestAnimationFrame分帧执行避免主线程长时间占用。真机调试与状态变更模拟器的渲染引擎与真实设备差异较大粒子特效的性能测试必须以真机为准。此外尽量减少globalAlpha的频繁设置粒子颜色建议优先使用rgba()而非hsl()以降低 Canvas 的解析开销。// ParticlePool.ets export class ParticlePool { private static pool: FireParticle[] []; private static MAX_POOL_SIZE 300; // 从池中获取或新建粒子 public static obtain(x: number, y: number, color: string): FireParticle { if (this.pool.length 0) { const p this.pool.pop()!; // 复用实例重新初始化运动学参数 p.x x; p.y y; p.color color; p.life 1.0; return p; } return new FireParticle(x, y, color); } // 粒子消亡时回收到池中 public static recycle(p: FireParticle) { if (this.pool.length this.MAX_POOL_SIZE) { this.pool.push(p); } } }四、 高阶物理模型与Canvas自绘实战当原生声明式组件无法满足复杂的物理交互需求时基于 Canvas 的自绘方案是最佳选择。半隐式欧拉积分Semi-implicit Euler在真实的烟花爆炸场景中粒子的运动学重算通常采用半隐式欧拉积分进行近似模拟。通过引入空气摩擦系数Drag和重力加速度Gravity在每一帧中先更新粒子的瞬时速度受摩擦阻力影响再更新其二维坐标位置。离屏双缓冲区与批量绘制为突破主线程丢帧INP的性能瓶颈可引入离屏双缓冲区Offscreen Canvas Buffer技术。先在后台离屏画布中完成粒子的擦除与重绘再一次性输出至前台画布。同时采用批量渲染Batching Drawing策略减少绘制指令的调用次数从而在 120Hz 高刷下实现极致的流畅度。// AdvancedPhysicsRenderer.ets export class AdvancedPhysicsRenderer { // 1. 半隐式欧拉积分先更新速度再更新位置 public static updateParticle(p: { x: number, y: number, vx: number, vy: number }, gravity: number, friction: number) { // 应用空气阻力与重力 p.vx * friction; p.vy (p.vy * friction) gravity; // 更新二维坐标 p.x p.vx; p.y p.vy; } // 2. 离屏双缓冲区与批量绘制在 Canvas 中应用 public static drawBatch(ctx: CanvasRenderingContext2D, particles: any[]) { // 批量渲染减少绘制指令的调用次数 ctx.beginPath(); for (const p of particles) { ctx.moveTo(p.x, p.y); ctx.arc(p.x, p.y, p.radius, 0, Math.PI * 2); } ctx.fill(); } }五、 多层粒子合成与复合视觉特效顶级的视觉表现往往不是单一粒子系统能够完成的而是需要多套粒子引擎的协同工作。两段式复合烟花为了还原真实的烟花绽放需要构建两段式动画首先是发射阶段一个光点从底部快速上升其次是爆炸阶段在光点到达最高点时瞬间向四周迸发大量彩色粒子。这通常需要两个独立的粒子系统或 Canvas 绘制管线进行无缝衔接。多层级叠加渲染在复杂的爆炸或火焰场景中可以同时使用火花粒子、烟雾粒子和碎片粒子。每种粒子负责表现爆炸的不同物理特征如火花负责发光与高亮烟雾负责体积感与消散最终合成出层次丰富、细节精美的综合视觉效果。// CompositeFirework.ets export class CompositeFirework { private rocket: { x: number, y: number, vy: number, targetY: number } | null null; private explosionParticles: any[] []; // 1. 发射阶段光点从底部上升 public launch(startX: number, startY: number, targetY: number) { this.rocket { x: startX, y: startY, vy: -15, targetY }; } // 2. 爆炸阶段到达最高点时瞬间迸发彩色粒子 public update() { if (this.rocket) { this.rocket.y this.rocket.vy; if (this.rocket.y this.rocket.targetY) { this.explode(this.rocket.x, this.rocket.y); this.rocket null; // 销毁发射器 } } // 更新爆炸粒子复用欧拉积分 for (let i this.explosionParticles.length - 1; i 0; i--) { AdvancedPhysicsRenderer.updateParticle(this.explosionParticles[i], 0.15, 0.96); if (this.explosionParticles[i].life 0) { this.explosionParticles.splice(i, 1); } } } private explode(x: number, y: number) { for (let i 0; i 50; i) { const angle Math.random() * Math.PI * 2; const speed Math.random() * 5 2; this.explosionParticles.push({ x, y, vx: Math.cos(angle) * speed, vy: Math.sin(angle) * speed, life: 1.0 }); } } }六、 渲染排序与混合模式Blend Mode为了让粒子特效更具立体感和真实感渲染器层面的配置至关重要。加色混合Additive Blending对于烟花、火光等发光特效应将 Canvas 的混合模式设置为lighter加色混合。这样当多个粒子重叠时其颜色值会相加产生耀眼的发光叠加效果而非互相遮挡。粒子排序策略在渲染器中可以根据业务需求配置粒子的绘制顺序。例如采用distance基于距离排序近大远小或oldestFirst基于生命周期排序老粒子先绘制以确保复杂场景下的视觉层级正确。// AdaptiveParticleManager.ets import { deviceInfo } from kit.BasicServicesKit; export class AdaptiveParticleManager { private static maxParticles: number 3000; private static burstCount: number 100; // 1. 根据设备性能动态调整粒子池容量 public static init() { // 模拟设备性能分级实际可通过 benchmark 或设备型号判断 const isHighEnd deviceInfo.brand HUAWEI deviceInfo.model.includes(Mate); if (!isHighEnd) { this.maxParticles 800; // 轻量级设备缩减总容量 this.burstCount 30; // 缩减单次爆发数量 } } // 2. 生命周期与内存安全严格回收机制 public static safeRecycle(particles: any[], pool: any[]) { for (let i particles.length - 1; i 0; i--) { if (particles[i].life 0) { // 确保透明度降至0时立即回收严防内存泄漏 if (pool.length this.maxParticles) { pool.push(particles[i]); } particles.splice(i, 1); } } } }七、 跨设备性能自适应与动态降级鸿蒙生态涵盖手机、平板、手表等多种终端粒子特效必须具备极强的硬件自适应能力。动态粒子池容量对于性能强劲的手机如 Mate 系列可以将单次爆发的粒子数量设置为 80~100 个粒子池总容量设为 3000 以上以营造震撼的视觉氛围而对于智能穿戴等轻量级设备则需自动将粒子数量缩减至 30 个以内并降低发射频率确保核心业务不卡顿、设备不发烫。生命周期与内存安全无论采用何种方案都必须为每个粒子设定严格的生命周期Alpha 衰减。当粒子透明度降至 0 时必须立即将其回收至对象池或从数组中剔除严防因无限累积导致的内存泄漏和系统崩溃。