在Blazor应用开发中,动态切换CSS实现深色和浅色模式是提升用户使用体验的重要功能,用户可以根据自己的使用场景选择更舒适的界面主题,避免强光下看不清内容或者暗环境下界面过亮刺眼的问题。

实现原理概述
实现深色/浅色模式的核心思路是定义两套不同主题的CSS样式,再通过Blazor的逻辑控制当前生效的样式。常见的方式有两种,一种是通过CSS变量定义不同主题的颜色值,切换时修改根节点的变量值;另一种是为不同主题准备独立的CSS文件,动态切换页面引入的CSS文件地址。
方案一:基于CSS变量的实现方式
1. 定义CSS变量
首先在项目的wwwroot/css/app.css文件中定义两套主题的变量,根节点默认使用浅色主题的变量:
/* 浅色主题变量 */
:root {
--primary-bg-color: #ffffff;
--primary-text-color: #333333;
--secondary-bg-color: #f5f5f5;
--border-color: #e0e0e0;
}
/* 深色主题变量 */
[data-theme="dark"] {
--primary-bg-color: #1a1a1a;
--primary-text-color: #e0e0e0;
--secondary-bg-color: #2d2d2d;
--border-color: #444444;
}
2. 编写基础页面样式
页面中的样式统一使用定义好的CSS变量,而不是直接写固定颜色值:
.page-container {
background-color: var(--primary-bg-color);
color: var(--primary-text-color);
min-height: 100vh;
padding: 20px;
}
.card {
background-color: var(--secondary-bg-color);
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 16px;
margin-bottom: 16px;
}
3. Blazor组件实现切换逻辑
创建一个主题切换组件,管理当前主题状态,修改根节点的data-theme属性来切换CSS变量:
@using Microsoft.JSInterop
@inject IJSRuntime JSRuntime
<div class="theme-switch-container">
<button @onclick="SwitchToLight" class="@(CurrentTheme == "light" ? "active" : "")">浅色模式</button>
<button @onclick="SwitchToDark" class="@(CurrentTheme == "dark" ? "active" : "")">深色模式</button>
</div>
@code {
[Parameter]
public string CurrentTheme { get; set; } = "light";
[Parameter]
public EventCallback<string> OnThemeChanged { get; set; }
private async Task SwitchToLight()
{
CurrentTheme = "light";
await JSRuntime.InvokeVoidAsync("setThemeAttribute", "light");
await OnThemeChanged.InvokeAsync(CurrentTheme);
}
private async Task SwitchToDark()
{
CurrentTheme = "dark";
await JSRuntime.InvokeVoidAsync("setThemeAttribute", "dark");
await OnThemeChanged.InvokeAsync(CurrentTheme);
}
}
4. 添加JS互操作代码
在wwwroot/js/app.js中添加修改根节点属性的JS方法:
function setThemeAttribute(theme) {
document.documentElement.setAttribute("data-theme", theme);
// 持久化主题到本地存储
localStorage.setItem("app-theme", theme);
}
// 初始化时读取本地存储的主题
function getInitTheme() {
return localStorage.getItem("app-theme") || "light";
}
5. 主布局中引入组件
在MainLayout.razor中引入主题切换组件,初始化时读取本地存储的主题:
@using Microsoft.JSInterop
@inject IJSRuntime JSRuntime
<div class="page-container">
<ThemeSwitchComponent @ref="themeComponent" OnThemeChanged="HandleThemeChanged" />
@Body
</div>
@code {
private ThemeSwitchComponent themeComponent;
protected override async Task OnInitializedAsync()
{
var initTheme = await JSRuntime.InvokeAsync<string>("getInitTheme");
await JSRuntime.InvokeVoidAsync("setThemeAttribute", initTheme);
}
private void HandleThemeChanged(string theme)
{
// 可以在这里处理主题切换后的额外逻辑
}
}
方案二:动态切换CSS文件实现
1. 准备两套独立CSS文件
在wwwroot/css目录下创建light-theme.css和dark-theme.css,分别编写两套主题的完整样式:
/* light-theme.css 内容 */
.page-container {
background-color: #ffffff;
color: #333333;
}
.card {
background-color: #f5f5f5;
border: 1px solid #e0e0e0;
}
/* dark-theme.css 内容 */
.page-container {
background-color: #1a1a1a;
color: #e0e0e0;
}
.card {
background-color: #2d2d2d;
border: 1px solid #444444;
}
2. 动态修改CSS引入地址
在index.html(Blazor WebAssembly)或者_Host.cshtml(Blazor Server)中,给CSS引入标签添加id:
<link id="theme-css" rel="stylesheet" href="css/light-theme.css" />
然后在Blazor组件中通过JS互操作修改这个link标签的href属性:
@using Microsoft.JSInterop
@inject IJSRuntime JSRuntime
<button @onclick="ChangeToDark">切换深色模式</button>
<button @onclick="ChangeToLight">切换浅色模式</button>
@code {
private async Task ChangeToDark()
{
await JSRuntime.InvokeVoidAsync("changeCssFile", "css/dark-theme.css");
localStorage.setItem("app-theme", "dark");
}
private async Task ChangeToLight()
{
await JSRuntime.InvokeVoidAsync("changeCssFile", "css/light-theme.css");
localStorage.setItem("app-theme", "light");
}
}
对应的JS方法:
function changeCssFile(cssPath) {
const link = document.getElementById("theme-css");
if (link) {
link.href = cssPath;
}
}
两种方案对比
| 对比项 | CSS变量方案 | 动态切换CSS文件方案 |
|---|---|---|
| 实现复杂度 | 较低,只需维护一套CSS文件 | 较高,需要维护两套独立的CSS文件 |
| 切换流畅度 | 高,无需重新加载CSS文件 | 稍低,需要加载新的CSS文件 |
| 样式隔离性 | 一般,变量全局生效 | 好,两套样式完全独立 |
| 适用场景 | 主题差异较小,仅颜色等基础样式不同 | 主题差异较大,布局或组件样式也有区别 |
扩展功能实现
监听系统主题变化
可以通过JS监听系统的主题偏好变化,自动同步应用主题:
function listenSystemTheme() {
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
mediaQuery.addEventListener("change", (e) => {
const newTheme = e.matches ? "dark" : "light";
setThemeAttribute(newTheme);
// 通知Blazor组件主题变化
if (window.dotnetHelper) {
window.dotnetHelper.invokeMethodAsync("HandleSystemThemeChange", newTheme);
}
});
}
在Blazor组件中注册dotnetHelper,处理系统主题变化事件即可。
主题状态持久化
前面已经通过localStorage存储了用户选择的主題,下次打开应用时自动读取存储的主题,不需要用户重新选择,提升使用体验。
注意:如果需要支持服务端预渲染,需要在服务端也读取主题状态,避免页面加载时出现主题闪烁的问题,可以在服务端通过cookie存储主题状态,预渲染时优先使用cookie中的主题值。