HTML&CSS&JS:丝滑无卡顿的明暗主题切换

这个 HTML 页面实现了一个现代化、丝滑过渡的白天/夜间主题切换功能,完全基于 CSS 自定义属性(CSS 变量)、light-dark() 函数和少量 JavaScript 实现持久化。


大家复制代码时,可能会因格式转换出现错乱,导致样式失效。建议先少量复制代码进行测试,若未能解决问题,私信回复源码两字,我会发送完整的压缩包给你。

演示效果

HTML&CSS

html 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>主题切换</title>
    <style>
        @charset "UTF-8";

        :root {
            --brand-color-oklch: oklch(65% 0.15 250);
            --light-bg: oklch(98% 0.01 250);
            --dark-bg: oklch(25% 0.05 250);
            --bg-color: light-dark(var(--light-bg), var(--dark-bg));
            --light-text: oklch(20% 0.05 250);
            --dark-text: oklch(95% 0.01 250);
            --text-color: light-dark(var(--light-text), var(--dark-text));
            --box-bg-light: oklch(from var(--brand-color-oklch) 90% c h);
            --box-bg-dark: oklch(from var(--brand-color-oklch) 35% c h);
            --box-bg: light-dark(var(--box-bg-light), var(--box-bg-dark));
            --box-text-light: oklch(from var(--brand-color-oklch) 25% c h);
            --box-text-dark: oklch(from var(--brand-color-oklch) 95% c h);
            --box-text: light-dark(var(--box-text-light), var(--box-text-dark));
            --toggle-track-light: oklch(80% 0.08 250);
            --toggle-track-dark: oklch(50% 0.12 250);
            --toggle-track: light-dark(var(--toggle-track-light),
                    var(--toggle-track-dark));
            --toggle-thumb-light: oklch(99% 0 0);
            --toggle-thumb-dark: oklch(30% 0.05 250);
            --toggle-thumb: light-dark(var(--toggle-thumb-light),
                    var(--toggle-thumb-dark));
            color-scheme: light;
        }

        html.dark {
            color-scheme: dark;
        }

        *,
        *::before,
        *::after {
            box-sizing: border-box;
        }

        body {
            background-color: var(--bg-color);
            color: var(--text-color);
            font-family: "Georgia", serif;
            display: grid;
            place-content: center;
            min-height: 100vh;
            margin: 0;
            gap: 2rem;
            transition: background-color 0.4s ease, color 0.4s ease;
        }

        .themed-box {
            background-color: var(--box-bg);
            color: var(--box-text);
            padding: 2rem 3rem;
            border-radius: 12px;
            font-size: 1.5rem;
            text-align: center;
            transition: background-color 0.4s ease, color 0.4s ease;
        }

        .toggle-wrap {
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 0.75rem;
            font-size: 0.85rem;
            letter-spacing: 0.05em;
            text-transform: uppercase;
            opacity: 0.75;
        }

        #theme-toggle {
            position: absolute;
            opacity: 0;
            width: 0;
            height: 0;
        }

        .toggle-label {
            position: relative;
            display: inline-block;
            width: 56px;
            height: 30px;
            cursor: pointer;
        }

        .toggle-label::before {
            content: "";
            position: absolute;
            inset: 0;
            background: var(--toggle-track);
            border-radius: 999px;
            transition: background 0.4s ease;
        }

        .toggle-label::after {
            content: "";
            position: absolute;
            top: 3px;
            left: 3px;
            width: 24px;
            height: 24px;
            border-radius: 50%;
            background: var(--toggle-thumb);
            box-shadow: 0 1px 4px oklch(0% 0 0/0.25);
            transition: transform 0.35s cubic-bezier(0.34, 1.56, 0.64, 1), background 0.4s ease;
        }

        #theme-toggle:checked+.toggle-label::after {
            transform: translateX(26px);
        }

        .toggle-label .icon {
            position: absolute;
            top: 50%;
            transform: translateY(-50%);
            font-size: 14px;
            line-height: 1;
            pointer-events: none;
            user-select: none;
            transition: opacity 0.3s ease;
        }

        .icon-sun {
            left: 6px;
        }

        .icon-moon {
            right: 6px;
        }

        .icon-moon {
            opacity: 0.35;
        }

        #theme-toggle:checked~.toggle-wrap .icon-sun {
            opacity: 0.35;
        }
    </style>
</head>

<body>
    <div class="themed-box">
        Hello 我是超级酷炫且丝滑的主题页面!
    </div>

    <div class="toggle-wrap">
        <span>白天</span>
        <input type="checkbox" id="theme-toggle">
        <label for="theme-toggle" class="toggle-label" aria-label="Toggle dark mode">
            <span class="icon icon-sun" aria-hidden="true">☀️</span>
            <span class="icon icon-moon" aria-hidden="true">🌙</span>
        </label>
        <span>夜间</span>
    </div>

    <script>
        const toggle = document.getElementById("theme-toggle");
        const html = document.documentElement;

        const saved = localStorage.getItem("theme");
        if (saved === "dark") {
            html.classList.add("dark");
            toggle.checked = true;
        }

        toggle.addEventListener("change", () => {
            if (toggle.checked) {
                html.classList.add("dark");
                localStorage.setItem("theme", "dark");
            } else {
                html.classList.remove("dark");
                localStorage.setItem("theme", "light");
            }
        });

    </script>
</body>

</html>

HTML

  • div themed-box:主题展示盒。背景 / 文字色随明暗主题切换,用于直观展示主题效果。
  • div toggle-wrap:切换控件容器。包含「白天 / 夜间」文字、切换复选框、滑块标签。
  • div themed-box:展示内容区域,其背景色和文字颜色会随主题自动变化。
  • input checkbox theme-toggle:隐藏的复选框,作为主题切换的"状态控制器"。通过 :checked 伪类触发样式变化。
  • label theme-toggle:可点击的自定义开关按钮,关联到隐藏的 checkbox,提升无障碍访问性。
  • span icon-sun / icon-moon:分别显示太阳(☀️)和月亮(🌙)图标,通过透明度变化指示当前模式。

CSS

1. 核心色彩变量(OKLCH 模型)

css 复制代码
:root {
  /* 品牌基准色(OKLCH:亮度 65%,色度 0.15,色相 250) */
  --brand-color-oklch: oklch(65% 0.15 250);

  /* 页面背景色:light-dark() 自动适配系统/手动主题 */
  --light-bg: oklch(98% 0.01 250); /* 浅色主题背景(高亮低彩) */
  --dark-bg: oklch(25% 0.05 250); /* 深色主题背景(低亮中彩) */
  --bg-color: light-dark(var(--light-bg), var(--dark-bg));

  /* 文字色、卡片背景/文字色、切换滑块色均采用相同逻辑 */
  --text-color: light-dark(var(--light-text), var(--dark-text));
  --box-bg: light-dark(var(--box-bg-light), var(--box-bg-dark));
  /* ... 其他变量 */

  color-scheme: light; /* 默认浅色配色方案 */
}

/* 暗主题时切换配色方案 */
html.dark {
  color-scheme: dark;
}

核心

  • OKLCH 色彩模型:相比 RGB/HEX,更接近人眼感知,亮度(L)、色度(C)、色相(H)分离,主题切换时色彩更协调;
  • light-dark () 函数:自动根据 color-scheme 或手动主题切换变量值,无需重复写两套样式;
  • 色彩继承:通过 oklch(from var(--brand-color-oklch) L C H) 基于品牌色派生主题色,保证视觉统一性。

2. 全局布局与过渡动效

css 复制代码
body {
  background-color: var(--bg-color);
  color: var(--text-color);
  font-family: "Georgia", serif;
  display: grid;
  place-content: center; /* 垂直水平居中 */
  min-height: 100vh;
  margin: 0;
  gap: 2rem;
  transition: background-color 0.4s ease, color 0.4s ease; /* 背景/文字色过渡 */
}

.themed-box {
  background-color: var(--box-bg);
  color: var(--box-text);
  padding: 2rem 3rem;
  border-radius: 12px;
  text-align: center;
  transition: background-color 0.4s ease, color 0.4s ease; /* 卡片色过渡 */
}

核心

  • 网格布局实现内容居中,适配全屏幕;
  • 所有主题相关颜色(背景、文字、卡片)都加 0.4s 过渡,保证切换时「丝滑无卡顿」;
  • 卡片 themed-box 作为主题效果的直观展示载体,颜色随主题同步变化。

3. 切换滑块(Toggle)样式

css 复制代码
/* 隐藏原生复选框 */
#theme-toggle {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}

/* 滑块轨道(label 伪元素) */
.toggle-label::before {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--toggle-track); /* 轨道色随主题变 */
  border-radius: 999px; /* 胶囊形轨道 */
  transition: background 0.4s ease;
}

/* 滑块按钮(label 伪元素) */
.toggle-label::after {
  content: "";
  position: absolute;
  top: 3px;
  left: 3px;
  width: 24px;
  height: 24px;
  border-radius: 50%; /* 圆形按钮 */
  background: var(--toggle-thumb); /* 按钮色随主题变 */
  box-shadow: 0 1px 4px oklch(0% 0 0/0.25);
  /* 自定义缓动曲线,切换更丝滑 */
  transition: transform 0.35s cubic-bezier(0.34, 1.56, 0.64, 1), background 0.4s ease;
}

/* 选中时滑块右移 */
#theme-toggle:checked+.toggle-label::after {
  transform: translateX(26px);
}

/* 太阳/月亮图标显隐 */
.icon-sun { left: 6px; }
.icon-moon { right: 6px; opacity: 0.35; }
#theme-toggle:checked~.toggle-wrap .icon-sun { opacity: 0.35; }
#theme-toggle:checked~.toggle-wrap .icon-moon { opacity: 1; } /* 暗主题时月亮图标高亮 */

核心

  • 用 label 伪元素模拟滑块轨道 / 按钮,隐藏原生复选框,保证自定义样式;
  • 滑块移动用「自定义缓动曲线」(cubic-bezier),比默认 ease 更有「弹性感」;
  • 太阳 / 月亮图标随复选框状态切换透明度,视觉反馈更直观。

JavaScript

CSS 通过 html.dark 类触发暗主题,JS 负责添加 / 移除该类,并同步 localStorage,实现「主题持久化」。

js 复制代码
// ===================== 核心功能:明暗主题切换 + 主题持久化 =====================
// 1. 获取核心DOM元素(操作入口)
// 获取页面中id为"theme-toggle"的复选框(主题切换开关,视觉上隐藏,通过label触发)
const toggle = document.getElementById("theme-toggle");
// 获取HTML根元素,用于通过类名控制全局主题样式(CSS中通过html.dark触发暗主题)
const html = document.documentElement;

// 2. 页面初始化:恢复用户上次保存的主题(持久化核心)
// 从浏览器本地存储(localStorage)读取键为"theme"的值,localStorage数据永久保存(除非手动清除)
const saved = localStorage.getItem("theme");
// 如果本地存储中保存的主题是"dark"(暗主题)
if (saved === "dark") {
    // 给HTML根元素添加"dark"类,触发CSS中定义的暗主题样式(如背景/文字色切换)
    html.classList.add("dark");
    // 将切换按钮(复选框)设置为选中状态,保证按钮视觉状态与主题一致(暗主题时滑块在右侧)
    toggle.checked = true;
}
// 补充:如果本地存储无值/值为"light",则不执行上述逻辑,默认显示浅色主题

// 3. 监听切换按钮状态变化,实现实时主题切换
// 给复选框添加"change"事件监听:当复选框选中状态改变时触发回调函数
// 触发场景:用户点击切换滑块(label标签),复选框状态从选中→未选中/未选中→选中
toggle.addEventListener("change", () => {
    // 判断复选框当前是否为选中状态(用户切换到暗主题)
    if (toggle.checked) {
        // 给HTML根元素添加"dark"类,切换到暗主题样式
        html.classList.add("dark");
        // 将当前主题(dark)保存到本地存储,实现"刷新/重启页面不丢失主题"
        localStorage.setItem("theme", "dark");
    } else {
        // 复选框未选中(用户切换回浅色主题):移除HTML根元素的"dark"类
        html.classList.remove("dark");
        // 将当前主题(light)保存到本地存储,记录用户选择
        localStorage.setItem("theme", "light");
    }
});

各位互联网搭子,要是这篇文章成功引起了你的注意,别犹豫,关注、点赞、评论、分享走一波,让我们把这份默契延续下去,一起在知识的海洋里乘风破浪!

相关推荐
文阿花13 分钟前
Echarts实现自定旋转3D饼状图
javascript·3d·echarts·饼状图
meilindehuzi_a1 小时前
深入理解 JavaScript 的同步与异步机制:从单线程设计到 Promise 核心应用
开发语言·javascript·ecmascript
如烟花的信页1 小时前
加速乐cookie逆向分析
javascript·爬虫·python·js逆向
永远的WEB小白1 小时前
css改变svg图标的颜色
前端·javascript·css
ikoala1 小时前
Codex 不得不装的 12 个插件,都在这了
前端·javascript·后端
赵庆明老师2 小时前
JS检查提交的文件是否合规
开发语言·前端·javascript
颂love2 小时前
Vue的两大生态以及组件通信
前端·javascript·vue.js·typescript
光影少年2 小时前
js单线程,为什在node环境下的js可以处理高并发请求?
前端·javascript·掘金·金石计划
moMo3 小时前
# JavaScript 的“等等我”:聊聊同步与异步
javascript
Xzh04233 小时前
Web 前端开发 — 期末复习指南(Html、Css、Js)
css·html5·web·js·期末