css-variables-and-var-macros[20260804112741]

0 阅读1分钟

CSS 变量与 Var 宏

项目代码:github.com/euv-dev/euv

euv 提供了在运行时动态管理 CSS 的强大原语。css-vars!var! 宏将响应式 Rust 状态与 CSS 样式连接起来,实现了主题切换、响应式设计和动态视觉效果——所有这些都无需离开 Rust 代码。

为什么需要动态 CSS 变量?

传统 CSS 是静态的。一旦样式表加载完成,值就固定了。然而,现代 Web 应用需要对样式进行运行时控制:暗色模式切换、用户自定义主题、由应用状态驱动的动画过渡,以及超越媒体查询的响应式布局。

CSS 自定义属性(--my-var)部分解决了这个问题,允许 JavaScript 在运行时设置和读取 CSS 值。euv 的 css-vars!var! 宏在此基础上进行了全面的原生响应式集成。

css-vars!

css-vars! 宏定义可以应用于元素的 CSS 自定义属性。它生成注入到文档中的 CSS 变量声明。

基本用法

css-vars! {
    --primary-color: "#3498db";
    --spacing-unit: "8px";
    --border-radius: "4px";
}

这会生成 CSS 自定义属性,可以在任何 CSS 规则或内联样式中通过 var(--primary-color)var(--spacing-unit) 等方式引用。

工作原理

css-vars! 被调用时,euv 会:

  1. 生成 CSS 自定义属性声明。
  2. 将它们注入到文档的样式中。
  3. 使其可用于任何 CSS 规则。

这些变量成为文档 CSS 环境的组成部分,可以被任何元素使用。

作用域

你可以通过将 css-vars! 与 euv 的组件系统结合使用,将 CSS 变量限定在特定组件或元素范围内。在组件上下文中定义的变量仅适用于该元素及其后代。

var! 宏:响应式变量

css-vars! 定义静态的 CSS 自定义属性,而 var! 创建响应式 CSS 变量 —— 当源信号变化时自动更新的变量。

基本用法

let primary_color: Signal<String> = use_signal(|| "#3498db".to_string());
var!(primary_color);

var! 宏接收一个 Signal<String> 并创建一个跟踪该信号值的 CSS 变量。当 primary_color 通过 set() 改变时,CSS 变量会在 DOM 中自动更新。

内部工作原理

var! 被调用时:

  1. 读取信号的当前值。
  2. 在目标元素上设置对应的 CSS 自定义属性。
  3. 订阅信号以监听后续变化。
  4. 当信号值变化时,CSS 属性自动更新。

这在 Rust 状态和 CSS 之间创建了一个无缝的响应式绑定。

组合使用 css-vars!var!

真正的威力在于同时使用两个宏。用 css-vars! 定义 CSS 变量结构,用 var! 驱动响应式的值:

css-vars! {
    --primary-color: "#3498db";
    --spacing-unit: "8px";
}

let theme_color: Signal<String> = use_signal(|| "#3498db".to_string());
var!(theme_color);

现在 --primary-color 被定义为 CSS 自定义属性,而 theme_color 信号以响应式方式驱动其值。

动态主题切换

euv 中 CSS 变量最引人注目的用例之一是动态主题切换。以下是如何构建主题切换器:

// 将主题颜色定义为信号
let bg_color: Signal<String> = use_signal(|| "#ffffff".to_string());
let text_color: Signal<String> = use_signal(|| "#333333".to_string());
let accent_color: Signal<String> = use_signal(|| "#3498db".to_string());

// 创建响应式 CSS 变量
var!(bg_color);
var!(text_color);
var!(accent_color);

// 切换到暗色模式
fn switch_to_dark_mode() {
    batch(|| {
        bg_color.set("#1a1a2e".to_string());
        text_color.set("#eaeaea".to_string());
        accent_color.set("#e94560".to_string());
    });
}

// 切换到亮色模式
fn switch_to_light_mode() {
    batch(|| {
        bg_color.set("#ffffff".to_string());
        text_color.set("#333333".to_string());
        accent_color.set("#3498db".to_string());
    });
}

注意使用 batch 来协调所有颜色更新。这确保所有 CSS 变量一起更新,防止部分主题应用导致的视觉闪烁。

响应式间距与布局

由信号驱动的 CSS 变量也可以为响应式布局提供动力,根据应用状态自适应:

let spacing_unit: Signal<String> = use_signal(|| "8px".to_string());
var!(spacing_unit);

// 在大屏幕上增加间距
watch!(window_width, |w: i32| {
    if w > 1200 {
        spacing_unit.set("16px".to_string());
    } else if w > 768 {
        spacing_unit.set("12px".to_string());
    } else {
        spacing_unit.set("8px".to_string());
    }
});

使用 CSS 变量控制动画

euv 内置了 CSS 关键帧动画:euv-spineuv-fade-ineuv-scale-ineuv-pulseeuv-slide-upeuv-slide-lefteuv-fade-in-upeuv-progresseuv-shimmer。你可以使用 var! 动态控制这些动画:

let spin_duration: Signal<String> = use_signal(|| "1s".to_string());
var!(spin_duration);

// 悬停时加速旋转动画
watch!(is_hovering, |hovering: bool| {
    if hovering {
        spin_duration.set("0.3s".to_string());
    } else {
        spin_duration.set("1s".to_string());
    }
});

class! 宏配合使用:

class! {
    pub c_anim_spin { animation: "euv-spin 1s linear infinite"; }
}

自定义关键帧动画

对于内置集合之外的动画,euv 提供了 Css::inject_css 来注入自定义的 @keyframes 规则:

Css::inject_css("@keyframes my-bounce {
    0%, 100% { transform: translateY(0); }
    50% { transform: translateY(-20px); }
}");

然后可以在 class! 定义中引用这些自定义动画:

class! {
    pub c_anim_bounce { animation: "my-bounce 0.5s ease-in-out infinite"; }
}

class! 宏集成

class! 宏生成 CSS 类定义。与 var! 结合使用,可以创建包含响应式 CSS 变量的类:

class! {
    pub c_card {
        display: "flex";
        padding: "10px";
        background-color: var(--card-bg);
        color: var(--card-text);
    }
}

// 响应式驱动 CSS 变量
let card_bg: Signal<String> = use_signal(|| "#ffffff".to_string());
let card_text: Signal<String> = use_signal(|| "#333333".to_string());
var!(card_bg);
var!(card_text);

实用示例

示例 1:暗色模式切换

let dark_mode: Signal<bool> = use_signal(|| false);

let bg: Signal<String> = use_signal(|| "#ffffff".to_string());
let fg: Signal<String> = use_signal(|| "#333333".to_string());
var!(bg);
var!(fg);

watch!(dark_mode, |is_dark: bool| {
    batch(|| {
        if is_dark {
            bg.set("#121212".to_string());
            fg.set("#e0e0e0".to_string());
        } else {
            bg.set("#ffffff".to_string());
            fg.set("#333333".to_string());
        }
    });
});

示例 2:用户可配置的主题色

let accent: Signal<String> = use_signal(|| "#3498db".to_string());
var!(accent);

// 当用户从颜色选择器中选取新颜色时:
fn on_color_pick(new_color: String) {
    accent.set(new_color);
}

示例 3:带响应式样式的进度条

let progress: Signal<f64> = use_signal(|| 0.0);
let progress_color: Signal<String> = use_signal(|| "#3498db".to_string());

var!(progress);
var!(progress_color);

watch!(progress, |p: f64| {
    if p < 0.3 {
        progress_color.set("#e74c3c".to_string());
    } else if p < 0.7 {
        progress_color.set("#f39c12".to_string());
    } else {
        progress_color.set("#2ecc71".to_string());
    }
});

总结

  • css-vars! 定义可在整个样式表中使用的 CSS 自定义属性。
  • var!Signal<String> 值与 CSS 自定义属性之间创建响应式绑定,当信号变化时自动更新 DOM。
  • 组合使用两个宏来构建动态主题、响应式布局和动画驱动的用户界面。
  • 使用 batch 协调多个 CSS 变量更新,防止视觉闪烁。
  • 自定义关键帧动画可通过 Css::inject_css("@keyframes my-bounce { ... }") 注入。
  • 内置动画包括 euv-spineuv-fade-ineuv-scale-ineuv-pulseeuv-slide-upeuv-slide-lefteuv-fade-in-upeuv-progresseuv-shimmer

euv 中的 CSS 变量将样式从静态的关注点转变为响应式应用状态的一等公民,用极少的代码实现丰富、动态的用户体验。


项目代码:github.com/euv-dev/euv