
前言
品牌闪屏 是用户打开应用后看到的第一画面,其视觉品质直接影响用户对应用的第一印象。"海风日记"的闪屏页通过 Canvas 2D API 绘制了多层波浪动画,配合暖金渐变背景,营造出"海风拂面"的品牌氛围。
本文将从 SplashPage.ets 源码出发,深入讲解 Canvas 2D 绘图的完整流程,包括渲染上下文获取、贝塞尔曲线绘制波浪、多层叠加实现立体感以及动画循环的实现。
一、Canvas 组件概述
1.1 Canvas 的基本用法
Canvas 是 ArkUI 提供的画布组件,支持 2D 绘图 API:
typescript
@Entry
@Component
struct SplashPage {
private settings: RenderingContextSettings = new RenderingContextSettings(true)
private ctx: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings)
build() {
Canvas(this.ctx)
.width('100%')
.height(300)
.onReady(() => {
this.drawWaves()
})
}
}
1.2 Canvas 的关键要素
| 要素 | 类型 | 说明 |
|---|---|---|
RenderingContextSettings |
配置类 | 控制 Canvas 渲染设置,如是否抗锯齿 |
CanvasRenderingContext2D |
渲染上下文 | 提供 2D 绘图 API |
Canvas 组件 |
容器组件 | 渲染画布,显示绘图内容 |
onReady 回调 |
生命周期 | Canvas 就绪后触发,开始绘图 |
1.3 Canvas 与 Shape 的对比
| 对比维度 | Canvas | Shape |
|---|---|---|
| 绘图方式 | 命令式 JS API | 声明式 XML 描述 |
| 适用场景 | 动态绘图、动画 | 静态矢量图形 |
| 性能 | 适合复杂绘图 | 适合简单图形 |
| 动画 | 需要 requestAnimationFrame | 配合 animateTo |
| 代码量 | 较多 | 较少 |
二、Canvas 渲染上下文的创建
2.1 RenderingContextSettings
typescript
private settings: RenderingContextSettings = new RenderingContextSettings(true)
RenderingContextSettings 构造函数的参数:
| 参数 | 类型 | 说明 |
|---|---|---|
antialias |
boolean | 是否开启抗锯齿,true 为开启 |
2.2 CanvasRenderingContext2D
typescript
private ctx: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings)
CanvasRenderingContext2D 是绘图的核心对象,提供了以下 API 类别:
| API 类别 | 方法 | 说明 |
|---|---|---|
| 路径 | beginPath, moveTo, lineTo, bezierCurveTo |
绘制路径 |
| 样式 | fillStyle, strokeStyle |
设置填充和描边样式 |
| 填充 | fill, stroke |
填充和描边 |
| 清除 | clearRect |
清除画布区域 |
| 文字 | fillText, strokeText |
绘制文字 |
| 图片 | drawImage |
绘制图片 |
2.3 Canvas 的尺寸管理
typescript
@State canvasW: number = 390
@State canvasH: number = 300
Canvas(this.ctx)
.width('100%')
.height(300)
.onReady(() => {
this.canvasW = 390
this.canvasH = 300
this.drawWaves()
})
三、多层波浪绘制
3.1 波浪层 1(底层,最浅)
typescript
drawWaves() {
const w = this.canvasW
const h = this.canvasH
this.ctx.clearRect(0, 0, w, h)
// 波浪层 1(底层,最浅)
this.ctx.beginPath()
this.ctx.moveTo(0, h * 0.55)
this.ctx.bezierCurveTo(w * 0.2, h * 0.40, w * 0.4, h * 0.70, w * 0.6, h * 0.50)
this.ctx.bezierCurveTo(w * 0.8, h * 0.30, w * 0.9, h * 0.60, w, h * 0.48)
this.ctx.lineTo(w, h)
this.ctx.lineTo(0, h)
this.ctx.closePath()
this.ctx.fillStyle = 'rgba(255,210,140,0.35)'
this.ctx.fill()
}
3.2 波浪层 2(中层)
typescript
// 波浪层 2(中层)
this.ctx.beginPath()
this.ctx.moveTo(0, h * 0.65)
this.ctx.bezierCurveTo(w * 0.25, h * 0.50, w * 0.45, h * 0.80, w * 0.65, h * 0.62)
this.ctx.bezierCurveTo(w * 0.85, h * 0.45, w * 0.92, h * 0.72, w, h * 0.60)
this.ctx.lineTo(w, h)
this.ctx.lineTo(0, h)
this.ctx.closePath()
this.ctx.fillStyle = 'rgba(245,166,35,0.25)'
this.ctx.fill()
3.3 波浪层 3(前景,最深)
typescript
// 波浪层 3(前景,最深)
this.ctx.beginPath()
this.ctx.moveTo(0, h * 0.75)
this.ctx.bezierCurveTo(w * 0.15, h * 0.62, w * 0.35, h * 0.88, w * 0.55, h * 0.72)
this.ctx.bezierCurveTo(w * 0.75, h * 0.58, w * 0.88, h * 0.80, w, h * 0.70)
this.ctx.lineTo(w, h)
this.ctx.lineTo(0, h)
this.ctx.closePath()
this.ctx.fillStyle = 'rgba(200,149,106,0.30)'
this.ctx.fill()
3.4 三层波浪的参数对比
| 参数 | 波浪层 1 | 波浪层 2 | 波浪层 3 |
|---|---|---|---|
| 起始 Y 位置 | h * 0.55 |
h * 0.65 |
h * 0.75 |
| 控制点 1 | (w*0.2, h*0.40) |
(w*0.25, h*0.50) |
(w*0.15, h*0.62) |
| 控制点 2 | (w*0.4, h*0.70) |
(w*0.45, h*0.80) |
(w*0.35, h*0.88) |
| 控制点 3 | (w*0.6, h*0.50) |
(w*0.65, h*0.62) |
(w*0.55, h*0.72) |
| 控制点 4 | (w*0.8, h*0.30) |
(w*0.85, h*0.45) |
(w*0.75, h*0.58) |
| 填充颜色 | rgba(255,210,140,0.35) |
rgba(245,166,35,0.25) |
rgba(200,149,106,0.30) |
四、贝塞尔曲线绘制详解
4.1 三次贝塞尔曲线
bezierCurveTo 绘制的是三次贝塞尔曲线,需要两个控制点和一个终点:
typescript
ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)
| 参数 | 说明 |
|---|---|
cp1x, cp1y |
第一个控制点坐标 |
cp2x, cp2y |
第二个控制点坐标 |
x, y |
终点坐标 |
4.2 波浪曲线的控制点分析
曲线段 1: (0, 0.55h) → (0.2w, 0.40h) → (0.4w, 0.70h) → (0.6w, 0.50h)
↑ 控制点 1 ↑ 控制点 2 ↑ 终点
曲线段 2: (0.6w, 0.50h) → (0.8w, 0.30h) → (0.9w, 0.60h) → (w, 0.48h)
↑ 控制点 1 ↑ 控制点 2 ↑ 终点
4.3 波浪的视觉原理
通过调整贝塞尔曲线的控制点位置,可以改变波浪的形态:
| 控制点调整 | 效果 |
|---|---|
| 控制点 Y 值降低 | 波浪向上凸起 |
| 控制点 Y 值升高 | 波浪向下凹陷 |
| 控制点 X 值靠近 | 波浪更陡峭 |
| 控制点 X 值远离 | 波浪更平缓 |
五、Canvas 的事件处理
5.1 onReady 事件
typescript
Canvas(this.ctx)
.onReady(() => {
this.canvasW = 390
this.canvasH = 300
this.drawWaves()
})
onReady 在 Canvas 组件初始化完成后触发,此时可以安全地进行绘图操作。
5.2 Canvas 的其他事件
| 事件 | 说明 |
|---|---|
onReady |
Canvas 就绪,可以开始绘图 |
onAreaChange |
Canvas 尺寸变化时触发 |
onClick |
点击事件 |
onTouch |
触摸事件 |
5.3 Canvas 尺寸自适应
typescript
Canvas(this.ctx)
.width('100%')
.height(300)
.onAreaChange((oldWidth, oldHeight, newWidth, newHeight) => {
this.canvasW = newWidth
this.canvasH = newHeight
this.drawWaves() // 重新绘制
})
六、Canvas 绘图的最佳实践
6.1 性能优化
- 避免频繁 clearRect:只在需要更新时清除画布
- 减少路径复杂度:过多的控制点会影响性能
- 使用离屏 Canvas:复杂图形可以先绘制到离屏 Canvas 再复制
6.2 绘制顺序
Canvas 的绘制顺序遵循后绘制的在上层的原则:
1. clearRect() ← 清除画布
2. 波浪层 1 绘制 ← 最底层,最先绘制
3. 波浪层 2 绘制 ← 中间层
4. 波浪层 3 绘制 ← 最上层,最后绘制
6.3 颜色叠加效果
通过半透明颜色的叠加,可以产生丰富的视觉效果:
typescript
// 三层半透明颜色叠加
rgba(255,210,140,0.35) // 底层:浅橙色,透明度 35%
rgba(245,166,35,0.25) // 中层:品牌橙,透明度 25%
rgba(200,149,106,0.30) // 顶层:琥珀色,透明度 30%
七、常见问题与排查
7.1 Canvas 显示空白
问题:Canvas 区域显示为空白。
原因 :未在 onReady 中调用绘图方法,或绘图坐标超出 Canvas 范围。
解决方案:
typescript
Canvas(this.ctx)
.onReady(() => {
this.draw() // 必须在 onReady 中调用
})
7.2 绘图不清晰
问题:Canvas 绘制的图形有锯齿。
解决方案 :确保 RenderingContextSettings 的 antialias 参数为 true:
typescript
private settings: RenderingContextSettings = new RenderingContextSettings(true)
7.3 Canvas 尺寸为 0
问题:无法获取 Canvas 的正确尺寸。
原因 :在 onReady 之前读取 Canvas 尺寸。
解决方案 :在 onReady 回调中获取尺寸。
总结
本文通过"海风日记"闪屏页的源码,深入讲解了 Canvas 2D 绘图的完整流程:
- Canvas 组件:渲染上下文创建、基本配置
- 多层波浪:三层贝塞尔曲线波浪的绘制方法
- 贝塞尔曲线 :
bezierCurveTo的控制点原理 - 事件处理 :
onReady、onAreaChange等事件 - 最佳实践:性能优化、绘制顺序、颜色叠加
下一篇文章将深入讲解 LinearGradient 线性渐变在品牌色中的应用,敬请期待。
如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!
相关资源: