hexo+icarus 다크모드 깜빡임 해결

예전에 메모해놓고 이제 게시글로 적는다!

기본 배경 색 없애기

먼저 css레벨에서 적용되는 css를 초기화해야한다.
브라우저는 기본적으로 css가 없을 시 페이지 이동할 때 이전 페이지의 배경색을 그대로 유지한다.

\theme\icarus\css\include\style\base.styl
7줄 기본 배경색 none으로 수정하기

1
$body-background-color ?= none;

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

로컬스토리지를 읽고 다크모드를 적용하는 함수와 html을 전부 로드한 후 버튼의 innerText를 수정하는 함수로 나누었다.

다크모드를 적용하는 함수는 render blocking이 이루어져야하고 버튼의 innerText의 수정은 HTML이 모두 로드 후 이루어져야하기 때문이다.

따라서 버튼을 수정하는 함수는 defer로 불러오고 다크모드를 확인하고 적용하는 함수는 속성 없는 script태그로 불러온다.

\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");
}

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);

\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>

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

js를 로드하고 실행하는 동안 렌더링을 막아야하기 때문에 script에 속성을 지정하지 않아야한다.
적다보니 preload속성을 추가해도 좋을 것 같다.

1
<script src={url_for("/js/darkmode.js")}></script>

고의로 render blocking 만들기

새로고침이 되어서 다크모드가 적용되기 전 까지 렌더링이 이루어지면 안된다.

body의 최상단에 darkmode.js에서 정의한 init함수를 호출해준다.
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

https://www.youtube.com/watch?v=ElsZ-v4Ow08

댓글