Canvas 2D 绘制多层波浪动画实现品牌闪屏

前言

品牌闪屏 是用户打开应用后看到的第一画面,其视觉品质直接影响用户对应用的第一印象。"海风日记"的闪屏页通过 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 性能优化

  1. 避免频繁 clearRect:只在需要更新时清除画布
  2. 减少路径复杂度:过多的控制点会影响性能
  3. 使用离屏 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 绘制的图形有锯齿。

解决方案 :确保 RenderingContextSettingsantialias 参数为 true

typescript 复制代码
private settings: RenderingContextSettings = new RenderingContextSettings(true)

7.3 Canvas 尺寸为 0

问题:无法获取 Canvas 的正确尺寸。

原因 :在 onReady 之前读取 Canvas 尺寸。

解决方案 :在 onReady 回调中获取尺寸。


总结

本文通过"海风日记"闪屏页的源码,深入讲解了 Canvas 2D 绘图的完整流程:

  1. Canvas 组件:渲染上下文创建、基本配置
  2. 多层波浪:三层贝塞尔曲线波浪的绘制方法
  3. 贝塞尔曲线bezierCurveTo 的控制点原理
  4. 事件处理onReadyonAreaChange 等事件
  5. 最佳实践:性能优化、绘制顺序、颜色叠加

下一篇文章将深入讲解 LinearGradient 线性渐变在品牌色中的应用,敬请期待。

如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!


相关资源:

相关推荐
Reart9 小时前
Leetcode 674.最长连续递增序列 (719)
后端·算法
芒鸽9 小时前
HarmonyOS ArkUI Search 搜索框:联想词、历史记录与热门标签
后端
Reart10 小时前
Leetcode 714.买卖股票的最佳时机含手续费(719)
后端·算法
芒鸽10 小时前
HarmonyOS ArkUI RichEditor 富文本编辑器:内联样式、Span 管理与格式工具栏
后端
花开彼岸天~10 小时前
Router 路由管理:pushUrl、replaceUrl、back 与参数传递
后端
wear工程师11 小时前
CompletableFuture 的 allOf 到底怎么收结果?面试别只说并行
java·后端
b1305381004911 小时前
DiaryDetail2Page 卡片式详情:琥珀渐变 + 引号装饰
后端
w1395485642212 小时前
AI 周报卡片:智能生成内容的 UI 呈现
后端
全堆鸿蒙12 小时前
天气药丸标签:Row + Text 组合实现品牌药丸
后端
HONG````12 小时前
HarmonyOS ArkUI Slider 与 Rating 实战:音量控制、亮度调节与星级评分
后端