You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用JavaScript模块切换主题时默认样式闪烁问题求助

解决主题切换时的页面闪烁问题

问题描述

我实现了一个基于localStorage的主题切换功能,通过给<html>标签添加lightMode/darkMode类来切换CSS变量,但刷新页面时,默认的浅色主题会先闪烁一下,才会切换到用户设置的主题。

我的CSS代码:

:root.lightMode {
    --text: #030202;
    --background: #f0efef;
}

:root.darkMode {
    --text: #f9f4f4;
    --background: #100f0f;
}

:root {
    --text: #030202;
    --background: #f0efef;
}

原JavaScript模块代码:

export let colorMode = "auto", localStorageAvailable = false;

export function updateColorMode(useLocalStorage, colorModePreference) {
   /*useLocalStorage = true | false;
   colorModePreference = null | "auto" | "light" | "dark";*/

   //Removes color mode classes from root then adds the desired one:
   function switchRootClass(className) {
       const rootClassList = document.documentElement.classList;

       if (rootClassList.contains("lightMode")) {
           rootClassList.remove("lightMode");
       };
       if (rootClassList.contains("darkMode")) {
           rootClassList.remove("darkMode");
       };

       rootClassList.add(className);
   };
   
   //Check if localStorage is available:
   function checkLocalStorage() {
       try {
           localStorage.setItem("test", "test");
           localStorage.removeItem("test");
           return true;
       } catch (error) {
           return false;
       }
   };

   if (useLocalStorage === true && checkLocalStorage()) {
       localStorageAvailable = true;
       //Check if localStorage item exist:
       if (!localStorage.getItem("colorMode")) {
           localStorage.setItem("colorMode", "auto");
       };
   } else {
       localStorageAvailable = false;
   };

   //Update colorMode to localStorage item or custom value: 
   if (colorModePreference === "auto" || colorModePreference === "light" || colorModePreference === "dark") {
       localStorageAvailable === true ? localStorage.setItem("colorMode", colorModePreference) : null;
       colorMode = colorModePreference;
   } else {
       localStorageAvailable === true ? colorMode = localStorage.getItem("colorMode") : null;
   };

   switch (colorMode) {
       case "auto":            
           window.matchMedia("(prefers-color-scheme: dark)").matches ?
               switchRootClass("darkMode")
               :
               switchRootClass("lightMode");

           //AddEventListeners to window so that it also updates when browser's color mode is altered:
           const darkModePreference = window.matchMedia("(prefers-color-scheme: dark)");
           const lightModePreference = window.matchMedia("(prefers-color-scheme: light)");

           darkModePreference.addEventListener("change", function darkModeListener(e){e.matches && colorMode === "auto" ? updateColorMode(false, "auto") : null});
           lightModePreference.addEventListener("change", function lightModeListener(e){e.matches && colorMode === "auto" ? updateColorMode(false, "auto") : null});
           break;
       case "light":
           switchRootClass("lightMode");
           break;
       case "dark":
           switchRootClass("darkMode");
           break;
       default:
           break;
   };

}

原主JS文件代码:

import { updateColorMode } from './colorModeModule.js';

document.onreadystatechange = () => {
    if (document.readyState === 'complete'){
        updateColorMode(true, null);
    }
};

问题根源

你的脚本是等到document.readyState === 'complete'才执行,这时候页面已经完成渲染,默认的:root样式已经显示出来,之后JS才修改<html>的类,导致视觉闪烁。

解决方案

把主题判断的核心逻辑内联到HTML的<head>标签中,在CSS加载前就设置好<html>的类,避免默认样式渲染。

  1. 修改HTML结构,在<head>里添加内联脚本(放在CSS之前):
<head>
  <!-- 内联脚本优先执行,提前设置主题类 -->
  <script>
    // 检查localStorage可用性
    function checkLocalStorage() {
      try {
        localStorage.setItem("test", "test");
        localStorage.removeItem("test");
        return true;
      } catch (e) {
        return false;
      }
    }

    const root = document.documentElement;
    let colorMode = "auto";

    if (checkLocalStorage()) {
      colorMode = localStorage.getItem("colorMode") || "auto";
    }

    // 直接设置主题类
    if (colorMode === "dark") {
      root.classList.add("darkMode");
    } else if (colorMode === "light") {
      root.classList.add("lightMode");
    } else {
      // 自动模式根据系统偏好设置
      window.matchMedia("(prefers-color-scheme: dark)").matches 
        ? root.classList.add("darkMode") 
        : root.classList.add("lightMode");
    }
  </script>

  <!-- 然后加载你的CSS -->
  <link rel="stylesheet" href="your-stylesheet.css">
</head>
  1. 调整原有的JS模块和主JS:
    • 内联脚本已经完成了初始类设置,后续模块只需要处理主题切换的逻辑和系统偏好变化的监听。
    • 主JS不需要等到complete状态,改为在DOM加载后初始化事件监听:
import { updateColorMode } from './colorModeModule.js';

// DOM加载后就初始化,不用等complete
document.addEventListener('DOMContentLoaded', () => {
  updateColorMode(true, null);
});
  1. 优化原模块中的switchRootClass函数,避免重复判断:
function switchRootClass(className) {
  const rootClassList = document.documentElement.classList;
  // 直接替换类,更简洁
  rootClassList.remove("lightMode", "darkMode");
  rootClassList.add(className);
}

效果说明

内联脚本在页面渲染前就执行,提前给<html>设置了正确的主题类,CSS加载后会直接应用对应的主题变量,完全避免默认样式的闪烁问题。

内容的提问来源于stack exchange,提问作者Douglas Florindo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 13:05:56