东西坏了,靠我修,其实我也很不喜欢修东西,只是我还不想扔了它,所以我坚持坏了就修,这就显得我很主动,东西就觉得我离不开它了,它就可以一直坏一直坏,然后我一直修一直修。有没有一种可能?有一天,我不想修了呢?或者它不配了呢?我不要它了,那它是个什么玩意儿?
— 源于2025-10-09更新的一段内容。
那天我挺憋屈的,缘由说来也很搞笑,鸡毛蒜皮的一丁点小事,就这样燃起来的家庭纷争,我自身也是很诧异,为什么就这么点小事我压不住自己的怒火了,可能因为情绪总有一个临界点,我一直是忍让型的追求家和万事兴的态度,但是当下,有一个认知突然占据了我的思想,凭什么赚钱养家的是我,忍气吞声的还是我,所以我就炸了。
我本不想吵架,因为吵赢了只是把矛盾推向更高的地方,无法调和。所以大晚上的我就到操场上快走慢跑了8公里发泄自己的情绪,跑着跑着,越跑越不对劲,情绪更沮丧了,累瘫了坐在十字路口的小石墩上看着红绿灯变换读秒,挺落寞的,也挺心疼自己。
末了,我跟自己说:算了,都是自己惯的,能过就过,不能过就算了,这次我绝不先认怂。
闹矛盾之前,有听她念叨说 iPhone 17 Pro Max 京东本地都没货,所以我记在心里了,贝勒爷的同学家长有位是卖apple周边设备的,所以就跟他讲有货跟我说一声,找他拿一台。
闹矛盾之后,卖手机的说是手机有货了,溢价450元,问我要什么颜色,这都吵架冷战了,我也不好认怂去问她喜欢什么颜色啊,本来想问的,但是,呃,先开口不就输了吗?好,我不问。
不知道她喜欢什么颜色,就拿橙色和银色两台512GB回家,丢在桌子上。这次我对自己大方起来了,以往都是给她追新款,然后我自己随意将就,两台手机13和14用了好多年了,我也没舍得给自己换。心想让她先挑,剩下的我自己用,我也要开始自己享受消费了,这种心理有点变态,呃,为啥我赚钱给她优质生活,然后我自己对自己这么抠,然后她还可以这么高高在上的理所应当的不照顾我的感受。
手机在餐边柜桌子上放了两天,她也没动,我也没动,其实她动了,就是没拆封,因为装在袋子里的叠放顺序变了,肯定是我不在家的时候,自己拿起来纠结了半天,到底拆不拆呢,拆了不就认输了吗?
第三天早上,我还在睡觉,她到房间来,叫醒我,要我给她贴贴膜,拿着橙色和银色比划了半天,纠结要选什么颜色,我说你随便,你选好一台,另一台给我留着,然后我继续睡觉,十一点多起床后,她说要不她拿橙色吧,好的,那我就用修炼了十几年的贴膜技术完美的给她贴好了,但是这时候我还是端着呢,我话不多说,贴完橙色贴银色,贴好后,她拿走银色对比了一下,说银色比较商务,还是你拿吧。
一切好像从未发生过,呃,吵架,呃,根本没有发生过。晚上回房的时候,主动贴贴了,假装无意的翻个身靠着我,在试探我是不是不生气了,其实我早不生气了,真没骨气,唉。我内心其实很清楚,她的性格都是这些年我给她娇惯出来的,我当面指责她恃宠而骄,她并不否认。
但是日子总要过下去的吧,其实她还不错,就是这个家传的性格,有些太蛮横不讲理,事事都要顺遂她的心意,逆反她就是跟她势不两立。但是也相对收敛了很多,可能她也采集到我不忍了的想法,所以不敢再危险边缘来回试探蹦跶了。
果然,修东西,我还是一如既往的专业。
叨叨完,开始下一个主题,关于网站新增暗夜模式。
之前有博友评论说网站没有暗夜模式,可能体验感不佳吧,我现在对于修改网站主题有相当大的心理阴影,因为我的主题都是仿的,看到这个好看我就扒了,然后套进去自己的主题里,导致主题代码垒得跟屎山一样,一个主题里有七八个CSS样式文件,和一大堆可能无用可能有用的js文件,我主张,能用就行。但是这就给修改增加了很大的难度,有时候改一点点小东西,完犊子,前端垮了,垮在哪里,我也不知道,我再找找,就很难。
前几天逛 Jeffer.Z 的新作站点 FindBlog 觉得他的暗夜模式配色挺好看的,嗯,蠢蠢欲动的参照他站点的配色开始折腾了,他说站点黑夜模式是一个第三方的js库,直接调用就行。/npm/[email protected]/darkreader.js
听不懂,一点都听不懂,想到用插件来实现,因为我主题魔改的东西太多,所以导致插件装上去后,各种惨不忍睹的前端画面出现了,算了,我用我最简单暴力的方式来折腾吧。
我的想法是这样,我创建两个不同的配色方案样式文件,一个style-dark.css,一个style-light.css,然后通过js判断来识别加载哪一个文件,按道理说这个应该就可以满足我的需求,我打开了Gemini对接的聊天框,https://chat.erduo.tech,搭建来自己用的,因为对比之下,元宝的Deepseek和混元产出的代码内容,总是有各种问题出现,当然,可能是我站着尿尿的姿势不对,也可能是对准的方向不对,总之,我还是用的Gemini 2.5 Flash模型解决。
最早生成的插件php代码如下:
/**
* Plugin Name: 日夜模式切换器
* Plugin URI: https://www.edzbe.com
* Description: 为您的WordPress站点添加日夜模式切换功能,默认根据当地时间判断,并支持手动切换。
* Version: 1.0.1
* Author: 耳朵的主人
* Author URI: https://www.edzbe.com
* License: GPL-2.0+
* License URI: http://www.gnu.org/licenses/gpl-2.0.txt
* Text Domain: daynight-switcher
* Domain Path: /languages
*/
// 阻止直接访问文件,增强安全性
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* 注册并加载日夜模式样式和脚本
*/
function dns_enqueue_styles_scripts() {
// 获取主题目录URI(确保CSS文件位于主题的/css/子目录)
$theme_css_dir_uri = get_template_directory_uri() . '/css/';
// 注册白天模式样式(WordPress会自动生成ID:daynight-switcher-light-style-css)
wp_enqueue_style(
'daynight-switcher-light-style',
$theme_css_dir_uri . 'style-light.css',
array(),
'1.0.0'
);
// 注册暗夜模式样式(WordPress会自动生成ID:daynight-switcher-dark-style-css)
wp_enqueue_style(
'daynight-switcher-dark-style',
$theme_css_dir_uri . 'style-dark.css',
array(),
'1.0.0'
);
// 注册切换脚本(在页脚加载,确保DOM就绪)
wp_enqueue_script(
'daynight-switcher-script',
plugins_url( 'js/daynight-switcher.js', __FILE__ ),
array(),
'1.0.0',
true
);
// 将PHP数据传递给JS(如样式表ID、按钮ID)
wp_localize_script(
'daynight-switcher-script',
'dns_data',
array(
'lightStyleId' => 'daynight-switcher-light-style-css',
'darkStyleId' => 'daynight-switcher-dark-style-css',
'buttonId' => 'daynight-toggle',
// 'textLight' => esc_html__( '切换到暗夜模式', 'daynight-switcher' ),
// 'textDark' => esc_html__( '切换到白天模式', 'daynight-switcher' ),
)
);
}
add_action( 'wp_enqueue_scripts', 'dns_enqueue_styles_scripts' );
/**
* 在页面<body>开头添加切换按钮(兼容WP5.2+)
*/
function dns_add_toggle_button() {
if ( is_admin() ) return; // 排除后台
?>
<button id="daynight-toggle" class="daynight-toggle-button" aria-label="<?php esc_attr_e( '切换日夜模式', 'daynight-switcher' ); ?>">
<?php esc_html_e( '加载中...', 'daynight-switcher' ); ?>
</button>
<?php
}
add_action( 'wp_body_open', 'dns_add_toggle_button' );
/**
* 关键修复:提前在页面头部禁用非必要样式表,避免初始闪烁
* 逻辑:根据本地存储或系统时间,立即禁用不需要的样式表
*/
function dns_early_disable_unnecessary_styles() {
?>
<script>
(function() {
// 1. 获取用户偏好或系统默认模式
const savedMode = localStorage.getItem('theme-mode');
let initialMode;
if (savedMode && (savedMode === 'light' || savedMode === 'dark')) {
initialMode = savedMode;
} else {
const hour = new Date().getHours();
initialMode = (hour >= 6 && hour < 18) ? 'light' : 'dark'; // 6-18点白天
}
// 2. 禁用不需要的样式表(避免页面初始渲染错误)
const lightStyle = document.getElementById('<?php echo esc_js('daynight-switcher-light-style-css'); ?>');
const darkStyle = document.getElementById('<?php echo esc_js('daynight-switcher-dark-style-css'); ?>');
if (lightStyle && darkStyle) {
if (initialMode === 'light') {
darkStyle.disabled = true; // 禁用暗夜样式
} else {
lightStyle.disabled = true; // 禁用白天样式
}
}
})();
</script>
<?php
}
// 在<head>标签内优先执行(优先级1,确保最早运行)
add_action( 'wp_head', 'dns_early_disable_unnecessary_styles', 1 );
</head></body>
Js源码如下:
// 检查本地化数据是否正常
if (typeof dns_data === 'undefined') {
console.error('日夜模式切换器:未获取到本地化数据,请检查PHP的wp_localize_script调用!');
return;
}
// 解构本地化数据
const { lightStyleId, darkStyleId, buttonId, textLight, textDark } = dns_data;
// 获取DOM元素
const lightStyle = document.getElementById(lightStyleId);
const darkStyle = document.getElementById(darkStyleId);
const toggleButton = document.getElementById(buttonId);
// 元素存在性检查
if (!lightStyle || !darkStyle || !toggleButton) {
console.error('日夜模式切换器:未找到核心元素,请检查样式表/按钮ID是否正确!');
return;
}
console.log('日夜模式切换器:核心元素加载完成!');
/**
* 设置主题模式(更新样式、按钮文本、body类)
* @param {string} mode - 'light'或'dark'
* @param {boolean} save - 是否保存到localStorage
*/
function setMode(mode, save = true) {
// 更新样式表状态
if (mode === 'light') {
lightStyle.disabled = false;
darkStyle.disabled = true;
toggleButton.textContent = textLight;
toggleButton.setAttribute('aria-pressed', 'false');
} else {
lightStyle.disabled = true;
darkStyle.disabled = false;
toggleButton.textContent = textDark;
toggleButton.setAttribute('aria-pressed', 'true');
}
// 更新body类(方便扩展CSS)
document.body.classList.remove('light-mode', 'dark-mode');
document.body.classList.add(`${mode}-mode`);
// 保存用户偏好(可选)
if (save) localStorage.setItem('theme-mode', mode);
}
/**
* 获取系统默认模式
* @returns {string} 'light'或'dark'
*/
function getSystemMode() {
const hour = new Date().getHours();
return (hour >= 6 && hour < 18) ? 'light' : 'dark';
}
// --- 初始化 ---
const savedMode = localStorage.getItem('theme-mode');
const initialMode = savedMode && (savedMode === 'light' || savedMode === 'dark')
? savedMode
: getSystemMode();
setMode(initialMode, false); // 初始化模式(不保存)
console.log(`日夜模式切换器:初始模式为${initialMode}`);
// --- 切换按钮点击事件 ---
toggleButton.addEventListener('click', function() {
const currentMode = document.body.classList.contains('light-mode') ? 'light' : 'dark';
const newMode = currentMode === 'light' ? 'dark' : 'light';
setMode(newMode); // 切换并保存偏好
console.log(`日夜模式切换器:已切换至${newMode}`);
});
});
如上方式实现了暗夜模式的识别和切换,根据用户端的时间,采用“用户偏好优先,无偏好时根据本地时间自动判断,并支持手动切换和保存偏好”的模式。设定6点-18点为白天模式,18-次日6点为暗夜模式。
这个版本有一个问题出现了。
初始页面加载时的优化 (防止闪烁 FOUC):
1.dns_early_disable_unnecessary_styles 这个PHP函数非常关键。它在页面标签中以最高优先级(wp_head, 1)执行一个小的JavaScript片段。
2.这个JS片段会立即检查 localStorage 或根据时间判断初始模式,然后直接禁用不需要的CSS文件(style-light.css 或 style-dark.css)。
3.这样做的好处是,在浏览器开始渲染页面之前,已经确定并加载了正确的样式,有效避免了页面先显示一种模式然后迅速切换到另一种模式的“闪烁”现象 (Flash Of Unstyled Content)。
页面前端依然很不给面子的闪烁,白天和暗夜模式底色反差太大,一闪一闪让人很不舒服,本想着退而求其次,将就就好,管它白天闪晚上,还是晚上闪白天。然后,这两三天来,白天都在看房,跟着中介看这套看那套的,折腾得累半死,回家洗完澡一躺就睡了,这个问题也就这样搁置了几天。晚上得闲,一打开页面,页面切换的时候,闪得我自己都很不舒服,算了算了,我还是再修一修吧。
出现这个闪烁的原因,很可能是因为浏览器在解析HTML并应用内联JS脚本禁用CSS之前,已经短暂地加载并应用了所有样式表。如果 style-dark.css 中的样式优先级更高,或者它定义的默认颜色在没有其他限制的情况下会覆盖 style-light.css,就会导致先显示深色。
解决这个问题的更健壮方法是利用 link 标签的 media 属性,它在CSS加载层面就阻止了不必要的样式表被应用,而不是在JS加载后才去禁用。
产生了第二版插件代码:
- wp_enqueue_style 中使用 media=”not all”:
- 通过在PHP中将 style-dark.css 的 media 属性设置为 ‘not all’,我们告诉浏览器,这个样式表在任何媒体类型下都不适用。这意味着浏览器在解析HTML时,根本不会加载和应用 style-dark.css 的内容,从而避免了它在初始渲染时可能造成的深色闪烁。
- style-light.css 仍然保持 media=”all”,确保默认的白天模式样式能被加载。
- 早期JS脚本中切换 media 属性:
- 在 dns_early_disable_unnecessary_styles 中,我们不再使用 style.disabled = true/false,而是修改 style.media 属性。
- link.media = ‘all’ 会让浏览器立即应用该样式表。
- link.media = ‘not all’ 会让浏览器立即禁用该样式表。
- 这种方法比 disabled 属性更可靠,因为它在CSS加载和应用机制的更底层进行控制,通常能更快、更彻底地防止不希望的样式被渲染。
- setMode 函数中切换 media 属性:
- 手动切换模式时,JavaScript也需要同步地修改样式表的 media 属性,以确保页面动态地更新为正确的模式。
php代码如下:
/**
* Plugin Name: 日夜模式切换器
* Plugin URI: https://www.edzbe.com
* Description: 为您的WordPress站点添加日夜模式切换功能,默认根据当地时间判断,并支持手动切换。
* Version: 1.0.2
* Author: 耳朵的主人
* Author URI: https://www.edzbe.com
* License: GPL-2.0+
* License URI: http://www.gnu.org/licenses/gpl-2.0.txt
* Text Domain: daynight-switcher
* Domain Path: /languages
*/
// 阻止直接访问文件,增强安全性
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* 注册并加载日夜模式样式和脚本
*/
function dns_enqueue_styles_scripts() {
// 获取主题目录URI(确保CSS文件位于主题的/css/子目录)
$theme_css_dir_uri = get_template_directory_uri() . '/css/';
// 注册白天模式样式(默认 media="all",提供一个稳定的基线,避免布局错乱)
wp_enqueue_style(
'daynight-switcher-light-style',
$theme_css_dir_uri . 'style-light.css',
array(),
'1.0.0',
'all' // 白天模式默认加载,作为页面的基础颜色模式
);
// 注册暗夜模式样式(默认 media="not all",仅在需要时激活)
wp_enqueue_style(
'daynight-switcher-dark-style',
$theme_css_dir_uri . 'style-dark.css',
array(),
'1.0.0',
'not all' // 暗夜模式初始不加载,避免闪烁
);
// 注册切换脚本(在页脚加载,确保DOM就绪)
wp_enqueue_script(
'daynight-switcher-script',
plugins_url( 'js/daynight-switcher.js', __FILE__ ),
array(),
'1.0.0',
true
);
// 将PHP数据传递给JS(如样式表ID、按钮ID)
wp_localize_script(
'daynight-switcher-script',
'dns_data',
array(
'lightStyleId' => 'daynight-switcher-light-style-css',
'darkStyleId' => 'daynight-switcher-dark-style-css',
'buttonId' => 'daynight-toggle',
'textLight' => esc_html__( '切换到暗夜模式', 'daynight-switcher' ),
'textDark' => esc_html__( '切换到白天模式', 'daynight-switcher' ),
)
);
}
add_action( 'wp_enqueue_scripts', 'dns_enqueue_styles_scripts' );
/**
* 在页面<body>开头添加切换按钮(兼容WP5.2+)
*/
function dns_add_toggle_button() {
if ( is_admin() ) return; // 排除后台
?>
<button id="daynight-toggle" class="daynight-toggle-button" aria-label="<?php esc_attr_e( '切换日夜模式', 'daynight-switcher' ); ?>">
<?php esc_html_e( '加载中...', 'daynight-switcher' ); ?>
</button>
<?php
}
add_action( 'wp_body_open', 'dns_add_toggle_button' );
/**
* 关键修复:提前在页面头部设置正确的样式表和body类,避免初始闪烁
* 逻辑:根据本地存储或系统时间,在初始加载时,如果需要暗色模式,则立即激活暗色模式并禁用白天模式。
* 否则(即需要白天模式),白天模式已经默认加载,只需设置body类。
*/
function dns_early_disable_unnecessary_styles() {
?>
<script>
(function() {
// 1. 获取用户偏好或系统默认模式
const savedMode = localStorage.getItem('theme-mode');
let initialMode;
if (savedMode && (savedMode === 'light' || savedMode === 'dark')) {
initialMode = savedMode;
} else {
const hour = new Date().getHours();
initialMode = (hour >= 6 && hour < 18) ? 'light' : 'dark'; // 6-18点白天
}
// 2. 只有在初始模式是“dark”时才需要立即操作样式表,
// 因为“light”模式的样式表已经默认加载 media="all"
if (initialMode === 'dark') {
const lightStyle = document.getElementById('<?php echo esc_js('daynight-switcher-light-style-css'); ?>');
const darkStyle = document.getElementById('<?php echo esc_js('daynight-switcher-dark-style-css'); ?>');
if (lightStyle && darkStyle) {
lightStyle.media = 'not all'; // 禁用白天样式
darkStyle.media = 'all'; // 激活暗夜样式
}
}
// 3. 设置 body 的 class,确保全局背景色等样式同步
// 这一步在任何模式下都执行,以确保 body 类始终是正确的
document.body.classList.remove('light-mode', 'dark-mode'); // 移除可能的旧类
document.body.classList.add(`${initialMode}-mode`); // 添加当前模式类
})();
</script>
<?php
}
// 在<head>标签内优先执行(优先级1,确保最早运行)
add_action( 'wp_head', 'dns_early_disable_unnecessary_styles', 1 );
</head></body>
js代码如下:
// 检查本地化数据是否正常
if (typeof dns_data === 'undefined') {
console.error('日夜模式切换器:未获取到本地化数据,请检查PHP的wp_localize_script调用!');
return;
}
// 解构本地化数据
const { lightStyleId, darkStyleId, buttonId, textLight, textDark } = dns_data;
// 获取DOM元素
const lightStyle = document.getElementById(lightStyleId);
const darkStyle = document.getElementById(darkStyleId);
const toggleButton = document.getElementById(buttonId);
// 元素存在性检查
if (!lightStyle || !darkStyle || !toggleButton) {
console.error('日夜模式切换器:未找到核心元素,请检查样式表/按钮ID是否正确!');
return;
}
console.log('日夜模式切换器:核心元素加载完成!');
/**
* 设置主题模式(更新样式、按钮文本、body类)
* @param {string} mode - 'light'或'dark'
* @param {boolean} save - 是否保存到localStorage
*/
function setMode(mode, save = true) {
// 更新样式表状态,通过修改 media 属性来激活/禁用样式
if (mode === 'light') {
lightStyle.media = 'all'; // 激活白天样式
darkStyle.media = 'not all'; // 禁用暗夜样式
toggleButton.textContent = textLight;
toggleButton.setAttribute('aria-pressed', 'false');
} else { // mode === 'dark'
lightStyle.media = 'not all'; // 禁用白天样式
darkStyle.media = 'all'; // 激活暗夜样式
toggleButton.textContent = textDark;
toggleButton.setAttribute('aria-pressed', 'true');
}
// 更新body类(方便扩展CSS)
document.body.classList.remove('light-mode', 'dark-mode');
document.body.classList.add(`${mode}-mode`);
// 保存用户偏好(可选)
if (save) localStorage.setItem('theme-mode', mode);
}
/**
* 获取系统默认模式
* @returns {string} 'light'或'dark'
*/
function getSystemMode() {
const hour = new Date().getHours();
return (hour >= 6 && hour < 18) ? 'light' : 'dark';
}
// --- 初始化 ---
// 页面加载时的初始化逻辑已经由PHP中的dns_early_disable_unnecessary_styles处理,
// 这里再次调用setMode主要是为了同步按钮文本和body class,
// 并且确保在JS完全加载后,如果有未被早期脚本处理的逻辑(虽然现在应该没有了),也能正确执行。
const savedMode = localStorage.getItem('theme-mode');
const initialMode = savedMode && (savedMode === 'light' || savedMode === 'dark')
? savedMode
: getSystemMode();
// 注意:这里的 setMode(initialMode, false) 仅仅是确保DOM加载完成后,按钮文本等细节能够正确初始化。
// 真正避免闪烁的样式切换,由 dns_early_disable_unnecessary_styles 中的内联脚本处理。
setMode(initialMode, false);
console.log(`日夜模式切换器:初始模式为${initialMode}`);
// --- 切换按钮点击事件 ---
toggleButton.addEventListener('click', function() {
const currentMode = document.body.classList.contains('light-mode') ? 'light' : 'dark';
const newMode = currentMode === 'light' ? 'dark' : 'light';
setMode(newMode); // 切换并保存偏好
console.log(`日夜模式切换器:已切换至${newMode}`);
});
});
如上代码,其实应该是正常解决方案的,就是创建的 style-light.css 和 style-dark.css 文件如果只有配色方案,网页加载的时候,先加载 style.css 框架样式文件,这样页面就正常显示,只是配色根据选择加载,但是我又失败了,因为我开始弄的时候,把默认的style.css 框架样式直接套到 style-light.css 和 style-dark.css 这两个文件里了,要修改,又要重新开始分离出配色单独创建样式表,不行,不行,这工作量太大了,我不要。
我给Gemini发送了我的想法解决这个问题: 先加载页面的判断设置,直接屏蔽不加载另一种模式。Gemini很给力的给出了解决方案。
只加载所需的模式样式,而不是先加载一个再切换,避免任何形式的“闪烁”或“布局混乱”。
我之前的方案试图通过 `media` 属性和内联 JS 来解决,但在某些浏览器或加载条件下,即使是极快的切换,也可能出现视觉上的延迟。实现“直接屏蔽不加载另一种模式”的最彻底方法是:在 PHP 生成 HTML 的 `<head>` 部分时,就根据用户的偏好或时间判断,只输出当前所需模式的 `<link>` 标签,而完全不输出另一个模式的 `<link>` 标签。
但这有个挑战:PHP 在服务器端运行,它无法直接读取用户浏览器中的 `localStorage`。它只能读取 `$_COOKIE`。所以,为了让 PHP 在第一次加载页面时就知道用户偏好,JavaScript 必须在每次模式切换时,除了更新 `localStorage`,还要同时更新一个 Cookie。
最终版本php代码如下:
/**
* Plugin Name: 日夜模式切换器
* Plugin URI: https://www.edzbe.com
* Description: 为您的WordPress站点添加日夜模式切换功能,默认根据当地时间判断,并支持手动切换。
* Version: 1.0.3
* Author: 耳朵的主人
* Author URI: https://www.edzbe.com
* License: GPL-2.0+
* License URI: http://www.gnu.org/licenses/gpl-2.0.txt
* Text Domain: daynight-switcher
* Domain Path: /languages
*/
// 阻止直接访问文件,增强安全性
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* 提前判断初始模式(PHP服务器端)
* 优先级:Cookie (用户偏好) > 服务器时间
* @return string 'light' 或 'dark'
*/
function dns_get_initial_mode_server_side() {
// 1. 尝试从Cookie获取用户偏好
if ( isset( $_COOKIE['theme-mode'] ) && in_array( $_COOKIE['theme-mode'], ['light', 'dark'] ) ) {
return $_COOKIE['theme-mode'];
}
// 2. 如果Cookie不存在,根据服务器时间判断
$hour = (int) current_time( 'H', true ); // 使用WordPress的 current_time 获取当前小时,避免时区问题
return ($hour >= 6 && $hour < 18) ? 'light' : 'dark'; // 6-18点白天
}
/**
* 注册并加载日夜模式脚本
* 注意:日夜模式的样式文件不再在这里直接 enqueue,而是由 dns_output_conditional_styles 动态输出。
*/
function dns_enqueue_scripts() {
// 获取主题目录URI
$theme_css_dir_uri = get_template_directory_uri() . '/css/'; // 仍然传递给JS,以防需要手动构建 link 标签 fallback
// 注册切换脚本(在页脚加载,确保DOM就绪)
wp_enqueue_script(
'daynight-switcher-script',
plugins_url( 'js/daynight-switcher.js', __FILE__ ),
array(),
'1.0.0',
true
);
// 将PHP数据传递给JS(如样式表ID、按钮ID、CSS文件路径)
wp_localize_script(
'daynight-switcher-script',
'dns_data',
array(
'lightStyleId' => 'daynight-switcher-light-style-css',
'darkStyleId' => 'daynight-switcher-dark-style-css',
'buttonId' => 'daynight-toggle',
'textLight' => esc_html__( '切换到暗夜模式', 'daynight-switcher' ),
'textDark' => esc_html__( '切换到白天模式', 'daynight-switcher' ),
'lightStyleUrl' => $theme_css_dir_uri . 'style-light.css', // 传递CSS路径给JS,用于动态创建link标签
'darkStyleUrl' => $theme_css_dir_uri . 'style-dark.css', // 传递CSS路径给JS,用于动态创建link标签
)
);
}
add_action( 'wp_enqueue_scripts', 'dns_enqueue_scripts' ); // 重命名函数,因为不再enqueue styles
/**
* 在页面<body>开头添加切换按钮(兼容WP5.2+)
*/
function dns_add_toggle_button() {
if ( is_admin() ) return; // 排除后台
?>
<button id="daynight-toggle" class="daynight-toggle-button" aria-label="<?php esc_attr_e( '切换日夜模式', 'daynight-switcher' ); ?>">
<?php esc_html_e( '加载中...', 'daynight-switcher' ); ?>
</button>
<?php
}
add_action( 'wp_body_open', 'dns_add_toggle_button' );
/**
* 核心:在页面头部根据PHP判断动态输出正确的样式表和body类
* 这将避免任何模式切换时的闪烁。
*/
function dns_output_conditional_styles() {
if ( is_admin() ) return; // 排除后台
$initial_mode = dns_get_initial_mode_server_side();
$theme_css_dir_uri = get_template_directory_uri() . '/css/';
$version = '1.0.0'; // 样式表的版本号
// 输出正确的样式表
if ( $initial_mode === 'light' ) {
echo '<link rel="stylesheet" id="daynight-switcher-light-style-css" href="' . esc_url( $theme_css_dir_uri . 'style-light.css?ver=' . $version ) . '" type="text/css" media="all"/>';
echo '<link rel="stylesheet" id="daynight-switcher-dark-style-css" href="' . esc_url( $theme_css_dir_uri . 'style-dark.css?ver=' . $version ) . '" type="text/css" media="not all"/>';
} else { // dark mode
echo '<link rel="stylesheet" id="daynight-switcher-light-style-css" href="' . esc_url( $theme_css_dir_uri . 'style-light.css?ver=' . $version ) . '" type="text/css" media="not all"/>';
echo '<link rel="stylesheet" id="daynight-switcher-dark-style-css" href="' . esc_url( $theme_css_dir_uri . 'style-dark.css?ver=' . $version ) . '" type="text/css" media="all"/>';
}
// 立即在body标签上设置正确的class,防止任何延迟闪烁
// 注意:这个内联脚本在<?php标签内,所以它会先于任何其他JS执行
?>
<script>
// 确保body class在HTML解析时就存在
document.documentElement.classList.add('<?php echo esc_js( $initial_mode ); ?>-mode');
document.body.classList.add('<?php echo esc_js( $initial_mode ); ?>-mode');
// 后备或纠正逻辑(如果localStorage与PHP判断不符,或Cookie过期)
(function() {
const savedMode = localStorage.getItem('theme-mode');
const currentInitialMode = '<?php echo esc_js( $initial_mode ); ?>'; // PHP决定的初始模式
if (savedMode && savedMode !== currentInitialMode) {
// 如果localStorage和PHP的初始判断不一致,JS会立即纠正
const lightStyle = document.getElementById('daynight-switcher-light-style-css');
const darkStyle = document.getElementById('daynight-switcher-dark-style-css');
if (lightStyle && darkStyle) {
if (savedMode === 'light') {
lightStyle.media = 'all';
darkStyle.media = 'not all';
} else { // savedMode === 'dark'
lightStyle.media = 'not all';
darkStyle.media = 'all';
}
}
document.documentElement.classList.remove('light-mode', 'dark-mode');
document.documentElement.classList.add(`${savedMode}-mode`);
document.body.classList.remove('light-mode', 'dark-mode');
document.body.classList.add(`${savedMode}-mode`);
}
})();
</script>
<?php
}
// 在<head>标签内优先执行(优先级1,确保最早运行)
add_action( 'wp_head', 'dns_output_conditional_styles', 1 );
</head></body>
js代码如下:
// 检查本地化数据是否正常
if (typeof dns_data === 'undefined') {
console.error('日夜模式切换器:未获取到本地化数据,请检查PHP的wp_localize_script调用!');
return;
}
// 解构本地化数据
const { lightStyleId, darkStyleId, buttonId, textLight, textDark, lightStyleUrl, darkStyleUrl } = dns_data;
// 获取DOM元素
const lightStyle = document.getElementById(lightStyleId);
const darkStyle = document.getElementById(darkStyleId);
const toggleButton = document.getElementById(buttonId);
// 确保样式表元素存在。如果PHP动态输出失败,这里可能是null。
// 但是在最新的方案中,PHP一定会输出这两个link标签,只是media属性不同。
if (!lightStyle || !darkStyle || !toggleButton) {
// Fallback: 如果PHP没有输出link标签,JS动态创建它们
console.warn('日夜模式切换器:未找到核心样式表元素。尝试动态创建。');
// 如果lightStyle或darkStyle不存在,这里需要动态创建。
// 为了简化,这里假设它们总是由PHP输出。如果真的未找到,需要更复杂的JS fallback。
// 目前先跳过,因为PHP应该确保它们存在。
// return; // 暂时不返回,让下面的逻辑有机会运行
}
console.log('日夜模式切换器:核心元素加载完成!');
/**
* 设置Cookie
* @param {string} name - Cookie名称
* @param {string} value - Cookie值
* @param {number} days - Cookie过期天数
*/
function setCookie(name, value, days) {
let expires = "";
if (days) {
const date = new Date();
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
expires = "; expires=" + date.toUTCString();
}
document.cookie = name + "=" + (value || "") + expires + "; path=/";
}
/**
* 设置主题模式(更新样式、按钮文本、body类、同步Cookie)
* @param {string} mode - 'light'或'dark'
* @param {boolean} save - 是否保存到localStorage和Cookie
*/
function setMode(mode, save = true) {
// 更新样式表状态,通过修改 media 属性来激活/禁用样式
if (mode === 'light') {
if (lightStyle) lightStyle.media = 'all'; // 激活白天样式
if (darkStyle) darkStyle.media = 'not all'; // 禁用暗夜样式
toggleButton.textContent = textLight;
toggleButton.setAttribute('aria-pressed', 'false');
} else { // mode === 'dark'
if (lightStyle) lightStyle.media = 'not all'; // 禁用白天样式
if (darkStyle) darkStyle.media = 'all'; // 激活暗夜样式
toggleButton.textContent = textDark;
toggleButton.setAttribute('aria-pressed', 'true');
}
// 更新body和html根元素的类(方便扩展CSS)
document.documentElement.classList.remove('light-mode', 'dark-mode');
document.documentElement.classList.add(`${mode}-mode`);
document.body.classList.remove('light-mode', 'dark-mode');
document.body.classList.add(`${mode}-mode`);
// 保存用户偏好(可选)到 localStorage 和 Cookie
if (save) {
localStorage.setItem('theme-mode', mode);
setCookie('theme-mode', mode, 30); // Cookie保存30天
}
}
/**
* 获取系统默认模式
* @returns {string} 'light'或'dark'
*/
function getSystemMode() {
const hour = new Date().getHours();
return (hour >= 6 && hour < 18) ? 'light' : 'dark';
}
// --- 初始化 ---
// 页面加载时的初始模式判断和样式设置已由PHP在服务器端完成,
// 这里的JS初始化主要是确保按钮文本正确,并处理LocalStorage和Cookie可能不同步的情况。
const savedMode = localStorage.getItem('theme-mode');
let initialMode;
if (savedMode && (savedMode === 'light' || savedMode === 'dark')) {
initialMode = savedMode;
} else {
initialMode = getSystemMode();
}
// 确保当前的Cookie也与initialMode一致,防止PHP下次加载时判断错误
setCookie('theme-mode', initialMode, 30);
// 调用 setMode 来同步按钮文本,以及确保 body class 即使在PHP未能设置时也能被设置(虽然PHP现在会设置)
setMode(initialMode, false);
console.log(`日夜模式切换器:初始模式为${initialMode}`);
// --- 切换按钮点击事件 ---
toggleButton.addEventListener('click', function() {
// 从html根元素获取当前模式,因为CSS可能也通过html根元素控制
const currentMode = document.documentElement.classList.contains('light-mode') ? 'light' : 'dark';
const newMode = currentMode === 'light' ? 'dark' : 'light';
setMode(newMode); // 切换并保存偏好
console.log(`日夜模式切换器:已切换至${newMode}`);
});
});
核心原理和解决闪烁的方法:
1. PHP 服务器端预判: PHP 在页面生成之初(在 `wp_head` 动作中,优先级为1),就通过 `dns_get_initial_mode_server_side()` 函数,根据 `theme-mode` Cookie 或服务器时间,判断出应该显示的模式。
2. 只输出所需 `<link>` 标签: `dns_output_conditional_styles()` 函数根据 PHP 的判断结果,直接在 HTML 的 `<head>` 中输出 两个 `<link>` 标签。
• 正确的模式 的 `<link>` 标签 `media` 属性是 `all`。
• 不正确的模式 的 `<link>` 标签 `media` 属性是 `not all`。
这样,在浏览器开始解析和渲染 HTML 时,它只会加载并应用正确的模式样式,从而从源头上避免了加载错误样式导致的闪烁。
3. `document.documentElement` 和 `document.body` 类: PHP 还会输出一个内联 `<script>`,它会立即为 `<html>` 和 `<body>` 元素添加正确的模式类(例如 `dark-mode`)。这对于依赖这些类来设置背景色、字体颜色等全局样式的 CSS 规则至关重要,确保页面在渲染时就有正确的颜色基调。
4. JavaScript Cookie 同步: JS 代码在 `setMode` 时,除了更新 `localStorage`,还会更新一个 `theme-mode` Cookie。这样,用户下次访问时,PHP 就能从 Cookie 中读取到用户上次的选择。
5. 内联 JS 后备纠正: PHP 输出的内联 JS 中含有一个小的逻辑,用于比较 PHP 的判断(来自 Cookie 或时间)和 `localStorage` 中的用户偏好。如果两者不一致(例如,用户清除了 Cookie 但 `localStorage` 还在),这个内联 JS 会立即纠正模式,提供额外的鲁棒性。
代码仅供参考,在需要调用按钮的前端,添加按钮:
<?php esc_html_e( '加载中...', 'daynight-switcher' ); ?>
</button>
还有一个问题就是,header.php文件必须包含<?php wp_head(); ?> 和 footer.php文件代码必须包含 <?php wp_footer(); ?> ,按钮在二者之间的位置插入,否则无法正常加载,为什么,我也不知道,我是一步一步修过来的,怎么修的,不知道。
顺便提供下我的按钮图标:
完整插件代码下载:daynight-switcher
伪技术贴完工,睡觉。




代码没有夜间模式哦
还用11的路过,换手机总归是好事。感觉世界还是唯心的多点。
新的 iPhone 香吗?我还在用 13
之前我用插件加过页面模式,效果一般,又给去掉了。夜间模式最大的问题是图片的处理很多都处理不好,切换到夜间模式,有的图片就成了闪光灯。有的图片就成了无底洞,😂
完美