如何让静态网站的暗色主题换页不闪烁 - 自写主题
2021年8月27日 - 955 字

问题

我写的这个 Hugo 主题之前一直有个毛病. 它默认是亮色主题,但如果调到暗色主题1,那么加载新页面时网站会先渲染亮色主题,然后再自动切换到暗色主题,观感上就是它闪烁了一下. 我本来以为这是静态网站的通病,直到我看到别人的主题就没这个毛病,比如 PaperMod. 所以这个问题是一定有解决方案的.

我先查了一下,Cupper 主题的维护者写过一篇文章:Fix the White Flash on Page Load When Using a Dark Theme on a Static Site. 我试了 ta 的方案,Chrome 浏览器下此方案确实解决了问题,但是 Firefox 下问题依然存在:Firefox 会把 visibility: hidden; opacity: 0; 渲染成白色,等 DOMContentLoaded 事件发生后,showContent() 函数再把内容展示出来,也就是说它还是会闪烁,只不过之前是闪一下亮色主题,现在是闪一下纯白页,实际观感同样糟糕.

Firefox 与 Chrome 的差异我觉得更像是 Chrome 的开发者使用了魔法,来帮助其用户获得更好的使用体验,同时这也掩盖了真正的问题,迷惑了使用 Chrome 浏览器的 Hugo 主题开发者.

找到问题的根源

核心的思路仍然是理解浏览器渲染页面的流程. 简单来说:

  • 你请求了一个网页;
  • 浏览器先把整个 HTML 下载下来,存成 DOM,然后从上到下依次解析、渲染页面;
  • 遇到外链的 CSS 或 JavaScript 就停止解析,先去把外链的文件下载回来装载上再继续;
  • 遇到 <style></style> 或刚刚装载了外部的 CSS 就把样式表存成 CSSOM 并重新渲染有影响的部分;
  • 遇到 <script></script> 或刚刚装载了外部的 JavaScript 就停止解析并运行脚本,运行完脚本后再继续解析、渲染;
  • 以此类推,直到完成.

而发生闪烁问题的原因就在于我把判断颜色的 JavaScript 脚本放在了 <body> 的最后,像这样:

1
2
3
4
5
6
7
8
<!DOCTYPE html>
<html>
    <body>
        <!-- lots of stuff…… -->

        <script src="changeTheme.js"></script>
    </body>
</html>

解决

问题的解决其实也简单,核心有二:

    1. 把读取主题的脚本放进 <head> 里,这样刚刚加载还没开始渲染时就能够确定用户当前选择的主题;
    1. 把更改主题的容器提升成整个 <html>,即:从原来的
1
2
3
4
<html>
    <body class="theme-container">
    </body>
</html>

变成现在的

1
2
3
4
<html class="theme-container">
    <body>
    </body>
</html>

这是因为 <body> 在这个时候还没有被渲染,<head> 里的脚本能操作的对象只有 <html>.


至此问题完全解决,完整的代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!DOCTYPE html>
<html class="theme-container">
    <head>
        <!-- lots of stuff…… -->

        <script>
            const themeContainer = document.querySelector(".theme-container");
            var theme = localStorage.getItem("theme");
            if (theme == "dark") {
                themeContainer.classList.add("dark");
            } else if (theme == "light") {
                themeContainer.classList.remove("dark");
            }
        </script>
    </head>

    <body>
        <!-- lots of stuff…… -->

        <script src="others.js"></script>
    </body>
</html>

  1. 本主题中暗色模式的实现方法为 CSS 变量,详见这篇教程↩︎

友情链接(该栏目下的其他文章)

  1. 第 1 期:Hugo 静态博客建站记 - 自写主题
  2. 第 2 期:博客记录书影音——Hugo 的 Data Templates - 自写主题
  3. 第 3 期:如何让静态网站的暗色主题换页不闪烁 - 自写主题(就是这篇!)
  4. 第 4 期:Hugo 如何让图片在暗色主题下反色 - 自写主题