feat(appearance): add FOUC prevention inline script
提供同步执行的 inline 脚本字符串,在 hydration 前从 localStorage 读取 themeColor 和 zoomLevel 写入 <html>,避免首次加载时的主题/缩放闪烁。 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
41
src/lib/appearance-script.ts
Normal file
41
src/lib/appearance-script.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
// src/lib/appearance-script.ts
|
||||
|
||||
/**
|
||||
* Storage keys for appearance preferences.
|
||||
* 与 Provider 共享,确保 inline 脚本和 React 层读写同一份数据。
|
||||
*/
|
||||
export const STORAGE_KEY_THEME_COLOR = "codeg-theme-color"
|
||||
export const STORAGE_KEY_ZOOM_LEVEL = "codeg-zoom-level"
|
||||
|
||||
/**
|
||||
* 同步执行的 inline 脚本,由 layout.tsx 通过 dangerouslySetInnerHTML 注入。
|
||||
*
|
||||
* 必须在第一帧渲染前完成 <html> 的 data-theme 属性和 font-size 内联样式写入,
|
||||
* 否则会出现 FOUC(先看到默认主题/字号,然后切换到用户偏好的闪烁)。
|
||||
*
|
||||
* 实现要点:
|
||||
* 1. 纯字符串,不依赖任何模块导入或外部符号 —— 避免 Next.js 把它当模块编译
|
||||
* 2. 白名单校验 —— localStorage 里的值若被篡改或残留旧版本,回退到默认
|
||||
* 3. try/catch 包裹 —— 隐私模式 / 嵌入 WebView 禁用 storage 时不抛错
|
||||
* 4. 数字常量与 theme-presets.ts 保持一致 —— 任何修改必须两边同步
|
||||
*/
|
||||
const SCRIPT = `
|
||||
(function() {
|
||||
try {
|
||||
var VALID_COLORS = ["neutral","zinc","slate","stone","gray","red","rose","orange","green","blue","yellow","violet"];
|
||||
var VALID_ZOOMS = [80, 90, 100, 110, 125, 150];
|
||||
|
||||
var storedColor = localStorage.getItem("${STORAGE_KEY_THEME_COLOR}");
|
||||
var color = VALID_COLORS.indexOf(storedColor) >= 0 ? storedColor : "neutral";
|
||||
document.documentElement.setAttribute("data-theme", color);
|
||||
|
||||
var storedZoom = parseInt(localStorage.getItem("${STORAGE_KEY_ZOOM_LEVEL}") || "", 10);
|
||||
var zoom = VALID_ZOOMS.indexOf(storedZoom) >= 0 ? storedZoom : 100;
|
||||
document.documentElement.style.fontSize = (16 * zoom / 100) + "px";
|
||||
} catch (e) {
|
||||
// localStorage 不可用时静默走默认
|
||||
}
|
||||
})();
|
||||
`
|
||||
|
||||
export const APPEARANCE_INIT_SCRIPT = SCRIPT
|
||||
Reference in New Issue
Block a user