欢迎回来
登录你的知识库账户
忘记密码?
还没有账户?立即注册
创建账户
注册你的专属知识库
已有账户?去登录
找回密码
输入注册邮箱获取验证码
返回登录
请输入图片中的验证码以继续注册
加载中...
取消
新建收藏
手动添加你喜欢的内容
取消
编辑头像与昵称
上传新头像或修改你的显示昵称
支持 JPG/PNG,最大 2MB
取消

问题反馈

notebasewww.notebase.cn
控制台
内容库
动态
管理
账户
U
用户
--
在线
v0.8.7 · 知识库
笔记
KnowledgeBase
网络无边,知识有迹。
0笔记
0工具
30推荐

分类导航

按主题直达

编辑精选

站内用户贡献 · 真实笔记

最新收录

每日更新
继续浏览全部内容 →
>
笔记
0
加载中...
工具
0
此页用于记录用户反馈问题后的每一次改进
笔记用法

“写笔记”支持四种格式——Word 文档、Excel 表格、Markdown、纯文本,起稿或二次编辑时都能随时切换,同一篇笔记想用哪种形态来记,都由你说了算。

md、txt、csv、json 这类纯文本则原样载入,不做多余加工。拿一张现成的表倒进来、改几笔、再导出去,等于白用一台免费的格式转换器。

要带走就在右上角点“下载”,可导出 PDF、Word、Markdown、Excel、TXT 等格式;列表卡片“⋯”菜单里,也有同样的下载入口。

工具用法

在“工具”页点“+ 上传工具”即可发布:填好名称与链接,再用 Markdown 把使用方法写清楚——能解决什么问题、怎么装、怎么用,比堆介绍实在。

要分发安装包就一并上传压缩包(ZIP、RAR、7Z、TAR.GZ,最大 35MB),别人在详情页一键下载;只放链接不带附件也可以。

工具按大家的收藏热度排序,好用的自然会被顶上来。发布后可在详情页或卡片菜单里编辑、下架。

隐藏笔记

写笔记时勾上“隐藏”,这篇就只存在于你自己的账号里:不进列表、不进搜索、不上首页精选,也不会出现在任何公开的页面,链接发给别人同样打不开。

适合放密码、草稿、日记这类只给自己看的内容;想公开,去“发布”打开它,把“隐藏”的勾去掉再保存,之后编辑会默认保持原状态,不会悄悄变回公开。

数据安全

你的内容会同时保存在多个副本上,系统定期做备份与完整性校验,再配合异地容灾机制:就算某台机器出问题,数据也不会丢,可以长期放心存放;特别重要的资料,仍建议你另外再留一份备份。

技术

全站跑在容器化、模块化的现代架构上,更新、部署、回滚都很快,扩展性和稳定性都按长期运营的标准来设计(Built for reliability, designed to scale)。

理念

这个网站最早只是一个人的笔记仓库,后来慢慢长成现在的知识中枢。设计上很克制——没有广告、没有追踪、没有推荐算法,只是干干净净地存放一些东西;既然做好了,就公开出来,万一有人用得上呢。

原则

不做大而全,不做平台梦,保持简单、保持克制、保持好奇。所有内容都由用户贡献、由用户维护:不会突然冒出付费墙,不会在角落塞广告位,也不会把你的数据卖给第三方。

更多

产品会持续迭代,站内日志页记录着每一次改动,改了什么都有迹可循;想了解这个站是怎么一步步走到今天的,翻翻日志就能看到来龙去脉。

举报

如果在这里看到涉嫌违规的内容,点对应卡片右侧的“举报”按钮就能提交,我们会尽快核实处理;也谢谢你花一点时间,一起把这里维护干净。

趋势
// 点击导航加载发现
归档
// 归档为空
最近浏览
// 暂无浏览记录
发布
// 加载中...
用户发布
// 加载中...
用户管理
// 加载中...
访问统计
// 加载中...
内容审核
// 加载中...
个人信息
// 加载中...
返回首页

如何用纯 HTML 和 CSS 实现深色模式切换(无需 JavaScript)

2026/7/6编程开发

为什么我们要让网站不依赖 JavaScript?

先说一个可能有点反直觉的观点:你的网站应该在没有 JavaScript 的情况下也能正常工作。我不是说让你完全不用 JS,而是说它应该是一个渐进增强(progressive enhancement)的选项,而不是根基。

这些年我做了不少静态网站生成器(SSG)的项目,比如 Hugo、Eleventy 这些。它们一个共同点就是:生成出来的页面本身就是完整的 HTML,不需要 JS 才能看到内容。我自己的博客、WikiZEIT 项目,还有我那个叫“Complite”的 Eleventy 起始模板,都用了我自己折腾出来的这个深色模式切换方案——纯 HTML + CSS,零 JavaScript。

你可能要问:现在谁还不用 JS 啊?有这个必要吗?

有。而且原因比你想象的更实际。

1. 无障碍(Accessibility)

虽然现代屏幕阅读器已经能处理一些 JS 渲染的页面了,但这不是唯一的考量。有些人还在用老电脑、老手机,浏览器可能不支持最新的 JS 特性。还有些人网络很差,直接关掉了 JS 来省流量。你希望这些用户也能正常访问你的内容吧?

2. SEO

Google 是少数几个能比较好地渲染 JS 页面的爬虫,但它依然需要额外的时间和资源去执行 JS。相比之下,直接返回的服务器端 HTML 几乎是瞬间就能被索引。如果你的页面是纯客户端渲染的 React 应用,爬虫可能只看到 <div id="root"></div> 这一行——直到 JS 下载并执行完。这对 SEO 来说是个隐患。

3. AI 爬虫和答案引擎

现在越来越多的 AI 爬虫(比如 OpenAI、Anthropic 的抓取工具)并不像浏览器那样执行 JS。它们直接读原始 HTML 响应。如果你的内容要靠 JS 才能渲染出来,AI 可能根本看不到。

所以结论很明确:让网站在没有 JS 的情况下也能展示内容,是对人和机器都更友好的做法。

当然,这不意味着你非得写纯 HTML + CSS。你可以用 React,但最好搭配 Next.js 的 SSR 或 SSG,或者直接用 Hugo、Eleventy 这样的静态站点生成器。我本人是 Eleventy 的忠实用户,也写过相关教程。


HTML 结构:就两个 radio 按钮

好,道理讲完了,咱们直接上手。这个深色模式切换的 HTML 核心就是两个 radio 按钮:

<div class="theme-toggle">
  <input type="radio" id="mode_dark" name="mode" value="dark">
  <label for="mode_dark" aria-label="切换到深色模式"> 🌙 </label>

  <input type="radio" id="mode_light" name="mode" value="light">
  <label for="mode_light" aria-label="切换到浅色模式"> ☀️ </label>
</div>

注意几点:

  • 两个 radio 按钮的 name 相同,所以是互斥的——你只能选一个。
  • 每个 input 都有一个对应的 label,并且我们用了 aria-label 来提升无障碍体验。
  • 图标用的是月亮和太阳 emoji,简单直观。

这个结构本身没有任何 JS,就是纯 HTML。


CSS 核心::has() 选择器才是真正的魔法

现在到了最精彩的部分。我们要用 CSS 的 :has() 伪类来判断当前选中了哪个 radio 按钮。

:has() 有点像“父选择器”——CSS 社区盼了好多年终于盼到了。比如 p:has(img) 会匹配所有包含图片的 <p> 标签。而在我们的场景里,我们可以这样写:

html:has(#mode_dark:checked) p {
  color: white;
  background: black;
}

意思就是:如果页面上有一个 id 为 mode_dark 的 radio 按钮被选中了,那么所有 <p> 标签都变成白字黑底。

反过来,html:has(#mode_light:checked) 就可以定义浅色模式。

就是这么简单。不需要任何 JS 监听事件,不需要 class 切换,纯 CSS 就完成了状态管理。


美化切换按钮:让太阳和月亮只显示一个

光有功能还不够,我们得让这个切换按钮看起来像那么回事。核心思路是:任何时候只显示一个图标,而且默认显示的是系统偏好对应的那个。

/* 容器布局 */
.theme-toggle {
  display: flex;
  align-items: center;
}

/* 彻底隐藏 radio 按钮本身 */
.theme-toggle input[type="radio"] {
  appearance: none;
  -webkit-appearance: none;
  margin: 0;
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

/* 让 label 变成一个可点击的 40x40 区域 */
.theme-toggle label {
  width: 40px;
  height: 40px;
  display: grid;
  place-items: center;
  cursor: pointer;
}

接下来是关键:默认情况下,显示月亮(深色模式图标),隐藏太阳。但如果用户系统偏好是深色模式,则反过来。

/* 默认(浅色系统偏好):显示月亮,隐藏太阳 */
label[for="mode_light"] {
  display: none;
}
label[for="mode_dark"] {
  display: grid;
}

/* 如果用户系统偏好是深色模式:显示太阳,隐藏月亮 */
@media (prefers-color-scheme: dark) {
  label[for="mode_light"] {
    display: grid;
  }
  label[for="mode_dark"] {
    display: none;
  }
}

然后,当用户手动切换后,我们要用 :has() 覆盖掉默认状态:

/* 用户手动选了深色模式:显示太阳(表示可以切回浅色) */
:root:has(#mode_dark:checked) label[for="mode_light"] {
  display: grid;
}
:root:has(#mode_dark:checked) label[for="mode_dark"] {
  display: none;
}

/* 用户手动选了浅色模式:显示月亮(表示可以切回深色) */
:root:has(#mode_light:checked) label[for="mode_light"] {
  display: none;
}
:root:has(#mode_light:checked) label[for="mode_dark"] {
  display: grid;
}

这里有个小细节值得注意:切换按钮上显示的是你将要切换到的模式图标。比如当前是深色模式,按钮上就显示太阳,点击后变成浅色模式。这符合直觉——用户看到太阳就知道点一下会变亮。


CSS 变量:让主题切换覆盖整个页面

最后一步,也是让整个方案真正可用的关键:用 CSS 变量来控制页面的所有颜色。

我们可以在 :root 上定义变量,然后用 :has() 和 @media 来给变量赋值:

/* 系统偏好:深色模式 */
@media (prefers-color-scheme: dark) {
  :root {
    --bg: #252525;   /* 深灰 */
    --fg: #fff;      /* 白色 */
  }
}

/* 用户手动选了深色模式 */
:root:has(#mode_dark:checked) {
  --bg: #252525;
  --fg: #fff;
}

/* 用户手动选了浅色模式 */
:root:has(#mode_light:checked) {
  --bg: #fff;        /* 白色 */
  --fg: #252525;     /* 深灰 */
}

然后在整个页面的 CSS 里,你只需要引用这些变量:

body {
  background: var(--bg);
  color: var(--fg);
}

这样一来,你就不需要到处写 html:has(#mode_dark:checked) p { color: white; } 这种重复代码了。所有颜色定义集中管理,清晰又干净。

当然,实际项目中你可能不止两个变量。你可能会有 --link-color、--border-color、--shadow 等等。但原理完全一样。


总结一下

这个方案的核心思路其实很简单:

  1. 用 radio 按钮存储用户选择(不需要 JS 变量或 localStorage)。
  2. 用 CSS 的 :has() 伪类检测选中状态,并据此改变样式。
  3. 用 @media (prefers-color-scheme: dark) 尊重系统偏好,作为默认值。
  4. 用 CSS 变量集中管理所有颜色,让代码更干净。

最终效果是:一个完全不需要 JavaScript 的深色模式切换按钮,而且它尊重用户系统偏好,用户可以手动覆盖。

如果你对这个方案感兴趣,可以看看我做的 CodePen 演示,或者直接去我的博客和 WikiZEIT 项目里看实际效果。

最后再说一句:渐进增强是个好习惯。先让 HTML + CSS 把事情做好,再考虑用 JS 锦上添花。这样你的网站对所有人、所有爬虫都更友好。


有任何问题可以 Twitter/X 上找我,我开着 DM。也可以看看我的个人博客。

编写使用方法
Markdown 格式 · Ctrl+Enter 确定
新建笔记
预览
数据表格
点击单元格编辑 · Tab 移动
A1fx
Sheet1
BIH1H2≡🔗</>
隐私提醒

取消
编辑工具
取消