【OpenHarmony/HarmonyOS】ArkUI 科幻星空动效:Canvas 粒子、流星与声明式多层动画

📅 2026/7/24 1:11:21 👁️ 阅读次数 📝 编程学习
【OpenHarmony/HarmonyOS】ArkUI 科幻星空动效:Canvas 粒子、流星与声明式多层动画

【OpenHarmony/HarmonyOS】ArkUI 科幻星空动效:Canvas 粒子、流星与声明式多层动画

如何不依赖视频背景,在 HarmonyOS 页面中实现具有空间感的动态星空?本文拆解一个由 Canvas 星粒子、流星、渐变背景和 ArkUI 几何动画共同组成的可复用组件。🌌

一、把背景拆成不同频率的图层

项目的GalaxyBackground不是一张静态图,而由四层组成:

  1. 静态深色渐变底;
  2. 缓慢旋转的模糊星云;
  3. Canvas 绘制的 100 颗星与随机流星;
  4. ArkUI Circle、Polygon、Rect 组成的旋转能量阵。

这种分层让不同内容使用最合适的工具:大量小粒子交给 Canvas,规则几何和声明式动画交给 ArkUI。

二、星星的数据结构

interfaceStar { x:number; y:number;size:number; speed:number; alpha:number; twinkleSpeed:number; }

初始化时为每颗星随机位置、大小、速度和闪烁相位:

for(leti =0; i <100; i++) {this.stars.push({x:Math.random() *360,y:Math.random() *800,size:Math.random() *2+0.5,speed:Math.random() *0.5+0.1,alpha:Math.random(),twinkleSpeed:Math.random() *0.05+0.01}); }

逻辑使用 360×800 的参考坐标,绘制时根据 Canvas 实际宽高缩放。因此同一组星星可以覆盖不同屏幕。

三、60 FPS 更新星星和亮度

this.renderInterval = setInterval(() => {this.nebulaRotation +=0.1;this.stars.forEach(star => { star.y += star.speed; star.alpha += star.twinkleSpeed;if(star.alpha >1|| star.alpha <0.2) { star.twinkleSpeed = -star.twinkleSpeed; }if(star.y >800) { star.y = -10; star.x = Math.random() *360; } });this.updateShootingStar();this.drawGalaxy(); },16);

亮度到达上下边界时反转变化方向,形成闪烁。星星超出底部后回到顶部并随机 X 坐标,形成持续流动。

严格来说,这段运动没有乘deltaTime,因此定时器延迟会让速度变慢。可复用组件最好记录真实帧间隔,让动画在不同负载下保持一致。

四、随机生成流星 ☄️

每次更新有约 1% 概率创建一颗流星:

if(!this.shootingStar&&Math.random() <0.01) {this.shootingStar= {x:Math.random() *360,y: -50,length:Math.random() *50+50,speed:Math.random() *15+10,angle:Math.PI/4}; }

绘制时用线性渐变从不透明白到透明:

const gradient = ctx.createLinearGradient( startX, startY,endX,endY ); gradient.addColorStop(0, 'rgba(255,255,255,1)'); gradient.addColorStop(1, 'rgba(255,255,255,0)'); ctx.beginPath(); ctx.strokeStyle = gradient; ctx.moveTo(startX, startY); ctx.lineTo(endX,endY); ctx.stroke();

一条带透明尾迹的线就能产生流星感,不需要图片序列。

五、Canvas 适配实际尺寸

constwidth = ctx.width;constheight = ctx.height;constscaleX = width /360;constscaleY = height /800; ctx.arc( star.x * scaleX, star.y * scaleY, star.size,0, Math.PI *2);

当前大小没有随屏幕比例缩放,位置会铺满画面而星点半径保持视觉像素稳定,这通常比等比放大更自然。若参考坐标纵横比与横屏差异很大,scaleXscaleY不同会改变流星角度,可改为统一缩放加居中裁切。

六、声明式几何动画的优势

外层能量阵使用多个 Circle 的虚线描边、两个 Polygon 组成六芒星、Rect 旋转构成核心。每层只需绑定角度状态:

Stack() {Circle({width:720,height:720}).fill(Color.Transparent).stroke('rgba(0,229,255,0.4)').strokeDashArray([40,10,10,10,80,20]); } .rotate({angle: this.magicRotationSlow }) .animation({duration:80000,iterations: -1,curve: Curve.Linear }) .onAppear(() => { this.magicRotationSlow =360; });

中层反向旋转,内层更快旋转,核心使用 Alternate 动画做呼吸缩放。速度、方向、透明度不同后,几何元素自然产生纵深感。

七、背景组件必须不拦截事件

所有背景层都设置:

.hitTestBehavior(HitTestMode.None)

否则全屏 Canvas 或 Stack 会挡住登录按钮、商城列表和战斗 HUD。视觉背景没有交互语义,应彻底退出命中测试。

八、生命周期清理不能遗漏

组件出现时启动循环,消失时清除:

aboutToDisappear(): void {if(this.renderInterval !== -1) { clearInterval(this.renderInterval);this.renderInterval = -1; } }

如果页面路由离开后仍保留定时器,后台会继续更新星星和调用 Canvas,既耗电又可能引用失效上下文。所有动画源都应有对称的停止逻辑。

九、复用组件时要警惕多实例成本

启动页、主页、设置、商城和排行榜都可能使用同一个背景组件。路由栈如果同时保留多个页面,可能存在多个 60 FPS 定时器。

优化方向包括:

  • 页面不可见时暂停;
  • 静态页面将星空降到 30 FPS;
  • 共享统一帧调度器;
  • 根据画质设置减少星数和模糊;
  • reduceMotion偏好开启时只保留静态背景;
  • 避免多个大半径blur同时持续动画。

十、不要在 Canvas 热循环里驱动大量 @State

stars被声明为@State,但内部对象属性在定时器中原地变化,真正绘制由 Canvas 手动完成。既然不依赖 ArkUI 重建星星节点,可将高频粒子数据作为普通私有字段,只把必要的声明式角度保留为状态,减少状态观察开销和语义混淆。

十一、如何让背景更适合产品而不是 Demo? 🎨

  • 降低星云模糊和中心图案对文字的干扰;
  • 不在所有页面使用同样强度,设置页应更安静;
  • 根据页面焦点改变局部亮度,而不是堆叠更多装饰;
  • 统一颜色 Token,避免每个组件复制十六进制值;
  • 为低性能模式关闭阴影和大模糊;
  • 对小屏检查 800px 圆环是否裁切得自然;
  • 让背景只负责氛围,不与主操作争夺对比度。

十二、总结 ✨

这个星空组件展示了一种实用组合:

  • Canvas 处理数量多、形状简单的星粒子和流星;
  • ArkUI 处理规则几何与长周期声明式动画;
  • 参考坐标适配不同屏幕;
  • 图层使用不同速度和方向建立纵深;
  • 背景退出命中测试;
  • 生命周期结束时清理定时器;
  • 画质、页面可见性和多实例数量共同决定性能。

真正成熟的动效不是元素越多越好,而是在不影响阅读、交互和功耗的前提下,让页面具有稳定而有辨识度的氛围。🌠


推荐标签:HarmonyOSOpenHarmonyArkUI动画Canvas粒子效果ArkTS