【OpenHarmony/HarmonyOs 】深色模式下的 Canvas 数学画板:坐标轴、网格、标签与曲线可读性优化

项目类型:OpenHarmony / HarmonyOS ArkTS 数学学习应用
项目名称:数学视界
对应主题:沉浸光感、全新视觉与交互体验
关键词:ArkTS、Canvas、深色模式、数学画板、坐标轴、可读性 🌙

一、为什么 Canvas 深色模式要单独写?

普通 ArkUI 组件可以通过 backgroundColorfontColor 切换深浅色,但 Canvas 不一样。Canvas 里的网格、坐标轴、刻度、标签、曲线都是手动绘制的。

如果只把页面背景变黑,而不处理 Canvas 内部颜色,就会出现:

  • 网格看不清;
  • 坐标轴太暗;
  • 标签和背景混在一起;
  • 曲线颜色对比度不足;
  • 数学图像难以阅读。

数学视界项目中的 CanvasBoard.ets 对 Canvas 深色模式做了手动适配,这篇文章就专门讲这部分。

二、深色模式状态来源

画板页在进入时读取主题状态:

aboutToAppear(): void {
  const themeManager = ThemeManager.getInstance()
  this.isDarkMode = themeManager.getIsDark()
  themeManager.addListener((isDark: boolean) => {
    this.isDarkMode = isDark
  })
  this.loadBoardData()
}

并提供颜色选择函数:

getColor(lightColor: string, darkColor: string): string {
  return this.isDarkMode ? darkColor : lightColor
}

Canvas 绘制时不能自动继承主题色,所以所有绘制颜色都需要主动调用 getColor()

三、画布背景:先清屏,再填充

绘制坐标系时,先清空画布:

ctx.clearRect(0, 0, w, h)

然后根据主题填充背景:

ctx.fillStyle = this.getColor('#FFFFFF', '#000000')
ctx.fillRect(0, 0, w, h)

浅色模式下是白色背景,深色模式下是黑色背景。

这一步必须在绘制网格和坐标轴之前完成,否则旧图像可能残留,或者背景和线条层级错乱。

四、网格颜色:深色下不能太亮,也不能太暗

网格绘制代码:

drawGrid(ctx: CanvasRenderingContext2D, origin: DrawPoint): void {
  const gridStep: number = this.getGridStep()
  ctx.strokeStyle = this.getColor('#E8EDF2', '#3A3A5A')
  ctx.lineWidth = 0.5
}

浅色模式:

#E8EDF2

深色模式:

![img](https://i-blog.csdnimg.cn/devpress/blog/18be13378a5b43fea98ae4941cf017e0.png "#left")

#3A3A5A

深色网格不适合用纯白,否则会抢过坐标轴和曲线;也不能太接近黑色,否则看不见。#3A3A5A 是一种低亮度蓝紫灰,适合作为背景辅助线。

五、坐标轴颜色:比网格更突出

坐标轴使用更高对比度:

ctx.strokeStyle = this.getColor('#333333', '#EEEEEE')
ctx.lineWidth = 1.5

浅色模式下用深灰 #333333,深色模式下用浅灰 #EEEEEE

坐标轴比网格更粗:

ctx.lineWidth = 1.5

这让用户能清楚区分:

  • 网格:辅助;
  • 坐标轴:主结构;
  • 曲线:学习对象。

六、箭头绘制:坐标方向要明确

x 轴箭头:

ctx.beginPath()
ctx.moveTo(w - 8, clampedY - 4)
ctx.lineTo(w, clampedY)
ctx.lineTo(w - 8, clampedY + 4)
ctx.stroke()

y 轴箭头:

ctx.beginPath()
ctx.moveTo(clampedX - 4, 8)
ctx.lineTo(clampedX, 0)
ctx.lineTo(clampedX + 4, 8)
ctx.stroke()

箭头和坐标轴使用同一颜色,能保证深色模式下方向标识仍然清楚。

七、刻度标签:文本颜色也要切换

x 轴刻度标签:

ctx.fillStyle = this.getColor('#555555', '#CCCCCC')
ctx.font = '10px sans-serif'
ctx.textAlign = 'center'
ctx.fillText(this.formatAxisLabel(xi), pt.x, clampedY + 14)

y 轴刻度标签:

ctx.fillStyle = this.getColor('#555555', '#CCCCCC')
ctx.font = '10px sans-serif'
ctx.textAlign = 'right'
ctx.fillText(this.formatAxisLabel(yi), clampedX - 6, pt.y + 3)

深色模式下标签使用 #CCCCCC,比坐标轴稍弱,但比网格清楚。

这符合视觉层级:

曲线/模型 > 坐标轴 > 标签 > 网格 > 背景

八、原点标识:O 也不能漏

原点标识:

if (this.showLabels) {
  ctx.fillStyle = this.getColor('#333333', '#EEEEEE')
  ctx.font = '11px sans-serif'
  ctx.textAlign = 'left'
  ctx.fillText('O', origin.x + 4, origin.y - 5)
}

原点使用和坐标轴接近的颜色,因为它属于坐标结构的一部分。

九、标签格式化:避免长数字破坏画面

坐标标签通过 formatAxisLabel() 处理:

formatAxisLabel(val: number): string {
  if (Number.isInteger(val)) return val.toString()
  if (Math.abs(val) < 0.01 || Math.abs(val) > 9999) {
    return val.toExponential(1)
  }
  return parseFloat(val.toPrecision(4)).toString()
}

这个函数避免出现很长的小数或大数字,保持坐标轴整洁。

数学画板的可读性不仅靠颜色,也靠数字显示克制。

十、深色图标背景:非 Canvas 区域也要统一

画板页还提供了图标背景映射:

getIconBg(bgColor: string): string {
  if (!this.isDarkMode) return bgColor
  const colorMap: Record<string, string> = {
    '#FFFFFF': '#1C1C1E',
    '#F5F5F5': '#2C2C2E',
    '#FFF8F0': '#000000',
    '#EBF5FF': '#1A2744',
    '#FFE8F0': '#3D1A28',
  }
  return colorMap[bgColor] || bgColor
}

Canvas 本身要处理坐标绘制,Canvas 外部的按钮、工具栏、参数面板也要保持主题一致。

十一、为什么这属于“沉浸光感”?

沉浸光感不只是大面积渐变。对数学画板来说,沉浸感来自:

  • 背景不刺眼;
  • 网格不抢戏;
  • 坐标轴清晰;
  • 标签可读;
  • 曲线颜色突出;
  • 工具栏和画布主题一致。

如果深色模式只是简单反色,数学图像会变得很难看。手动绘制层级才是关键。

十二、总结

这篇文章专门讲 Canvas 数学画板的深色可读性,和之前“参数化曲线绘制”文章区分开。

核心实现包括:

  • 🌙 用 ThemeManager 获取深色状态;
  • 🎨 用 getColor() 切换 Canvas 内部绘制颜色;
  • 🖼 先清屏再填充深浅色背景;
  • 🧱 网格使用低对比辅助色;
  • 📏 坐标轴使用高对比主结构色;
  • 🔢 刻度标签使用中等对比文本色;
  • 🧭 原点和箭头保持坐标方向清晰;
  • ✂️ 用 formatAxisLabel() 控制数字长度。

Canvas 深色模式不是换背景这么简单。尤其是数学画板,所有线条和文字都要重新考虑层级,这样才能真正做到“看得清、用得久、不累眼”。🚀

img

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐