阅读时长:约 19 分钟 | 难度:★★★★☆ | 篇章:第 2 篇 · 启动体验:Splash 与动画
对应源码:entry/src/main/ets/pages/SplashPage.ets中的drawStarField方法
前言
玄象项目启动页的"星空背景"是整个应用视觉调性的奠基。深邃夜空、百点繁星、银河流光——这套视觉语言用纯 Canvas API 实现,未引入任何图片资源。本篇将深入剖析玄象项目drawStarField方法的实现细节:从CanvasRenderingContext2DAPI、Math.random()随机散点算法、createLinearGradient银河渐变,到绘制性能优化与可复用性重构。掌握这套 Canvas 绘制方法论,您就能用纯代码绘制任何复杂视觉场景。
提示:玄象项目星空背景未引入图片,是因为 Canvas 矢量绘制具有"无极缩放、极小体积、动态可控"三大优势。对于启动页这种轻量场景,纯 Canvas 实现是最优解。
一、drawStarField 完整源码
1.1 源码全貌
privatedrawStarField=(canvas:CanvasRenderingContext2D,canvasWidth:number,canvasHeight:number)=>{// 1. 绘制深色背景canvas.fillStyle=Colors.BG_DARK;canvas.fillRect(0,0,canvasWidth,canvasHeight);// 2. 绘制星星for(leti=0;i<100;i++){constx=Math.random()*canvasWidth;consty=Math.random()*canvasHeight*0.7;constsize=Math.random()*2+0.5;constalpha=Math.random()*0.8+0.2;canvas.fillStyle=`rgba(255, 255, 220,${alpha})`;canvas.beginPath();canvas.arc(x,y,size,0,Math.PI*2);canvas.fill();}// 3. 银河效果constgradient=canvas.createLinearGradient(0,0,canvasWidth,canvasHeight*0.5);gradient.addColorStop(0,'rgba(212, 168, 67, 0)');gradient.addColorStop(0.5,'rgba(212, 168, 67, 0.05)');gradient.addColorStop(1,'rgba(212, 168, 67, 0)');canvas.fillStyle=gradient;canvas.fillRect(0,0,canvasWidth,canvasHeight*0.5);}1.2 三大绘制阶段
玄象项目星空绘制分为三大阶段:
| 阶段 | 操作 | 视觉效果 |
|---|---|---|
| 背景填充 | fillRect全屏深色 | 深邃夜空 |
| 星点散布 | 100 个随机星点 | 繁星点点 |
| 银河渐变 | 线性渐变带 | 银河流光 |
二、CanvasRenderingContext2D 核心 API
2.1 fillStyle 填充样式
canvas.fillStyle=Colors.BG_DARK;// 字符串色值canvas.fillStyle='rgba(255, 255, 220, 0.5)';// RGBA 字符串canvas.fillStyle=gradient;// 渐变对象fillStyle接受三种类型的值:
- CSS 颜色字符串:
'#D4A843'或'rgba(255,255,220,0.5)' - CanvasGradient 对象:通过
createLinearGradient创建 - CanvasPattern 对象:通过
createPattern创建
2.2 fillRect 绘制矩形
canvas.fillRect(0,0,canvasWidth,canvasHeight);fillRect(x, y, width, height)使用当前fillStyle填充矩形区域。
2.3 beginPath / arc / fill 绘制圆形
canvas.beginPath();canvas.arc(x,y,size,0,Math.PI*2);canvas.fill();玄象项目用三步绘制每个星点:
beginPath:开始一条新路径arc(x, y, radius, startAngle, endAngle):绘制圆形路径fill:填充当前路径
2.4 arc 角度参数详解
arc的角度参数:
| 参数 | 含义 |
|---|---|
x | 圆心 x 坐标 |
y | 圆心 y 坐标 |
radius | 半径 |
startAngle | 起始角度(弧度) |
endAngle | 结束角度(弧度) |
绘制完整圆:startAngle = 0,endAngle = Math.PI * 2。
提示:
Math.PI * 2等于 360 度。玄象项目绘制完整星点使用此值。
三、随机散点算法
3.1 星点参数随机化
for(leti=0;i<100;i++){constx=Math.random()*canvasWidth;consty=Math.random()*canvasHeight*0.7;constsize=Math.random()*2+0.5;constalpha=Math.random()*0.8+0.2;// 绘制星点}玄象项目为每个星点生成 4 个随机参数:
| 参数 | 计算公式 | 取值范围 |
|---|---|---|
x | Math.random() * canvasWidth | [0, canvasWidth] |
y | Math.random() * canvasHeight * 0.7 | [0, 0.7 × canvasHeight] |
size | Math.random() * 2 + 0.5 | [0.5, 2.5] |
alpha | Math.random() * 0.8 + 0.2 | [0.2, 1.0] |
3.2 y 坐标限制的设计意图
consty=Math.random()*canvasHeight*0.7;玄象项目刻意将星点的 y 坐标限制在屏幕上方 70% 区域,原因:
- 避开底部星图区:下方 40% 区域有独立的星图装饰,星点不应与之重叠。
- 模拟真实星空:地平线附近星空稀疏,高处星空密集。
3.3 星点尺寸的分布规律
constsize=Math.random()*2+0.5;玄象项目星点尺寸分布:
- 小星点(0.5-1.0):占 25%,远处微星
- 中星点(1.0-2.0):占 50%,常态星点
- 大星点(2.0-2.5):占 25%,明亮主星
提示:
Math.random()返回 [0, 1) 区间的浮点数。Math.random() * 2 + 0.5的取值范围是 [0.5, 2.5)。
3.4 透明度的随机化
constalpha=Math.random()*0.8+0.2;canvas.fillStyle=`rgba(255, 255, 220,${alpha})`;玄象项目通过随机透明度营造星点闪烁感:
- 高透明度(0.8-1.0):明亮星点
- 中透明度(0.5-0.8):常态星点
- 低透明度(0.2-0.5):微弱星点
3.5 模板字符串拼接 RGBA
canvas.fillStyle=`rgba(255, 255, 220,${alpha})`;玄象项目使用模板字符串动态拼接颜色:
255, 255, 220:暖白色 RGB 值,模拟星光色温${alpha}:随机透明度
提示:玄象项目使用
(255, 255, 220)而非(255, 255, 255),是因为暖白色更符合东方夜空的视觉调性。
四、银河渐变带
4.1 createLinearGradient 线性渐变
constgradient=canvas.createLinearGradient(0,0,canvasWidth,canvasHeight*0.5);createLinearGradient(x0, y0, x1, y1)创建线性渐变:
| 参数 | 含义 | 玄象项目值 |
|---|---|---|
x0, y0 | 渐变起点 | (0, 0) 左上角 |
x1, y1 | 渐变终点 | (canvasWidth, 0.5 × canvasHeight) 右中部 |
渐变方向:从左上角到右中部,斜向 45 度流向。
4.2 addColorStop 颜色断点
gradient.addColorStop(0,'rgba(212, 168, 67, 0)');gradient.addColorStop(0.5,'rgba(212, 168, 67, 0.05)');gradient.addColorStop(1,'rgba(212, 168, 67, 0)');addColorStop(offset, color)添加颜色断点:
| offset | alpha | 视觉效果 |
|---|---|---|
| 0 | 0 | 完全透明 |
| 0.5 | 0.05 | 微弱金光 |
| 1 | 0 | 完全透明 |
4.3 银河带的设计意图
玄象项目银河带的设计意图:
- 强化中心:渐变带从透明到微亮再到透明,形成中间亮、两端暗的"光带"。
- 斜向流向:45 度斜向渐变模拟银河流动方向。
- 微妙金光:
rgba(212, 168, 67, 0.05)透明度仅 5%,避免抢夺主视觉。
4.4 银河带的覆盖区域
canvas.fillRect(0,0,canvasWidth,canvasHeight*0.5);银河带覆盖屏幕上方 50% 区域,与星点分布区域(上方 70%)部分重叠,营造星点在银河中漂浮的视觉。
五、Canvas 绘制性能优化
5.1 减少状态切换
玄象项目星空绘制存在的性能优化点:
// 当前实现:每次循环都修改 fillStylefor(leti=0;i<100;i++){canvas.fillStyle=`rgba(255, 255, 220,${alpha})`;// 状态切换// ...}// 优化方案 1:按 alpha 分组绘制constgroups={};for(leti=0;i<100;i++){constalpha=(Math.floor(Math.random()*8)+2)/10;// 离散化if(!groups[alpha])groups[alpha]=[];groups[alpha].push({x,y,size});}for(constalphaingroups){canvas.fillStyle=`rgba(255, 255, 220,${alpha})`;// 一次设置for(conststarofgroups[alpha]){canvas.beginPath();canvas.arc(star.x,star.y,star.size,0,Math.PI*2);canvas.fill();}}5.2 使用 Path2D 批量绘制
constpath=newPath2D();for(leti=0;i<100;i++){path.arc(x,y,size,0,Math.PI*2);}canvas.fill(path);// 一次填充所有路径5.3 离屏 Canvas 预渲染
对于静态星空,可使用离屏 Canvas预渲染一次:
privateoffscreenCanvas:OffscreenCanvas;aboutToAppear():void{this.offscreenCanvas=newOffscreenCanvas(canvasWidth,canvasHeight);constctx=this.offscreenCanvas.getContext('2d');this.drawStarField(ctx,canvasWidth,canvasHeight);}// 主 Canvas 直接 drawImagemainCtx.drawImage(this.offscreenCanvas,0,0);提示:玄象项目启动页仅渲染一次,性能优化收益不大。但在动画场景(如每帧重绘的星空)中,离屏 Canvas 可显著提升性能。
六、绘制函数的可复用性重构
6.1 当前实现的局限
玄象项目drawStarField是箭头函数赋值给类成员:
privatedrawStarField=(canvas,width,height)=>{// ...};这种写法存在局限:
- 无法继承:箭头函数无法被子类重写。
- 无法静态化:箭头函数不属于原型方法。
- 测试困难:箭头函数难以 Mock。
6.2 重构为静态方法
// common/utils/StarFieldRenderer.etsexportclassStarFieldRenderer{staticrender(canvas:CanvasRenderingContext2D,width:number,height:number,options:StarFieldOptions):void{canvas.fillStyle=options.bgColor;canvas.fillRect(0,0,width,height);for(leti=0;i<options.starCount;i++){constx=Math.random()*width;consty=Math.random()*height*options.starAreaRatio;constsize=Math.random()*options.maxStarSize+options.minStarSize;constalpha=Math.random()*0.8+0.2;canvas.fillStyle=`rgba(${options.starColor},${alpha})`;canvas.beginPath();canvas.arc(x,y,size,0,Math.PI*2);canvas.fill();}}}6.3 使用接口定义配置
interfaceStarFieldOptions{bgColor:string;// 背景色starCount:number;// 星点数量starColor:string;// 星点颜色 RGB(如 "255, 255, 220")minStarSize:number;// 最小星点尺寸maxStarSize:number;// 最大星点尺寸starAreaRatio:number;// 星点分布区域比例hasGalaxy:boolean;// 是否绘制银河galaxyColor:string;// 银河颜色}提示:玄象项目若推出"星宿详情页"等其他需要星空背景的页面,可直接复用
StarFieldRenderer,传入不同配置即可。
七、星空绘制的扩展方向
7.1 添加流星动画
privatemeteors:Meteor[]=[];privatedrawMeteors=(canvas:CanvasRenderingContext2D)=>{for(constmeteorofthis.meteors){constgradient=canvas.createLinearGradient(meteor.x,meteor.y,meteor.x-meteor.length*Math.cos(meteor.angle),meteor.y-meteor.length*Math.sin(meteor.angle));gradient.addColorStop(0,'rgba(255, 255, 220, 1)');gradient.addColorStop(1,'rgba(255, 255, 220, 0)');canvas.strokeStyle=gradient;canvas.lineWidth=2;canvas.beginPath();canvas.moveTo(meteor.x,meteor.y);canvas.lineTo(meteor.x-meteor.length*Math.cos(meteor.angle),meteor.y-meteor.length*Math.sin(meteor.angle));canvas.stroke();}};7.2 添加星点闪烁动画
privatestarTwinkle=(canvas:CanvasRenderingContext2D,stars:Star[])=>{for(conststarofstars){star.alpha+=star.twinkleSpeed*star.twinkleDirection;if(star.alpha>1||star.alpha<0.2){star.twinkleDirection*=-1;}canvas.fillStyle=`rgba(255, 255, 220,${star.alpha})`;canvas.beginPath();canvas.arc(star.x,star.y,star.size,0,Math.PI*2);canvas.fill();}};7.3 添加银河旋涡
privatedrawGalaxySwirl=(canvas:CanvasRenderingContext2D,centerX:number,centerY:number)=>{constarms=4;conststarsPerArm=200;for(letarm=0;arm<arms;arm++){for(leti=0;i<starsPerArm;i++){constangle=(arm/arms)*Math.PI*2+(i/starsPerArm)*Math.PI*4;constradius=(i/starsPerArm)*200;constx=centerX+Math.cos(angle)*radius;consty=centerY+Math.sin(angle)*radius;canvas.fillStyle=`rgba(212, 168, 67,${Math.random()*0.5+0.2})`;canvas.beginPath();canvas.arc(x,y,Math.random()*1.5+0.5,0,Math.PI*2);canvas.fill();}}};八、玄象项目 Canvas 绘制规范总结
8.1 Canvas 绘制四步法
玄象项目 Canvas 绘制遵循"四步法":
- 状态设置:
fillStyle/strokeStyle/lineWidth - 路径定义:
beginPath/moveTo/lineTo/arc - 填充或描边:
fill/stroke - 状态恢复:(可选)保存与恢复状态
8.2 Canvas 绘制规范表
| 场景 | API 组合 |
|---|---|
| 绘制矩形 | fillStyle+fillRect |
| 绘制圆形 | fillStyle+beginPath+arc+fill |
| 绘制线段 | strokeStyle+lineWidth+beginPath+moveTo+lineTo+stroke |
| 绘制渐变 | createLinearGradient+addColorStop+ 赋值给fillStyle |
| 绘制文本 | fillStyle+font+fillText |
总结
本篇以玄象项目drawStarField方法为蓝本,深入剖析了 HarmonyOS Canvas 绘制的实现细节:从CanvasRenderingContext2D核心 API、Math.random()随机散点算法、createLinearGradient银河渐变,到绘制性能优化(按 alpha 分组、Path2D 批量、离屏 Canvas)与可复用性重构(静态方法 + 接口配置)。掌握这套 Canvas 绘制方法论,您就能用纯代码绘制任何复杂视觉场景。
下一篇:《13 · Canvas 太极图算法:阴阳半圆 + 鱼眼小圆的数学推导》,将带您深入玄象项目启动页中央的太极图绘制算法。
如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!
相关资源:
- HarmonyOS 官方文档:CanvasRenderingContext2D
- HarmonyOS 官方文档:Canvas 组件
- MDN Canvas API:developer.mozilla.org/zh-CN/docs/Web/API/Canvas_API
- 开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net