import { DARK_MODE, DEFAULT_THEME, LIGHT_MODE, SYSTEM_MODE, WALLPAPER_BANNER, WALLPAPER_NONE, WALLPAPER_OVERLAY, } from "@constants/constants"; import type { LIGHT_DARK_MODE, WALLPAPER_MODE } from "@/types/config"; import { backgroundWallpaper, expressiveCodeConfig, siteConfig, } from "../config"; import { isHomePage as checkIsHomePage } from "./layout-utils"; // Declare global functions declare global { interface Window { initSemifullScrollDetection?: () => void; semifullScrollHandler?: () => void; } } export function getDefaultHue(): number { const fallback = "250"; // 检查是否在浏览器环境中 if (typeof document === "undefined") { return Number.parseInt(fallback, 10); } const configCarrier = document.getElementById("config-carrier"); return Number.parseInt(configCarrier?.dataset.hue || fallback, 10); } export function getDefaultTheme(): LIGHT_DARK_MODE { // 如果配置文件中设置了 defaultMode,使用配置的值 // 否则使用 DEFAULT_THEME(向后兼容) return siteConfig.themeColor.defaultMode ?? DEFAULT_THEME; } // 获取系统主题 export function getSystemTheme(): LIGHT_DARK_MODE { if (typeof window === "undefined") { return LIGHT_MODE; } return window.matchMedia("(prefers-color-scheme: dark)").matches ? DARK_MODE : LIGHT_MODE; } // 解析主题(如果是system模式,则获取系统主题) export function resolveTheme(theme: LIGHT_DARK_MODE): LIGHT_DARK_MODE { if (theme === SYSTEM_MODE) { return getSystemTheme(); } return theme; } export function getHue(): number { // 先检查全局对象 if (typeof window === "undefined" || !window.localStorage) { return getDefaultHue(); } const stored = localStorage.getItem("hue"); return stored ? Number.parseInt(stored, 10) : getDefaultHue(); } export function setHue(hue: number): void { // 先检查是否在浏览器环境 if ( typeof window === "undefined" || !window.localStorage || typeof document === "undefined" ) { return; } localStorage.setItem("hue", String(hue)); const r = document.querySelector(":root") as HTMLElement; if (!r) { return; } r.style.setProperty("--hue", String(hue)); } export function applyThemeToDocument(theme: LIGHT_DARK_MODE) { // 检查是否在浏览器环境中 if (typeof document === "undefined") { return; } // 解析主题 const resolvedTheme = resolveTheme(theme); // 获取当前主题状态的完整信息 const currentIsDark = document.documentElement.classList.contains("dark"); const currentTheme = document.documentElement.getAttribute("data-theme"); // 计算目标主题状态 let targetIsDark = false; // 初始化默认值 switch (resolvedTheme) { case LIGHT_MODE: targetIsDark = false; break; case DARK_MODE: targetIsDark = true; break; default: // 处理默认情况,使用当前主题状态 targetIsDark = currentIsDark; break; } // 检测是否真的需要主题切换: // 1. dark类状态是否改变 // 2. expressiveCode主题是否需要更新 const needsThemeChange = currentIsDark !== targetIsDark; const expectedTheme = targetIsDark ? expressiveCodeConfig.darkTheme : expressiveCodeConfig.lightTheme; const needsCodeThemeUpdate = currentTheme !== expectedTheme; // 如果既不需要主题切换也不需要代码主题更新,直接返回 if (!needsThemeChange && !needsCodeThemeUpdate) { return; } // 批量 DOM 操作,减少重绘 if (needsThemeChange) { // 添加过渡保护类(但会导致大量重绘,所以使用更轻量的方式) // document.documentElement.classList.add("is-theme-transitioning"); // 直接切换主题,利用 CSS 变量的特性让浏览器优化过渡 if (targetIsDark) { document.documentElement.classList.add("dark"); } else { document.documentElement.classList.remove("dark"); } } // Set the theme for Expressive Code based on current mode if (needsCodeThemeUpdate) { document.documentElement.setAttribute("data-theme", expectedTheme); } } // 系统主题监听器引用 let systemThemeListener: | ((e: MediaQueryListEvent | MediaQueryList) => void) | null = null; export function setTheme(theme: LIGHT_DARK_MODE): void { // 检查是否在浏览器环境中 if ( typeof localStorage === "undefined" || typeof localStorage.setItem !== "function" ) { return; } // 先应用主题 applyThemeToDocument(theme); // 保存到localStorage localStorage.setItem("theme", theme); // 如果切换到 system 模式,需要监听系统主题变化 if (theme === SYSTEM_MODE) { setupSystemThemeListener(); } else { // 如果切换其他模式,移除系统主题监听 cleanupSystemThemeListener(); } } // 设置系统主题监听器 export function setupSystemThemeListener() { // 先清理之前的监听器 cleanupSystemThemeListener(); if (typeof window === "undefined") { return; } const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)"); // 处理系统主题变化的回调 const handleSystemThemeChange = (e: MediaQueryListEvent | MediaQueryList) => { const isDark = e.matches; const currentIsDark = document.documentElement.classList.contains("dark"); // 如果主题状态没有变化,直接返回 if (currentIsDark === isDark) { return; } // 直接应用系统主题,不使用过渡保护类以避免大量重绘 if (isDark) { document.documentElement.classList.add("dark"); } else { document.documentElement.classList.remove("dark"); } // Set the theme for Expressive Code const expressiveTheme = isDark ? expressiveCodeConfig.darkTheme : expressiveCodeConfig.lightTheme; document.documentElement.setAttribute("data-theme", expressiveTheme); // 触发自定义事件通知其他组件(仅在真正切换时触发) window.dispatchEvent(new CustomEvent("theme-change")); }; // 立即调用一次以设置初始状态 handleSystemThemeChange(mediaQuery); // 监听系统主题变化(现代浏览器) if (mediaQuery.addEventListener) { mediaQuery.addEventListener("change", handleSystemThemeChange); } else { // 兼容旧浏览器 mediaQuery.addListener(handleSystemThemeChange); } systemThemeListener = handleSystemThemeChange; } // 清理系统主题监听器 function cleanupSystemThemeListener() { if (typeof window === "undefined" || !systemThemeListener) { return; } const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)"); if (mediaQuery.removeEventListener) { mediaQuery.removeEventListener("change", systemThemeListener); } else { // 兼容旧浏览器 mediaQuery.removeListener(systemThemeListener); } systemThemeListener = null; } export function getStoredTheme(): LIGHT_DARK_MODE { // 检查是否在浏览器环境中 if ( typeof localStorage === "undefined" || typeof localStorage.getItem !== "function" ) { return getDefaultTheme(); } return ( (localStorage.getItem("theme") as LIGHT_DARK_MODE) || getDefaultTheme() ); } // 初始化主题监听器(用于页面加载后) export function initThemeListener() { if ( typeof localStorage === "undefined" || typeof localStorage.getItem !== "function" ) { return; } const theme = getStoredTheme(); // 如果主题是 system 模式,需要监听系统主题变化 if (theme === SYSTEM_MODE) { setupSystemThemeListener(); } } // Wallpaper mode functions export function applyWallpaperModeToDocument(mode: WALLPAPER_MODE) { // 检查是否允许切换壁纸模式 const isSwitchable = backgroundWallpaper.switchable ?? true; if (!isSwitchable) { // 如果不允许切换,直接返回,不执行任何操作 return; } // 获取当前的壁纸模式 const currentMode = (document.documentElement.getAttribute( "data-wallpaper-mode", ) as WALLPAPER_MODE) || backgroundWallpaper.mode; // 如果模式没有变化,直接返回 if (currentMode === mode) { // 即使是相同模式,也要确保UI状态正确 ensureWallpaperState(mode); return; } // 添加过渡保护类 document.documentElement.classList.add("is-wallpaper-transitioning"); // 更新数据属性 document.documentElement.setAttribute("data-wallpaper-mode", mode); // 使用 requestAnimationFrame 确保在下一帧执行,避免闪屏 requestAnimationFrame(() => { const body = document.body; // 移除所有壁纸相关的CSS类 body.classList.remove("enable-banner", "wallpaper-transparent"); // 根据模式添加相应的CSS类 switch (mode) { case WALLPAPER_BANNER: body.classList.add("enable-banner"); showBannerMode(); break; case WALLPAPER_OVERLAY: body.classList.add("wallpaper-transparent"); showOverlayMode(); break; case WALLPAPER_NONE: hideAllWallpapers(); break; default: hideAllWallpapers(); break; } // 更新导航栏透明模式 updateNavbarTransparency(mode); // 在下一帧移除过渡保护类 requestAnimationFrame(() => { document.documentElement.classList.remove("is-wallpaper-transitioning"); }); }); } // 确保壁纸状态正确 function ensureWallpaperState(mode: WALLPAPER_MODE) { const body = document.body; // 移除所有壁纸相关的CSS类 body.classList.remove("enable-banner", "wallpaper-transparent"); // 根据模式添加相应的CSS类 switch (mode) { case WALLPAPER_BANNER: body.classList.add("enable-banner"); showBannerMode(); break; case WALLPAPER_OVERLAY: body.classList.add("wallpaper-transparent"); showOverlayMode(); break; case WALLPAPER_NONE: hideAllWallpapers(); break; } // 更新导航栏透明模式 updateNavbarTransparency(mode); } function showBannerMode() { // 隐藏全屏壁纸(通过CSS类和display控制) const overlayContainer = document.querySelector( "[data-overlay-wallpaper]", ) as HTMLElement; if (overlayContainer) { overlayContainer.style.display = "none"; overlayContainer.classList.add("hidden"); overlayContainer.classList.add("opacity-0"); overlayContainer.classList.remove("opacity-100"); } // 显示banner壁纸(通过CSS类和display控制) const bannerWrapper = document.getElementById("banner-wrapper"); if (bannerWrapper) { // 检查当前是否为首页 const isHomePage = checkIsHomePage(window.location.pathname); const isMobile = window.innerWidth < 1024; // 移动端非首页时,不显示banner;桌面端始终显示 if (isMobile && !isHomePage) { bannerWrapper.style.display = "none"; bannerWrapper.classList.add("mobile-hide-banner"); } else { // 首页或桌面端:先设置display,然后使用requestAnimationFrame确保渲染 bannerWrapper.style.display = "block"; bannerWrapper.style.setProperty("display", "block", "important"); requestAnimationFrame(() => { bannerWrapper.classList.remove("hidden"); bannerWrapper.classList.remove("opacity-0"); bannerWrapper.classList.add("opacity-100"); bannerWrapper.classList.remove("mobile-hide-banner"); }); } } // 显示横幅图片来源文本 const creditDesktop = document.getElementById("banner-credit-desktop"); const creditMobile = document.getElementById("banner-credit-mobile"); if (creditDesktop) creditDesktop.style.display = ""; if (creditMobile) creditMobile.style.display = ""; // 显示横幅首页文本(如果启用且是首页) const bannerTextOverlay = document.querySelector(".banner-text-overlay"); if (bannerTextOverlay) { // 检查是否启用 homeText const homeTextEnabled = backgroundWallpaper.banner?.homeText?.enable; // 检查当前是否为首页 const isHomePage = checkIsHomePage(window.location.pathname); // 只有在启用且在首页时才显示 if (homeTextEnabled && isHomePage) { bannerTextOverlay.classList.remove("hidden"); } else { bannerTextOverlay.classList.add("hidden"); } } // 调整主内容位置 adjustMainContentPosition("banner"); // 处理移动端非首页主内容区域位置 const mainContentWrapper = document.querySelector(".absolute.w-full.z-30"); if (mainContentWrapper) { const isHomePage = checkIsHomePage(window.location.pathname); const isMobile = window.innerWidth < 1024; // 只在移动端非首页时调整主内容位置 if (isMobile && !isHomePage) { mainContentWrapper.classList.add("mobile-main-no-banner"); } else { mainContentWrapper.classList.remove("mobile-main-no-banner"); } } // 移除透明效果(横幅模式不使用半透明) adjustMainContentTransparency(false); // 调整导航栏透明度 const navbar = document.getElementById("navbar"); if (navbar) { // 获取导航栏透明模式配置(banner模式) const transparentMode = backgroundWallpaper.banner?.navbar?.transparentMode || "semi"; navbar.setAttribute("data-transparent-mode", transparentMode); // 重新初始化半透明模式滚动检测(如果需要) if ( transparentMode === "semifull" && typeof window.initSemifullScrollDetection === "function" ) { window.initSemifullScrollDetection(); } } } function showOverlayMode() { // 显示全屏壁纸(通过CSS类和display控制) const overlayContainer = document.querySelector( "[data-overlay-wallpaper]", ) as HTMLElement; if (overlayContainer) { // 先设置display,然后使用requestAnimationFrame确保渲染 overlayContainer.style.display = "block"; overlayContainer.style.setProperty("display", "block", "important"); requestAnimationFrame(() => { overlayContainer.classList.remove("hidden"); overlayContainer.classList.remove("opacity-0"); overlayContainer.classList.add("opacity-100"); }); } // 隐藏banner壁纸(通过CSS类和display控制) const bannerWrapper = document.getElementById("banner-wrapper"); if (bannerWrapper) { bannerWrapper.style.display = "none"; bannerWrapper.classList.add("hidden"); bannerWrapper.classList.add("opacity-0"); bannerWrapper.classList.remove("opacity-100"); } // 隐藏横幅图片来源文本 const creditDesktop = document.getElementById("banner-credit-desktop"); const creditMobile = document.getElementById("banner-credit-mobile"); if (creditDesktop) creditDesktop.style.display = "none"; if (creditMobile) creditMobile.style.display = "none"; // 隐藏横幅首页文本 const bannerTextOverlay = document.querySelector(".banner-text-overlay"); if (bannerTextOverlay) { bannerTextOverlay.classList.add("hidden"); } // 调整主内容透明度 adjustMainContentTransparency(true); // 调整布局为紧凑模式 adjustMainContentPosition("overlay"); } function hideAllWallpapers() { // 隐藏所有壁纸(通过CSS类和display控制) const bannerWrapper = document.getElementById("banner-wrapper"); const overlayContainer = document.querySelector( "[data-overlay-wallpaper]", ) as HTMLElement; if (bannerWrapper) { bannerWrapper.style.display = "none"; bannerWrapper.classList.add("hidden"); bannerWrapper.classList.add("opacity-0"); } if (overlayContainer) { overlayContainer.style.display = "none"; overlayContainer.classList.add("hidden"); overlayContainer.classList.add("opacity-0"); overlayContainer.classList.remove("opacity-100"); } // 隐藏横幅图片来源文本 const creditDesktop = document.getElementById("banner-credit-desktop"); const creditMobile = document.getElementById("banner-credit-mobile"); if (creditDesktop) creditDesktop.style.display = "none"; if (creditMobile) creditMobile.style.display = "none"; // 隐藏横幅首页文本 const bannerTextOverlay = document.querySelector(".banner-text-overlay"); if (bannerTextOverlay) { bannerTextOverlay.classList.add("hidden"); } // 调整主内容位置和透明度 adjustMainContentPosition("none"); adjustMainContentTransparency(false); } function updateNavbarTransparency(mode: WALLPAPER_MODE) { const navbar = document.getElementById("navbar"); if (!navbar) return; let transparentMode: string; let enableBlur: boolean; // 根据当前壁纸模式设置导航栏透明模式和模糊效果 if (mode === WALLPAPER_OVERLAY) { // 全屏壁纸模式 transparentMode = "none"; enableBlur = false; } else if (mode === WALLPAPER_NONE) { // 纯色背景模式 transparentMode = "none"; enableBlur = false; } else { // Banner模式:使用配置的透明模式和模糊效果 transparentMode = backgroundWallpaper.banner?.navbar?.transparentMode || "semi"; enableBlur = backgroundWallpaper.banner?.navbar?.enableBlur ?? true; } // 更新导航栏的透明模式属性 navbar.setAttribute("data-transparent-mode", transparentMode); navbar.setAttribute("data-enable-blur", String(enableBlur)); // 移除现有的透明模式类 navbar.classList.remove( "navbar-transparent-semi", "navbar-transparent-full", "navbar-transparent-semifull", ); // 移除scrolled类 navbar.classList.remove("scrolled"); // 滚动检测功能 if ( transparentMode === "semifull" && mode === WALLPAPER_BANNER && typeof window.initSemifullScrollDetection === "function" ) { // 仅在Banner模式的semifull下启用滚动检测 window.initSemifullScrollDetection(); } else if (window.semifullScrollHandler) { // 移除滚动监听器 window.removeEventListener("scroll", window.semifullScrollHandler); delete window.semifullScrollHandler; } } function adjustMainContentPosition( mode: WALLPAPER_MODE | "banner" | "none" | "overlay", ) { const mainContent = document.querySelector( ".absolute.w-full.z-30", ) as HTMLElement; if (!mainContent) return; // 移除现有的位置类 mainContent.classList.remove("mobile-main-no-banner", "no-banner-layout"); switch (mode) { case "banner": // Banner模式:主内容在banner下方 mainContent.style.top = "calc(var(--banner-height) - 3rem)"; break; case "overlay": // Overlay模式:使用紧凑布局,主内容从导航栏下方开始 mainContent.classList.add("no-banner-layout"); mainContent.style.top = "5.5rem"; break; case "none": // 无壁纸模式:主内容从导航栏下方开始 mainContent.classList.add("no-banner-layout"); mainContent.style.top = "5.5rem"; break; default: mainContent.style.top = "5.5rem"; break; } } function adjustMainContentTransparency(enable: boolean) { const mainContent = document.querySelector(".absolute.w-full.z-30"); const body = document.body; if (!mainContent || !body) return; if (enable) { mainContent.classList.add("wallpaper-transparent"); body.classList.add("wallpaper-transparent"); } else { mainContent.classList.remove("wallpaper-transparent"); body.classList.remove("wallpaper-transparent"); } } export function setWallpaperMode(mode: WALLPAPER_MODE): void { // 检查是否在浏览器环境中 if ( typeof localStorage === "undefined" || typeof localStorage.setItem !== "function" ) { return; } localStorage.setItem("wallpaperMode", mode); applyWallpaperModeToDocument(mode); } export function initWallpaperMode(): void { const storedMode = getStoredWallpaperMode(); applyWallpaperModeToDocument(storedMode); } export function getStoredWallpaperMode(): WALLPAPER_MODE { // 检查是否在浏览器环境中 if ( typeof localStorage === "undefined" || typeof localStorage.getItem !== "function" ) { return backgroundWallpaper.mode; } return ( (localStorage.getItem("wallpaperMode") as WALLPAPER_MODE) || backgroundWallpaper.mode ); }