学会这篇,你能给自己的 PHP 站做出一套「首屏不闪、刷新记住、可跟随系统」的主题切换,代码不到 50 行。
第一步:先定方案,别一上来就写 JS
主题切换有三条路,选错后面全是坑:
- 纯前端 localStorage——JS 在页面加载后才改样式,首屏一定闪一下白(FOUC),适合纯静态页;
- Cookie + PHP 服务端渲染——PHP 在输出
<html> 时就把主题写进属性,首屏零闪烁,推荐;
- 用户表存字段——登录用户跨设备同步,通常和第 2 条叠加使用。
结论:Cookie 为主,登录用户再存一份到数据库。
第二步:用 CSS 变量定义主题
把颜色抽成变量,挂在 :root 和 html[data-theme] 上,切换时只改一个属性,不用重载样式表:
:root{
--bg:#fff; --fg:#222; --card:#f7f7f7; --accent:#2b7de9;
}
html[data-theme="dark"]{
--bg:#16181d; --fg:#e8e8e8; --card:#1f2229; --accent:#5aa2ff;
}
body{ background:var(--bg); color:var(--fg); }
注意:属性写在 <html> 上,不要写在 <body>。写在 body 上时浏览器已经开始渲染,容易看到闪动。
第三步:PHP 读 Cookie,带白名单校验
在模板最顶部(输出任何内容之前)处理,直接放进 <html> 标签:
<?php
$themes = ['light', 'dark', 'blue']; // 白名单,只认这几个
$t = $_COOKIE['theme'] ?? 'light';
if (!in_array($t, $themes, true)) $t = 'light';
?>
<html lang="zh-CN" data-theme="<?= htmlspecialchars($t, ENT_QUOTES) ?>">
这一步是整个方案的关键:PHP 先给页面定好主题,浏览器第一帧就是正确的颜色。
注意:in_array 白名单和 htmlspecialchars 一个都不能少。Cookie 是用户可以随手改的,直接把 $_COOKIE['theme'] 回显进 HTML 属性就是标准的反射型 XSS。转义时带上 ENT_QUOTES,防止引号逃逸。
第四步:前端切换按钮
切换只需三件事:改属性、写 Cookie、按钮状态更新。
<button type="button" data-theme-set="dark">深色</button>
<button type="button" data-theme-set="light">浅色</button>
<script>
document.querySelectorAll('[data-theme-set]').forEach(function(btn){
btn.addEventListener('click', function(){
var t = this.dataset.themeSet;
document.documentElement.setAttribute('data-theme', t);
document.cookie = 'theme=' + t + ';path=/;max-age=31536000;SameSite=Lax'
+ (location.protocol === 'https:' ? ';Secure' : '');
});
});
</script>
服务端如果用 setcookie() 写过主题 Cookie,参数(path、SameSite、Secure)要和这里保持一致。
注意:PHP 端 setcookie 的 path 是 /,JS 端如果漏了 path=/,会生成两个同名 Cookie,页面刷新后主题随机跳。
第五步:让「自动」跟随系统
给主题加一个 auto 值,用媒体查询兜底,这样连 JS 都不用参与:
@media (prefers-color-scheme: dark){
html[data-theme="auto"]{ --bg:#16181d; --fg:#e8e8e8; --card:#1f2229; }
}
PHP 白名单里加上 'auto',默认值设成 auto 即可。用户选过具体主题就覆盖,没选过就一直跟着系统走。
第六步:登录用户跨设备同步(可选)
数据库用户表加一个 theme varchar(20) 字段,有登录态时优先用它:
$t = $user['theme'] ?? $t; // 用户设置优先于 Cookie
切换时再发一个 POST 请求写库,接口里必须带 CSRF 令牌。
注意:如果站里开了页面级 HTML 缓存或 CDN 全站缓存,主题一进 HTML 就会被缓存成「所有人共用一个颜色」。这种场景要么把主题排除出缓存 key,要么退回纯前端方案。
小结
- 主题状态放 Cookie,PHP 在输出
<html> 时确定,首屏才不闪;纯 JS 方案一定会闪。
- 两个铁律:白名单校验 +
htmlspecialchars 转义,缺一个就是 XSS。
- 样式全部走 CSS 变量,切换只改
data-theme 一个属性,性能最好也最好维护。
auto 值配合 prefers-color-scheme 媒体查询,能免 JS 跟随系统。
- 主题偏好写数据库时记得带 CSRF 令牌,Cookie 的 path/SameSite 前后端要一致。