第24章 昼夜模式与毛玻璃效果

现代 UI 特效实现

作者:zyyc-0336  |  日期:2026-06-12

作者:zyyc-0336  |  日期:2026-06-12

内容标签#CSS #昼夜模式 #毛玻璃 #前端

本文是前端学习路径第24章(阶段五:外围生态)

一句话总结:用 CSS 变量 + JS 切换主题实现昼夜模式,用 backdrop-filter: blur() 实现毛玻璃效果,两者结合就是现代网站的"高级感"密码。


学习路径建议
路径
CSS 变量(自定义属性)基础 ↓ 昼夜模式原理:亮色 / 暗色两套变量 ↓ 手动实现主题切换按钮 ↓ next-themes 库(偷懒神器) ↓ backdrop-filter 毛玻璃效果 ↓ 实战:导航栏毛玻璃 + 主题切换

常见错误提示

忘加 transition:切换主题时颜色突变很刺眼,记得加过渡动画

backdrop-filter 不生效:元素必须有半透明背景(如 rgba),否则看不出效果

SSR 闪烁:服务端渲染时还没读到主题,会先闪一下亮色再变暗色,用 next-themes 解决

prefers-color-scheme 搞混:这是系统设置,不是你的主题设置,别忘了让用户手动选


目录


24.1 什么是昼夜模式

概念解释

昼夜模式就是网站有"亮色主题"和"暗色主题"两套配色方案,用户可以一键切换。你天天用的微信、手机设置、VS Code 都有这个功能。

为什么需要?

实现原理

步骤做什么用什么
1定义两套颜色变量CSS 变量(--bg-color 等)
2根据主题切换变量值htmldata-theme="dark" 类名
3提供切换按钮JS 监听点击,切换类名
4记住用户选择localStorage

"我直接写两套 CSS 文件不行吗?" 可以,但维护两套文件太痛苦。用 CSS 变量只需要改变量值,样式代码只写一份。


24.2 CSS 变量实现主题切换

概念解释

CSS 变量(也叫"自定义属性")就是你可以自己定义的"颜色名字",然后在整个网站里引用它。切换主题时只需要改这个"名字"对应的值。

语法表格

语法说明示例
--变量名: 值;定义变量--bg-color: #ffffff;
var(--变量名)使用变量background: var(--bg-color);
:root { }全局定义(相当于 html)变量放这里,全站可用
[data-theme="dark"]属性选择器选中暗色主题暗色变量放这里

完整可运行代码

css
/* styles/theme.css */ /* ========== 亮色主题(默认) ========== */ :root { --bg-color: #ffffff; --text-color: #1a1a1a; --card-bg: #f5f5f5; --border-color: #e0e0e0; --primary-color: #3b82f6; --shadow-color: rgba(0, 0, 0, 0.1); } /* ========== 暗色主题 ========== */ [data-theme="dark"] { --bg-color: #1a1a2e; --text-color: #e0e0e0; --card-bg: #16213e; --border-color: #2a2a4a; --primary-color: #60a5fa; --shadow-color: rgba(0, 0, 0, 0.3); } /* ========== 应用变量 ========== */ body { background-color: var(--bg-color); color: var(--text-color); transition: background-color 0.3s ease, color 0.3s ease; } .card { background: var(--card-bg); border: 1px solid var(--border-color); box-shadow: 0 2px 8px var(--shadow-color); }

逐行讲解

代码说明
1-7:root { --bg-color: #fff... }亮色主题的颜色定义,:root = 全局
10-16[data-theme="dark"] { ... }当 html 标签有 data-theme="dark" 时,覆盖这些变量
19-22body { background: var(--bg-color)... }使用变量,切换主题时自动变色
21transition: ... 0.3s ease加过渡动画,切换时颜色渐变不突兀

transition 一定要加! 不加的话主题切换是"闪"过去的,很丑。加个 0.3 秒的过渡就丝滑了。


24.3 手动实现主题切换按钮

概念解释

手动实现就是不用任何库,纯用原生 JS 来切换主题。原理很简单:点击按钮 → 切换 htmldata-theme 属性 → CSS 变量自动生效。

完整可运行代码

jsx
// components/ThemeToggle.jsx 'use client' import { useState, useEffect } from 'react' export default function ThemeToggle() { const [theme, setTheme] = useState('light') // 页面加载时,读取 localStorage 里保存的主题 useEffect(() => { const saved = localStorage.getItem('theme') if (saved) { setTheme(saved) document.documentElement.setAttribute('data-theme', saved) } }, []) // 切换主题 const toggleTheme = () => { const newTheme = theme === 'light' ? 'dark' : 'light' setTheme(newTheme) document.documentElement.setAttribute('data-theme', newTheme) localStorage.setItem('theme', newTheme) // 记住选择 } return ( <button onClick={toggleTheme} className="theme-toggle"> {theme === 'light' ? '🌙 切换暗色' : '☀️ 切换亮色'} </button> ) }
css
/* 按钮样式 */ .theme-toggle { padding: 8px 16px; border: 2px solid var(--border-color); border-radius: 20px; background: var(--card-bg); color: var(--text-color); cursor: pointer; font-size: 14px; transition: all 0.3s ease; } .theme-toggle:hover { border-color: var(--primary-color); }

逐行讲解

代码说明
5const [theme, setTheme] = useState('light')用 state 记录当前主题
8-13useEffect(() => {...})页面加载时从 localStorage 读取上次保存的主题
10document.documentElement.setAttribute(...)<html> 标签设置 data-theme 属性
17-21toggleTheme 函数亮变暗、暗变亮,同时存到 localStorage
25{theme === 'light' ? '🌙' : '☀️'}根据主题显示不同图标

document.documentElement 是什么? 就是 <html> 标签。不是 document.body!变量是定义在 :root(等于 html)上的,所以要改 html 的属性。

忘记 'use client':这个组件用了 useStateuseEffect,是客户端组件,必须加这行,Next.js 才不会报错。


24.4 用 next-themes 偷懒

概念解释

next-themes 是专门为 Next.js 写的主题切换库,帮你处理了:

一句话:5 行代码搞定主题切换,别自己造轮子了。

安装与使用对比

对比项手动实现next-themes
代码量30+ 行5 行
SSR 闪烁需要自己处理自动处理
系统主题跟随要自己写内置支持
localStorage手动读写自动
学习成本低(但坑多)极低

完整可运行代码

bash
# 第 1 步:安装 npm install next-themes
jsx
// app/providers.jsx 'use client' import { ThemeProvider } from 'next-themes' export default function Providers({ children }) { return ( <ThemeProvider attribute="data-theme" defaultTheme="system"> {children} </ThemeProvider> ) }
jsx
// app/layout.jsx import Providers from './providers' export default function RootLayout({ children }) { return ( <html lang="zh" suppressHydrationWarning> <body> <Providers> {children} </Providers> </body> </html> ) }
jsx
// components/ThemeToggle.jsx 'use client' import { useTheme } from 'next-themes' import { useEffect, useState } from 'react' export default function ThemeToggle() { const { theme, setTheme } = useTheme() const [mounted, setMounted] = useState(false) // 避免 SSR 水合不匹配 useEffect(() => setMounted(true), []) if (!mounted) return null return ( <button onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')} className="theme-toggle" > {theme === 'dark' ? '☀️ 亮色模式' : '🌙 暗色模式'} </button> ) }

逐行讲解

文件关键代码说明
providers.jsx<ThemeProvider attribute="data-theme">告诉库用 data-theme 属性切换主题
providers.jsxdefaultTheme="system"默认跟随系统设置
layout.jsxsuppressHydrationWarning防止 SSR 水合警告(因为服务端和客户端主题可能不同)
ThemeToggle.jsxconst { theme, setTheme } = useTheme()一行代码获取主题状态和切换函数
ThemeToggle.jsxif (!mounted) return null服务端不渲染,避免闪烁

mounted 是干嘛的? 服务端渲染时不知道用户是什么主题,直接渲染会导致服务端和客户端内容不一致(水合错误)。先返回 null,等客户端挂载后再渲染。

suppressHydrationWarning 别乱加:只在 <html> 标签上加,其他地方加了会掩盖真正的错误。


24.5 backdrop-filter 毛玻璃效果

概念解释

毛玻璃效果就是让元素背景变成"磨砂玻璃"——你能透过它看到后面的模糊内容,但又看不清楚。macOS 的 Dock 栏、iOS 的通知中心都是这种效果。

语法表格

属性说明
backdrop-filter: blur(10px)数值越大越模糊核心属性,设置模糊半径
backdrop-filter: blur() saturate(180%)多个滤镜组合模糊 + 增强饱和度
background: rgba(255,255,255,0.7)半透明背景必须有,否则看不出模糊
-webkit-backdrop-filter同上Safari 浏览器需要加前缀

关键:为什么必须有半透明背景?

背景设置效果
background: white完全看不到毛玻璃,白色挡住了
background: rgba(255,255,255,0.7)✅ 70% 白色 + 透过去的内容模糊
background: rgba(255,255,255,0)全透明,模糊效果太强,内容看不清
没写 background❌ 无效,backdrop-filter 需要背景才能生效

完整可运行代码

css
/* 毛玻璃卡片 */ .glass-card { background: rgba(255, 255, 255, 0.25); /* 半透明背景 */ backdrop-filter: blur(12px); /* 核心:模糊 */ -webkit-backdrop-filter: blur(12px); /* Safari 兼容 */ border: 1px solid rgba(255, 255, 255, 0.3); /* 半透明边框 */ border-radius: 12px; padding: 20px; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); } /* 暗色模式下的毛玻璃 */ [data-theme="dark"] .glass-card { background: rgba(0, 0, 0, 0.25); /* 暗色半透明 */ border: 1px solid rgba(255, 255, 255, 0.1); }

逐行讲解

代码说明
2background: rgba(255, 255, 255, 0.25)25% 不透明度的白色,让后面的内容可以"透过来"
3backdrop-filter: blur(12px)对元素后面的内容施加 12px 高斯模糊
4-webkit-backdrop-filterSafari 浏览器的兼容写法,必须加
5border: 1px solid rgba(255,255,255,0.3)半透明边框,和毛玻璃风格统一

写了 backdrop-filter 没效果? 99% 是因为没有写 background(或背景完全不透明)。毛玻璃效果的本质是"透过半透明背景看模糊的后面内容",没有半透明背景就没效果。

Safari 没效果? 忘了加 -webkit-backdrop-filter。苹果系浏览器(Safari、iOS WebView)必须加 -webkit- 前缀。

filter: blur()backdrop-filter: blur():前者模糊的是元素自身内容,后者模糊的是元素后面的内容。导航栏要用 backdrop-filter


24.6 实战:导航栏毛玻璃 + 主题切换

完整可运行代码

jsx
// components/Navbar.jsx 'use client' import { useTheme } from 'next-themes' import { useEffect, useState } from 'react' export default function Navbar() { const { theme, setTheme } = useTheme() const [mounted, setMounted] = useState(false) useEffect(() => setMounted(true), []) return ( <nav className="navbar"> <div className="navbar-inner"> {/* 左侧 Logo */} <span className="logo">🌐 MyBlog</span> {/* 右侧导航 + 主题切换 */} <div className="nav-right"> <a href="/">首页</a> <a href="/about">关于</a> <a href="/blog">文章</a> {mounted && ( <button onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')} className="theme-btn" > {theme === 'dark' ? '☀️' : '🌙'} </button> )} </div> </div> </nav> ) }
css
/* styles/navbar.css */ /* 导航栏:固定在顶部 + 毛玻璃 */ .navbar { position: fixed; /* 固定定位,滚动时跟着走 */ top: 0; left: 0; right: 0; z-index: 1000; /* 确保在最上层 */ /* 毛玻璃三件套 */ background: rgba(255, 255, 255, 0.7); backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px); border-bottom: 1px solid rgba(0, 0, 0, 0.1); transition: background 0.3s ease; } /* 暗色模式导航栏 */ [data-theme="dark"] .navbar { background: rgba(26, 26, 46, 0.7); border-bottom: 1px solid rgba(255, 255, 255, 0.05); } /* 导航栏内容居中 */ .navbar-inner { max-width: 1200px; margin: 0 auto; padding: 12px 24px; display: flex; justify-content: space-between; align-items: center; } /* Logo */ .logo { font-size: 20px; font-weight: bold; color: var(--text-color); } /* 右侧导航链接 */ .nav-right { display: flex; align-items: center; gap: 20px; } .nav-right a { color: var(--text-color); text-decoration: none; font-size: 15px; opacity: 0.8; transition: opacity 0.2s; } .nav-right a:hover { opacity: 1; } /* 主题切换按钮 */ .theme-btn { padding: 6px 10px; border: 1px solid var(--border-color); border-radius: 8px; background: transparent; cursor: pointer; font-size: 18px; transition: all 0.2s; } .theme-btn:hover { background: var(--card-bg); }

效果说明

特性实现方式
导航栏固定在顶部position: fixed
背景模糊(毛玻璃)backdrop-filter: blur(16px)
半透明背景rgba(255,255,255,0.7)
暗色模式适配[data-theme="dark"] 选择器覆盖颜色
切换主题next-themesuseTheme
防止闪烁mounted 状态判断

position: fixed 后导航栏"消失"了? 可能被其他元素挡住了,检查 z-index 是否足够大。

max-width: 1200px 的作用? 让内容不会在大屏幕上拉得太宽。margin: 0 auto 让它居中。


24.7 本章复盘

知识点核心内容一句话记忆
CSS 变量--变量名 定义,var() 使用变量是主题切换的基础
主题切换原理给 html 加 data-theme 属性,CSS 变量自动切换改一个属性,全局颜色跟着变
手动实现JS 切换 data-theme + localStorage 持久化自己造轮子,30 行代码
next-themesThemeProvider 包裹 + useTheme 使用5 行代码,自动处理 SSR 闪烁
mounted 防闪烁useEffect 设置 mounted,未挂载返回 null服务端不渲染主题相关内容
backdrop-filterblur(12px) 模糊后面的内容必须配合半透明 background
-webkit- 前缀Safari 浏览器兼容苹果浏览器要加前缀
filter vs backdrop-filterfilter 模糊自身,backdrop-filter 模糊后面导航栏用 backdrop
suppressHydrationWarning只在 <html> 上加,消除 SSR 警告别乱加,只加 html 标签

速查卡片

速查
🎨 昼夜模式 = CSS 变量 + data-theme 切换 🧊 毛玻璃 = 半透明背景 + backdrop-filter: blur() ⚡ next-themes = ThemeProvider + useTheme 🔒 持久化 = localStorage(库帮你搞定) 🚫 防闪烁 = mounted 状态 + suppressHydrationWarning

🎉 恭喜你完成了阶段五(外围生态)的全部学习! 从 Tailwind CSS 到第三方组件,从 MongoDB 数据库到评论功能,再到昼夜模式与毛玻璃效果——你的前端技能树又点亮了一大片。


下一章预告:前端基础学习告一段落!接下来可以探索TypeScript状态管理测试等进阶主题。

本文档由 zyyc-0336 编写,最后更新:2026-06-14

24.7 本章复盘

知识点核心内容一句话记忆
CSS 变量--变量名 定义,var() 使用变量是主题切换的基础
主题切换原理给 html 加 data-theme 属性,CSS 变量自动切换改一个属性,全局颜色跟着变
手动实现JS 切换 data-theme + localStorage 持久化自己造轮子,30 行代码
next-themesThemeProvider 包裹 + useTheme 使用5 行代码,自动处理 SSR 闪烁
mounted 防闪烁useEffect 设置 mounted,未挂载返回 null服务端不渲染主题相关内容
backdrop-filterblur(12px) 模糊后面的内容必须配合半透明 background
-webkit- 前缀Safari 浏览器兼容苹果浏览器要加前缀
filter vs backdrop-filterfilter 模糊自身,backdrop-filter 模糊后面导航栏用 backdrop
suppressHydrationWarning只在 <html> 上加,消除 SSR 警告别乱加,只加 html 标签

下一章预告:前端基础学习告一段落!接下来可以探索TypeScript状态管理测试等进阶主题。

本文档由 zyyc-0336 编写,最后更新:2026-06-14