第十四章:CSS 样式方案与 Tailwind 集成

博客v1.0系列教程(Dioxus)博客 v1.0 系列教程 (Dioxus)

第十四章:CSS 样式方案与 Tailwind 集成

1. Dioxus 中的样式方式

Dioxus 为组件提供了多种样式方案:

| 方式 | 适用场景 | 特点 | |------|---------|------| | 内联 style | 动态样式、少量覆盖 | 直接绑定 Signal,响应式变化 | | 类名 class | 静态样式、Tailwind | 配合 CSS 框架,批量管理 | | document::Stylesheet | 全局样式文件 | 引入外部 CSS | | CSS-in-RSX | 组件级样式 | 类似 styled-components |

1.1 内联样式

Dioxus 的内联样式支持两种写法:

// 方式一:字符串(推荐,支持动态值)
div {
    style: "color: var(--text); background: {bg_color}; padding: {padding}px;",
    "内容"
}

// 方式二:键值对(仅静态)
div {
    color: "red",
    background_color: "blue",
    padding: "10px",
}

注意:键值对方式不支持动态值(不能插入 {signal}),如果需要条件样式,用字符串方式并拼接:

div {
    style: "background: {if dark_mode() { \"#1a1a2e\" } else { \"#fff\" }}; color: {if dark_mode() { \"#fff\" } else { \"#000\" }};",
}

1.2 类名管理

// 静态类名
div { class: "flex items-center gap-4 p-4 rounded-lg" }

// 条件类名(字符串拼接)
div { class: "p-4 rounded-lg {if selected() { \"bg-blue-500 text-white\" } else { \"bg-gray-100\" }}" }

// 多条件类名(预计算)
let card_class = use_memo(move || {
    let base = "rounded-lg border p-4 transition-all";
    match (theme(), variant()) {
        ("dark", "primary") => format!("{base} bg-blue-900 border-blue-700"),
        ("light", "primary") => format!("{base} bg-blue-50 border-blue-200"),
        ("dark", _) => format!("{base} bg-gray-800 border-gray-700"),
        _ => format!("{base} bg-white border-gray-200"),
    }
});

div { class: "{card_class}" }

1.3 条件样式的三种模式

// 模式一:类名切换(推荐)
let btn = "px-4 py-2 rounded {if disabled() { \"opacity-50 cursor-not-allowed\" } else { \"hover:bg-blue-600 cursor-pointer\" }}";

// 模式二:内联 style 切换
button {
    style: "background: {if danger() { \"#ef4444\" } else { \"#3b82f6\" }};",
}

// 模式三:use_memo 预计算完整样式字符串
let style = use_memo(move || {
    format!(
        "background:{}; color:{}; border:1px solid {};",
        if dark() { "#1e1e2e" } else { "#fff" },
        if dark() { "#cdd6f4" } else { "#4c4f69" },
        if dark() { "#313244" } else { "#ccd0da" },
    )
});
div { style: "{style}" }

2. Tailwind CSS 集成

2.1 CDN 方式(开发用)

index.html 中添加:

<script src="https://cdn.tailwindcss.com"></script>

这是项目当前使用的方式,适合开发和原型阶段。

2.2 编译方式(生产推荐)

# 安装 Tailwind CLI
npm install -D tailwindcss

# 初始化配置
npx tailwindcss init
// tailwind.config.js
module.exports = {
    content: ["./src/**/*.rs", "./index.html"],
    theme: { extend: {} },
    plugins: [],
}
# 构建 CSS
npx tailwindcss -i ./input.css -o ./public/tailwind.css --watch

2.3 Tailwind 与 CSS 变量配合

/* main.css */
:root {
    --bg: #eff1f5;
    --card: #e6e9ef;
    --border: #ccd0da;
    --text: #4c4f69;
    --primary: #1e66f5;
}

[data-theme="dark"] {
    --bg: #1e1e2e;
    --card: #181825;
    --border: #313244;
    --text: #cdd6f4;
    --primary: #89b4fa;
}

在组件中使用:

div {
    class: "rounded-lg border p-5",
    style: "background: var(--card); border-color: var(--border);",
    h3 { class: "font-semibold", style: "color: var(--secondary);", "标题" }
}

这样做的好处:Tailwind 处理布局和间距,CSS 变量处理颜色主题,分工清晰。

3. 响应式布局

3.1 Tailwind 断点

// 响应式网格
div { class: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6",
    // 手机: 1列
    // 平板: 2列
    // 桌面: 3列
}

// 响应式三栏布局
div { class: "lg:grid lg:grid-cols-[220px_1fr_240px] lg:gap-8",
    aside { class: "hidden lg:block", /* 侧边栏 */ }
    main { /* 主内容 */ }
    aside { class: "hidden lg:block", /* 右侧边栏 */ }
}

| 断点 | 前缀 | 最小宽度 | |------|------|---------| | 手机 | 默认 | 0 | | 平板 | md: | 768px | | 桌面 | lg: | 1024px | | 宽屏 | xl: | 1280px |

3.2 响应式侧边栏模式

// 移动端:侧边栏作为抽屉
#[component]
fn ResponsiveLayout() -> Element {
    let sidebar_open = use_signal(|| false);

    rsx! {
        div { class: "flex",
            // 移动端遮罩
            if sidebar_open() {
                div {
                    class: "fixed inset-0 bg-black/50 z-40 lg:hidden",
                    onclick: move |_| sidebar_open.set(false),
                }
            }
            // 侧边栏:移动端浮层 | 桌面端固定
            aside {
                class: "fixed lg:sticky top-0 left-0 h-full z-50
                        transform {if sidebar_open() { \"translate-x-0\" } else { \"-translate-x-full\" }}
                        lg:transform-none lg:w-60 w-72
                        transition-transform duration-300",
                style: "background: var(--card);",
                // 侧边栏内容
            }
            // 主内容
            main { class: "flex-1 min-w-0",
                // 移动端菜单按钮
                button {
                    class: "lg:hidden p-2",
                    onclick: move |_| sidebar_open.set(true),
                    "☰"
                }
            }
        }
    }
}

4. 动画与过渡

4.1 CSS Transition

// Tailwind 过渡类
a {
    class: "transition-colors duration-200 hover:text-blue-500",
}
button {
    class: "transition-all duration-300 hover:scale-105 hover:shadow-lg",
}
div {
    class: "transition-opacity duration-500 {if visible() { \"opacity-100\" } else { \"opacity-0\" }}",
}

4.2 动画进入/离开

#[component]
fn FadeIn(visible: bool, children: Element) -> Element {
    rsx! {
        div {
            class: "transition-all duration-500 overflow-hidden",
            style: "max-height: {if visible { \"500px\" } else { \"0\" }}; opacity: {if visible { \"1\" } else { \"0\" }};",
            {children}
        }
    }
}

// 使用
FadeIn {
    visible: expanded(),
    div { class: "p-4", "折叠面板内容..." }
}

4.3 列表动画

// 列表项出现动画
for (i, article) in articles.iter().enumerate() {
    div {
        key: "{article.slug}",
        class: "transition-all duration-300",
        style: "animation: fadeInUp 0.3s ease-out {i * 50}ms both;",
        ArticleCard { summary: article.clone() }
    }
}

CSS 中定义关键帧:

@keyframes fadeInUp {
    from { opacity: 0; transform: translateY(20px); }
    to   { opacity: 1; transform: translateY(0); }
}

5. CSS 变量主题系统深入

5.1 变量定义

:root {
    /* 颜色 */
    --bg: #eff1f5;
    --card: #e6e9ef;
    --border: #ccd0da;
    --text: #4c4f69;
    --secondary: #5c5f77;
    --tertiary: #9ca0b0;
    --primary: #1e66f5;
    --primary-light: #d6e4fb;
    --hover: #ccd0da;

    /* 间距 */
    --sidebar-width: 220px;
    --header-height: 64px;

    /* 字体 */
    --font-mono: "SF Mono", "Fira Code", monospace;
}

[data-theme="dark"] {
    --bg: #1e1e2e;
    --card: #181825;
    --border: #313244;
    --text: #cdd6f4;
    --secondary: #bac2de;
    --tertiary: #6c7086;
    --primary: #89b4fa;
    --primary-light: #2a4a7a;
    --hover: #313244;
}

5.2 组件级变量覆盖

div {
    style: "
        --card-bg: var(--card);
        --card-border: var(--border);
        background: var(--card-bg);
        border-color: var(--card-border);
    ",
}

5.3 JavaScript 主题切换

function toggleTheme() {
    var html = document.documentElement;
    var next = html.getAttribute('data-theme') === 'dark' ? 'light' : 'dark';
    html.setAttribute('data-theme', next);
    localStorage.setItem('blog-theme', next);
}

6. 实战:三栏布局完整实现

use dioxus::prelude::*;

#[component]
fn ThreeColumnLayout(
    left: Element,
    center: Element,
    right: Element,
) -> Element {
    rsx! {
        div { class: "min-h-screen flex flex-col",
            style: "background: var(--bg); color: var(--text);",

            // 顶部导航栏
            Header {}

            // 三栏主体
            main { class: "flex-1",
                div { class: "max-w-[1400px] mx-auto px-4 md:px-6 py-7
                             lg:grid lg:grid-cols-[220px_1fr_240px] lg:gap-8",

                    // 左侧栏
                    aside { class: "hidden lg:block",
                        div { class: "sticky top-24 max-h-[calc(100vh-8rem)] overflow-y-auto
                                      space-y-5",
                            style: "scrollbar-width: thin; scrollbar-color: var(--border) transparent;",
                            {left}
                        }
                    }

                    // 主内容区
                    div { class: "min-w-0", {center} }

                    // 右侧栏
                    aside { class: "hidden lg:block",
                        div { class: "sticky top-24 max-h-[calc(100vh-8rem)] overflow-y-auto
                                      space-y-5",
                            style: "scrollbar-width: thin; scrollbar-color: var(--border) transparent;",
                            {right}
                        }
                    }
                }
            }

            Footer {}
        }
    }
}

7. 暗夜模式图片适配

/* 图片自动适配暗夜模式 */
.prose-content img {
    filter: none;
    transition: filter 0.3s;
}

[data-theme="dark"] .prose-content img {
    filter: brightness(0.8) contrast(1.2);
}

/* 或者使用 picture 元素提供两套图 */
picture {
    source {
        srcset: "{dark_src}",
        media: "(prefers-color-scheme: dark)",
    }
    img { src: "{light_src}" }
}

8. 小结

  • Dioxus 支持内联 styleclass 两种样式方式
  • 动态样式用字符串内联 + Signal,静态用 Tailwind 类名
  • CSS 变量 + data-theme 是实现暗夜模式的最佳方案
  • Tailwind 断点实现响应式布局,hidden lg:block 控制侧边栏显隐
  • transition-* 类实现简单动画
  • CSS 变量集中管理颜色主题,组件只引用 var(--xxx),解耦和维护性都好
dioxuscsstailwindstylingresponsive