news 2026/9/12 0:11:11

AIRI 舞台 UI 焕新实录:纯 CSS 波浪动画与自定义主题色的工程实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
AIRI 舞台 UI 焕新实录:纯 CSS 波浪动画与自定义主题色的工程实现

AIRI 舞台 UI 焕新实录:纯 CSS 波浪动画与自定义主题色的工程实现

【免费下载链接】airi💖🧸 Self hosted, you-owned Grok Companion, a container of souls of waifu, cyber livings to bring them into our worlds, wishing to achieve Neuro-sama's altitude. Capable of realtime voice chat, Minecraft, Factorio playing. Web / macOS / Windows supported.项目地址: https://gitcode.com/GitHub_Trending/ai/airi

本篇技术指南围绕 AIRI(Project AIRI,自托管的 AI 伴生桌面项目)2025 年 3 月 10 日发布的一篇开发日志展开,系统复盘了这次"舞台 UI 与设置 UI 风格重构"背后落地到仓库中的三项核心技术:新一代设置界面的视觉升级、用纯 CSS 实现的舞台波浪动画,以及让舞台与 Logo 可以整体换肤的动态主题色体系(含"RGB ON"全光谱动态模式)。读完本文,你将理解这些功能在 packages/stage-layouts 与 packages/stage-ui 中的真实实现细节、可复用的参数与调用链,并了解仓库为简化构建而将独立包剥离到@proj-airi组织背后的工程思路。

背景:一次 UI 风格的"灵光一现"

根据开发日志的记述,3 月 7 日(周五)作者一直在尝试为 AIRI 舞台 UI 与设置 UI 构思新风格,直到当天开发直播结束时灵感才浮现。从 3 月 7 日开始,团队着手实现新的设置 UI,并在接下来几天内取得了快速进展。参与这次迭代的贡献者包括 LemonNekoGH、sumimakito、kwaa、luoling8192 与 junkwarrior87 等。

在仓库中,这些 UI 的最终落点主要分布在:

  • packages/stage-layouts/src/layouts/settings.vue:设置页的整体布局框架;
  • packages/stage-ui/src/components/scenarios/settings:设置项组件(模型设置、预览舞台等);
  • packages/stage-pages/src/pages/settings:设置页面的业务编排模块;
  • packages/i18n/src/locales/zh-Hans/settings.yaml:与设置相关的多语言文案(该语言目录下同时存在 en、ja、ko、es、fr、ru、vi 等对应文件,说明设置界面本身是完整国际化的)。

新设置 UI:从基础版本到"点状节奏感"

开发日志记录了设置 UI 的两次关键视觉迭代:

  1. 基础版本(new-ui-v1):由作者首先完成设置设计的基础版本,呈现了全新的菜单结构;
  2. 点状按钮效果(new-ui-v2):随后 sumimakito 上线帮忙为按钮实现了"点状效果"(dotted effect),让菜单在视觉上获得了更强的节奏感

现在我们能从菜单中感受到更多的节奏感,对吧?!

这种"点状"视觉语言与舞台整体的氛围相互呼应。值得注意的是,这些 UI 组件并非一次性完成,而是经过多轮 PR 持续打磨——开发日志特别提到,最终成果依赖一组连续的提交记录(PR #53、#60、#61、#63 等,涉及颜色自定义;PR #54、#55、#65 等,涉及波浪动画)。

仓库瘦身:把独立包迁往 @proj-airi 组织

在重构 UI 的过程中,团队发现packages/目录下有一部分包实际上是独立的包,并不在 Project AIRI 的核心工作流中。把它们继续留在主仓库会无谓地增大仓库的安装体积、拖慢构建流程,因此团队决定将这些包迁移到新注册的 GitHub 组织@proj-airi下。首批迁移的公开仓库包括:

  • webai-examples:用于制作 WebGPU 及相关内容的演示;
  • lobe-icons:Lobe Icons 的移植版本,面向 Iconify JSON 与 UnoCSS 使用。

这两个仓库保持开源并沿用 MIT 许可证。

仓库内也能找到这次组织拆分留下的痕迹:例如 packages/stage-layouts/src/composables/theme-color.ts 中直接以@proj-airi/stage-ui/libs@proj-airi/stage-ui/stores/settings的形式引用stage-ui包——即stage-ui这类被广泛复用的包已经以@proj-airi作用域发布,与主仓库解耦。可以推断,这样的拆分让stage-ui等包可以被 AIRI 之外的 Web 应用(如stage-webui-server-auth等)独立消费,同时主仓库自身只保留与其业务强相关的代码,从而精简依赖图与构建链。

纯 CSS 波浪动画:从"不可能"到"太疯狂了"

3 月 8 日,junkwarrior87 上线并帮助团队用纯 CSS制作了舞台上的波浪动画。开发日志感叹"这简直太疯狂了,我从来没想到这居然能实现"。这一实现的最终形态可以在 packages/stage-layouts/src/components/Backgrounds/default/part-animated-wave.vue 中找到。

组件参数一览

该组件是一个高度可配置的波浪背景单元,对外暴露了以下 Props(均带默认值):

参数类型默认值说明
heightnumber40波浪主体高度(像素)
amplitudenumber14波幅,即波浪起伏的最大幅度(像素)
waveLengthnumber250单个完整波周期的像素长度
fillColorstringoklch(95% 0.10 var(--chromatic-hue))波浪的填充颜色
direction'up' \| 'down''down'波浪拱起的方向
movementDirection'left' \| 'right''left'波浪滚动的方向
animationSpeednumber50动画速度(像素/秒)

实现原理:正弦波路径 + mask-image 技巧

组件通过generateSineWavePath()以 1px 为步长逐点生成 SVG 正弦波路径:先用Math.ceil(width / waveLength)计算需要铺满宽度的完整波数,再以Math.PI * 2 / waveLength作为相位因子,逐点计算amplitude * Math.sin(factor * x)的纵向偏移,最终闭合路径形成可填充的区域。根据direction的不同,基准 Y 坐标与闭合边会相应调整,从而决定波浪是向上还是向下拱起。

真正的关键在于填充色的控制:SVG 作为background-image时无法直接修改内部 fill 颜色,因此组件采用mask-image方案——先把正弦波路径编码为 data URL SVG 作为遮罩,再让一个普通 div 以fillColor作为background被遮罩裁剪出波浪形状。这样,填充色就完全交给 CSS 变量与主题体系驱动,实现了"同一个波浪、随主题变色"的效果。

动画本身则是一段纯 CSS 关键帧:波浪层宽度设为200vw,配合mask-repeat: repeat-x横向平铺;@keyframes wave-animation让元素从translate(0)平移到translate(var(--wave-translate)),其中--wave-translate恰好是-waveLength(平移一个整周期后无缝衔接),--animation-durationwaveLength / animationSpeed计算得出;当movementDirection'right'时,通过animation-direction: reverse反转滚动方向。

波浪与主题色的联动

在 packages/stage-layouts/src/components/Backgrounds/default/index.vue 中,波浪被叠加在PatternCross(交叉纹理)之上,并且明暗模式的填充色都直接引用主题色变量:

  • 暗色模式:oklch(35% calc(var(--chromatic-chroma) * 0.6) var(--chromatic-hue))
  • 亮色模式:color-mix(in srgb, oklch(95% calc(var(--chromatic-chroma-50) * 0.5) var(--chromatic-hue)) 80%, oklch(100% 0 360))

也就是说,波浪的颜色由全局 CSS 变量--chromatic-hue(色相)与--chromatic-chroma(饱和度)统一决定——这正是下一节"自定义主题色"能够从设置面板一处改动、全舞台即时生效的底层机制。

舞台颜色自定义:几小时完成的惊喜

3 月 8 日临近结束时,LemonNekoGH 和 junkwarrior87 完成了整个舞台的颜色自定义功能,开发日志感叹"我从来没想过这能在短短几个小时内完成"。更让人惊喜的是,连 Logo 也能跟随自定义颜色变化。

主题色状态:OKLCH 色相即主题

主题色状态集中在 packages/stage-ui/src/stores/settings/theme.ts 中,实现思路非常简洁——整套主题只由一个"色相值"驱动

  • DEFAULT_THEME_COLORS_HUE = 220.44:默认主题色相(偏蓝);
  • themeColorsHue:持久化在localStoragesettings/theme/colors/hue键下,保存当前色相数值;
  • themeColorsHueDynamic:持久化在settings/theme/colors/hue-dynamic键下,标记是否开启动态(RGB ON)模式;
  • setThemeColorsHue(hue):写入色相并自动关闭动态模式
  • applyPrimaryColorFrom(color):借助culoriconverter('oklch')将任意颜色字符串转换为 OKLCH,再提取其h值作为主题色相,实现"从任意颜色一键取色";
  • isColorSelectedForPrimary(hexColor):把候选色也转为 OKLCH 后与当前色相比较,容差为hueDifference < 0.01 || hueDifference > 359.99(即色相相同或恰好首尾相接);若处于动态模式则一律返回false,因为此时没有任何"手动选中"的预设色。

选择 OKLCH 而非 hex/RGB 是有意为之:OKLCH 将色相与明度、饱和度解耦,使"只改色相、不动明暗层次"成为可能,从而保证无论用户选什么颜色,界面的明暗对比与材质层次都保持稳定。

CSS 变量如何驱动全舞台

色相值最终以 CSS 变量--chromatic-hue(以及配套的--chromatic-chroma--chromatic-chroma-50等)的形式注入组件树,例如 packages/stage-ui/src/constants/inject.ts 定义了chromaticHue注入键,packages/stage-ui/src/libs/color-from-element.ts 中也会读取--chromatic-hue(默认回退到'200')。波浪填充色、交叉纹理(pattern-cross.vue 中的--cross-color)都引用同一组变量,因此一处改色、全局生效。

Logo 跟随变色则更有意思:packages/stage-layouts/src/components/Layouts/HeaderLink.vue 对 Logo 应用了filter: hue-rotate(calc(var(--chromatic-hue, 0) * 1deg))——直接利用 CSS 的hue-rotate滤镜,把 Logo 的每个像素沿色相环旋转--chromatic-hue度,从而让 Logo 与当前主题色保持一致,无需准备多套 Logo 素材。

浏览器主题色(meta theme-color)的实时同步

为了让浏览器标签栏/地址栏的theme-color也与舞台保持一致,packages/stage-layouts/src/composables/theme-color.ts 提供了一套完整的同步机制:

  • useThemeColor(colorFrom):从颜色提供函数取值,经colorjs.io转为 sRGB hex 后写入meta[name="theme-color"]
  • 对于波浪背景,通过themeColorFromPropertyOf('.widgets.top-widgets .colored-area', 'background-color')直接读取舞台上波浪实际渲染出的background-color,确保浏览器主题色与所见即所得;
  • 对于图片背景,使用colorFromElement(基于 html2canvas 克隆采样,采样区域为顶部 140px、步长 10px、缩放 0.5)计算平均色;
  • 当背景为波浪且开启动态色相时,用useIntervalFn250ms 间隔刷新theme-color——注释明确说明这是为了"在不做逐帧渲染的前提下,让动画波浪背景的 theme-color 保持新鲜"。

(完整的动态效果可查看仓库内的演示视频:customizable-theme-colors.mp4。)

RGB ON:让色相在整个光谱中闪耀

颜色自定义的最后一块拼图来自 PR #64 保留的功能:"我想要动态的!"——开发日志把它形象地称为RGB ON模式(色相在整个 RGB 光谱中连续变化)。这一能力此前由 LemonNekoGH 演示,最终由 junkwarrior87 保留进主题体系。

从源码看,RGB ON 的开关正是themeColorsHueDynamic:当它为true时,isColorSelectedForPrimary恒返回false(没有固定选中色),而色相值会随时间持续变化;useBackgroundThemeColor中的 250ms 定时同步器也仅在"波浪背景 +themeColorsHueDynamic"组合下启用,驱动theme-color跟随色相环滚动。可以推断,动态模式下--chromatic-hue由一个持续递增的动画/定时器驱动,波浪、纹理、Logo 乃至浏览器主题色因此得以像呼吸灯一样在完整色相环上循环——这正是"RGB ON"名字的由来。

结语:三天的协作成果

回顾这次迭代:3 月 7 日确定新设置 UI 风格,3 月 8 日完成纯 CSS 波浪动画与整个舞台的颜色自定义(含动态 RGB 模式),期间还完成了独立包向@proj-airi组织的迁移以精简主仓库。这些改动背后,是 stage-layouts 中"组件 + composable + CSS 变量"的分层设计:part-animated-wave.vue提供可参数化的波浪基元,theme-color.ts提供主题色同步管线,settings/theme.ts提供以 OKLCH 色相为核心的轻量状态管理。无论读者是想复刻同样的波浪动画、搭建可换肤的舞台系统,还是设计"一个变量控制全局主题"的架构,这份实现都提供了可直接参考的范本。开发日志最后也表示,AIRI 对所有人开放且友好,即使是编程新手也欢迎参与贡献。

【免费下载链接】airi💖🧸 Self hosted, you-owned Grok Companion, a container of souls of waifu, cyber livings to bring them into our worlds, wishing to achieve Neuro-sama's altitude. Capable of realtime voice chat, Minecraft, Factorio playing. Web / macOS / Windows supported.项目地址: https://gitcode.com/GitHub_Trending/ai/airi

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/12 0:02:18

【单片机毕业设计】基于 STM32 的 ESP8266 无线通信智能灯光终端设计 基于 STM32 的 LCD1602 状态显示语音调光台灯设计(023607)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于嵌入式单片机&#xff0c;Java、小程序技术领域和毕业项目实战 ✌️…

作者头像 李华
网站建设 2026/9/11 23:58:50

RISC-V五级流水线CPU设计源码解析:冒险处理与Verilator仿真

简介&#xff1a;基于RISC-V的五级流水线CPU设计项目是一套面向计算机体系结构学习与毕业设计场景的完整资源包。项目整合了处理器设计源码、工程构建脚本、自测试用例与项目说明文档&#xff0c;既有助于在校学生理解取指、译码、执行、访存、写回各流水级的工作原理&#xff…

作者头像 李华
网站建设 2026/9/11 23:58:01

itemCF与SVD混合推荐算法:电影推荐系统的原理与实现

简介&#xff1a;基于itemCF与SVD的电影推荐算法本科毕业论文设计资源包&#xff0c;面向机器学习、人工智能方向的本科生及推荐系统研究者。该设计围绕协同过滤与矩阵分解两类主流技术&#xff0c;系统探讨item-based collaborative filtering与奇异值分解在电影推荐场景中的建…

作者头像 李华
网站建设 2026/9/11 23:54:44

Prophet时间序列预测实战:从安装到生产部署

简介&#xff1a;本资源是一份面向Python初学者与数据分析从业者的Prophet时间序列预测入门实践脚本&#xff0c;聚焦业务场景下的快速建模与结果解读。压缩包为1KB的ZIP文件&#xff0c;内含1个核心Python脚本&#xff08;prophet.py&#xff09;&#xff0c;完整实现了数据加…

作者头像 李华