hexo + github 블로그 다크모드 깜빡임 해결

렌더링이 시작된 후에 defer속성으로 받아온 스크립트가 실행되다보니 화면 전환중에 흰화면으로 잠시 전환되는 현상이 발생하였다.

다음과 같은 방법으로 수정하였다.

body의 기본 배경 색 없애기

먼저 7번째 줄에 있는 body의 background-color를 none으로 수정하였다.

\theme\icarus\css\include\style\base.styl

1
$body-background-color ?= none;

기존 js파일을 두 개로 나누기

기존 js파일의 경우 init 함수에서 버튼 설정과 다크모드 로드를 동시에 해주었는데,
다크모드 로드는 렌더링 전에 실행되어야 하고 버튼 설정은 렌더링 후에(최소 DOM트리 생성 이후) 실행되어야 한다.
때문에 파일을 두 개로 나누었다.

darkmode.js엔 chinsun9님의 게시글을 읽고 matchMedia를 읽어서 실행하는 부분을 추가하였다.
(너무 친절하게 답변해주셨다..!)

localstorage와 matchMedia를 이용하여 다크모드를 로드하는 함수이다.
\themes\icarus\source\js\darkmode.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function init() {
const darkScheme = window.matchMedia("(prefers-color-scheme:dark)");
const theme = localStorage.getItem("theme");
if (theme === "dark") {
document.body.classList.add("dark");
return;
}
if (theme === "light") {
return;
}

if (darkScheme) document.body.classList.add("dark");

localStorage.setItem("theme", darkScheme ? "dark" : "light");
}

버튼 관련 설정을 하는 js 파일이다.
hexo\themes\icarus\source\js\darkmodebutton.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
const darkModeButton = document.querySelector("#changeModeButton");
const darkScheme = window.matchMedia("(prefers-color-scheme:dark)");

darkScheme.addEventListener("change", (e) => {
console.log("hidark");
const newColorScheme = e.matches ? "dark" : "light";
localStorage.setItem("theme", newColorScheme);

if (newColorScheme === "dark") {
document.body.classList.add("dark");
btn.innerText = "🌙";
return;
}
document.body.classList.remove("dark");
btn.innerText = "🌞";
});

function toggleTheme() {
console.log("hibutton");
document.body.classList.toggle("dark");
darkModeButton.innerText = document.body.classList.contains("dark")
? "🌙"
: "🌞";
localStorage.setItem(
"theme",
document.body.classList.contains("dark") ? "dark" : "light"
);
}

function initButton() {
console.log("initButton");
const data = localStorage.getItem("theme");
if (data == "dark") {
darkModeButton.innerText = "🌙";
return;
}
darkModeButton.innerText = "🌞";
}

initButton();
darkModeButton.addEventListener("click", toggleTheme);

작성한 js파일의 위치 지정해주기

  1. darkmodebutton.js
    버튼은 렌더링후에(최소 DOM트리 생성 이후) 실행되어야 하므로 아래부분에 추가해주었다.
    \themes\icarus\layout\common\scripts.jsx
    56번쨰 줄에 darkmodebutton.js추가

    1
    2
    <script src={url_for("/js/main.js")} defer></script>
    <script src={url_for("/js/darkmodebutton.js")} defer></script>
  2. darkmode.js
    body 최상단에서 실행하기위해 head태그에서 js파일을 로드했다.
    적다보니 preload속성을 추가해도 좋을 것 같다.

    hexo\themes\icarus\layout\common\head.jsx

    1
    <script src={url_for("/js/darkmode.js")}></script>
  3. darkmode.js의 init 함수
    다크모드를 로드하는 건 렌더링 이전에 실행되어야한다.
    때문에 body태그 최상단에 rendering block 요소로 defer나 async 속성을 지정하지 않고 넣어주었다.
    hexo\themes\icarus\layout\layout.jsx

    1
    2
    3
    4
    <html lang={language ? language.substr(0, 2) : ""}>
    <Head site={site} config={config} helper={helper} page={page} />
    <body class={`is-${columnCount}-column`}>
    <script>init();</script>

ref

[A1] 웹뷰에서 다크모드 상속받기: 일관성있는 사용자 경험을 위하여
blog fix 다크 모드 화면 깜박임 제거

댓글