☰
airi 项目实践:用 VueUse `useCssVar` 在 Vue 3 中响应式操控 CSS 变量
2026/10/14 2:27:34 网站建设 项目流程
  • 人工智能
  • AI Agent
  • MCP 服务
  • 工具调用
  • GUI 自动化
  • 语音
  • 数字人
  • 交互助手

【免费下载链接】airi

💖🧸 自托管、归你拥有的 Grok 风格 AI 伴侣与 waifu / 赛博生命灵魂容器,目标是接近 Neuro-sama 的高度;支持实时语音聊天、Minecraft 和 Factorio 游玩,支持 Web / macOS / Windows。

项目地址:https://gitcode.com/moeru-ai/airi
点击查看免费下载

本篇技术指南以 VueUse 官方 composableuseCssVar为切入点,讲解如何在 Vue 3 / Nuxt 项目中以响应式的方式读取和写入 CSS 自定义属性(Custom Properties,即--var形式的 CSS 变量),并结合 airi 仓库中基于--chromatic-hue主题色变量的真实实现,展示useCssVar与传统document.documentElement.style.setProperty()手写方案的差异与适用场景。读完本文,你将掌握useCssVar的完整签名、三种典型调用姿势、observe与initialValue选项的用途,以及如何用同样的思路改造主题切换、动态配色等前端需求。

useCssVar是 VueUse 中归入Browser类别的一个轻量工具函数(见仓库内技能索引 .agents/skills/vueuse-functions/SKILL.md,Invocation 规则为AUTO,即满足场景时可直接使用)。airi 项目根目录的 pnpm-workspace.yaml 已声明@vueuse/core: ^14.4.0与@vueuse/shared: ^14.4.0,因此在 airi 各 Vue 前端应用(apps/stage-web、apps/stage-pocket、apps/stage-tamagotchi等)中可直接 import 使用。

函数定位:一行代码读写 CSS 变量

CSS 变量(如--color、--chromatic-hue)定义在元素或根节点上,通过var(--name)在样式表中消费。原生操作需要手写:

const color = getComputedStyle(el).getPropertyValue('--color') el.style.setProperty('--color', newColor)

useCssVar将上述读写封装为一个ShallowRef:读取变量当前值、写入变量新值,且返回值本身具备 Vue 响应性,写入 ref 即同步写入目标元素的 CSS 变量。

注意:useCssVar操作的是 CSS 变量本身,与useStyleTag(动态注入<style>标签)属于不同的关注点;若需要响应式地向<head>注入整段样式,应改用 useStyleTag。

基本用法(官方 Usage 完整继承)

VueUse 官方文档给出的useCssVar用法如下,以下代码可直接在 Vue 3<script setup>中运行:

import { useCssVar } from '@vueuse/core' import { useTemplateRef } from 'vue' const el = useTemplateRef('el') const color1 = useCssVar('--color', el) const elv = useTemplateRef('elv') const key = ref('--color') const colorVal = useCssVar(key, elv) const someEl = useTemplateRef('someEl') const color2 = useCssVar('--color', someEl, { initialValue: '#eee' })

模板侧对应:

<template> <div ref="el" style="--color: #f00">Example</div> <div ref="elv">Dynamic Key</div> <div ref="someEl">With Initial Value</div> </template>

三个示例分别演示了三种核心形态:

形态参数写法适用场景
字符串 propuseCssVar('--color', el)变量名固定,绑定一个元素
响应式 propuseCssVar(key, elv),其中key = ref('--color')变量名需要动态切换(运行时更换要读写的变量)
初始值兜底useCssVar('--color', someEl, { initialValue: '#eee' })元素上尚未定义该变量,需要提供一个回退值

注意el、elv、someEl由 Vue 3.5+ 的useTemplateRef生成,它们是MaybeElementRef形态的模板引用;useCssVar内部会自动完成unref与元素解析。

类型声明与完整参数表

useCssVar的完整类型声明如下(继承自官方 Type Declarations):

export interface UseCssVarOptions extends ConfigurableWindow { initialValue?: string /** * Use MutationObserver to monitor variable changes * @default false */ observe?: boolean } /** * Manipulate CSS variables. * * @param prop 需要读取/写入的 CSS 变量名(如 '--color'),可为 ref 或 getter * @param target 目标元素引用(MaybeElementRef) * @param options 配置项 */ export declare function useCssVar( prop: MaybeRefOrGetter<string | null | undefined>, target?: MaybeElementRef, options?: UseCssVarOptions, ): ShallowRef<string | undefined, string | undefined>

各参数与选项的语义说明:

参数 / 选项类型说明
propMaybeRefOrGetter<string \| null \| undefined>CSS 变量名。传字符串、ref('--name')或() => '--name'均可;传null/undefined时返回值退化为undefined,不会抛出异常
targetMaybeElementRef(可选)目标 DOM 元素。不传时回退到ConfigurableWindow提供的默认 window/document 环境
options.initialValuestring(可选)目标元素上尚未定义该变量时使用的初始值,作为返回 ref 的兜底
options.observeboolean,默认false是否启用MutationObserver监听变量变化(见下文“observe 模式”小节)
返回值ShallowRef<string \| undefined>当前 CSS 变量值;对返回值赋值会写回目标元素

返回类型采用ShallowRef而非Ref:变量值是一个字符串原始类型,浅层响应即可,避免了深层代理带来的无谓开销。

observe选项:用 MutationObserver 感知外部修改

默认情况下(observe: false),useCssVar返回的 ref 只在你的代码写入时同步到元素;如果其他脚本、CSS 动画或用户操作改变了该 CSS 变量,ref 不会自动更新。

将observe: true打开后,函数会在目标元素上挂载MutationObserver,通过观察元素style属性的变更来把外部变化同步回响应式 ref。适用典型场景:

  • 变量的实际值由第三方库或 Web Animations API 持续修改;
  • 多个组件各自独立写入同一个 CSS 变量,需要某一方始终感知最新值;
  • 通过浏览器 DevTools 手动调整变量后,希望页面状态跟随。

代价是额外开启一个MutationObserver,存在持续监听开销,因此官方将其默认关闭;仅在确实需要“外部写 → ref 自动读”双向同步时开启。

// 感知外部对 --accent 的修改 const accent = useCssVar('--accent', rootEl, { observe: true }) // 配合 watch 响应变量变化 watch(accent, (value) => { console.log('--accent changed to', value) })

结合 airi 仓库源码:主题色变量的实战对照

airi 的 Web 端与桌面端都以 CSS 变量承载主题配色,其中最具代表性的是--chromatic-hue(色相角,数值范围 0–360)。先看仓库中手写原生方案的实现(apps/stage-web/src/App.vue):

watch(settings.themeColorsHue, () => { document.documentElement.style.setProperty('--chromatic-hue', settings.themeColorsHue.value.toString()) }, { immediate: true }) watch(settings.themeColorsHueDynamic, () => { document.documentElement.classList.toggle('dynamic-hue', settings.themeColorsHueDynamic.value) }, { immediate: true })

桌面端 apps/stage-tamagotchi/src/renderer/App.vue 采用完全一致的写法:监听themeColorsHue后把数值字符串写回根节点的--chromatic-hue。为了让色相值可以参与动画,两处都在样式中用@property声明了该变量为可插值类型(apps/stage-web/src/App.vue、apps/stage-tamagotchi/src/renderer/App.vue):

@property --chromatic-hue { syntax: '<number>'; initial-value: 0; inherits: true; }

这段声明使--chromatic-hue成为带类型的可注册自定义属性,浏览器据此能对色相值做平滑插值动画,这是 CSS 变量驱动动态配色(airi 中由themeColorsHueDynamic控制、通过给根节点切换dynamic-hueclass 启用)的基础。

对照useCssVar,上述手写 watch 可以等价改写为更简洁的“ref 即写入”形态:

import { useCssVar } from '@vueuse/core' const chromaticHue = useCssVar('--chromatic-hue', document.documentElement) // 直接赋值即同步到 <html> 根节点 watch(settings.themeColorsHue, (hue) => { chromaticHue.value = hue.toString() }, { immediate: true })
  • 两种写法最终都落到element.style.setProperty层面,效果等价;
  • useCssVar的价值在于:省去手动维护 watch 与 setProperty 的样板代码,同时把“读取当前值”也纳入同一个 ref——当observe: true时还能自动感知其他来源对变量的修改;
  • 从源码结构看,airi 选择手写 watch +setProperty,是因为主题色更新集中在根节点且只由 store 单向驱动,属于“单点、单向”场景;而useCssVar更适用于“多写入方、需要双向同步或需要随处读取”的场景。

动态 key 与 getter 形态的进阶说明

prop参数支持MaybeRefOrGetter,意味着变量名本身可以响应式变化:

const key = ref('--color') const val = useCssVar(key, el) // 切换变量名,无需重建 composable key.value = '--background'

从函数签名可以推断,prop为null/undefined时函数会安全退化为空值状态(返回undefined),因此可以在元素尚未挂载或变量名尚未确定的阶段提前调用。若结合getter形态(() => computedName.value),还能把变量名的选择逻辑集中到一处,便于在v-for或动态组件中复用同一份绑定逻辑。

SSR 与运行环境注意事项

  • useCssVar依赖document与 DOM 元素,属于浏览器专用 composable;在 SSR(Nuxt)环境下请勿在setup顶层直接使用,应配合onMounted或仅在客户端执行,避免服务端渲染阶段访问不存在的document。
  • target未显式传入时,行为依赖ConfigurableWindow提供的默认 window 环境(useCssVar的选项接口继承了ConfigurableWindow),因此理论上可注入自定义 window 以实现测试环境模拟;实际项目中建议总是显式传入document.documentElement或具体元素引用,减少歧义。
  • 返回值为ShallowRef<string | undefined>:读取到变量值为空字符串或变量未定义时,可能得到undefined,消费侧(如模板插值、color-mix拼接)应做好空值兜底;这正是initialValue选项存在的意义。
  • 写入的值是字符串:数值型变量(如 airi 的--chromatic-hue)需要先toString(),这一约束与原生setProperty完全一致。

何时使用useCssVar

结合本文的仓库对照,给出落地建议:

  • 优先使用:需要在多处读取同一 CSS 变量、需要把变量值与组件状态双向绑定、变量名动态变化、需要MutationObserver感知外部修改(observe: true)。
  • 保持手写:更新点唯一且集中在根节点(如 airi 的主题色单向写入)、追求极致零依赖、或变量同时被@property动画驱动且不需要读取回读值时,手写watch + setProperty(如 apps/stage-tamagotchi/src/renderer/App.vue)依然是最直观的选择。
  • 无论哪种方案,若希望变量参与 CSS 过渡/动画,都别忘了用@property注册变量类型——这是 airi 主题色实现中容易被忽略却至关重要的配套写法。

useCssVar是“读写 CSS 变量”这一高频需求的最小封装,配合 .agents/skills/vueuse-functions/SKILL.md 中AUTO的调用规则,可在不引入任何额外依赖的前提下(@vueuse/core已在 pnpm-workspace.yaml 全局声明)显著降低主题切换、动态配色、组件级样式令牌管理类代码的维护成本。

  • 人工智能
  • AI Agent
  • MCP 服务
  • 工具调用
  • GUI 自动化
  • 语音
  • 数字人
  • 交互助手

【免费下载链接】airi

💖🧸 自托管、归你拥有的 Grok 风格 AI 伴侣与 waifu / 赛博生命灵魂容器,目标是接近 Neuro-sama 的高度;支持实时语音聊天、Minecraft 和 Factorio 游玩,支持 Web / macOS / Windows。

项目地址:https://gitcode.com/moeru-ai/airi
点击查看免费下载

相关推荐

上一篇:Awesome-Computer-Vision-Paper-List项目架构解析:理解代码实现原理
下一篇:Pentaho Kettle 11.x 技术架构深度解析与性能评测

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询