hexo+ icarus 다크모드 구현하기

나는 다크모드를 아주 애용한다.

다크모드를 적용하며 유익한 경험이여서 글로 작성해두려고 한다.

hexo + icarus 작동원리

우선 hexo + icarus의 원리부터 알아야한다.

아래에 작성할 모든 디렉터리는 hexo루트 폴더 기준이다.

루드 디렉토리의 내부 파일을 수정한다.

그 후 정적파일인 public 폴더를 생성하고 배포하면 사이트에 반영되는 구조이다.

1
hexo generate --deploy

커스텀 디자인을 하기 위해선 이 ./themes 폴더를 수정해야한다.

버튼 만들기

./themes/icarus/layout/common 폴더엔 icarus의 여러 요소들의 jsx파일이 작성되어있다.

hexo\themes\icarus\layout\common\navbar.jsx파일에서 검색버튼 옆에 버튼을 만들었다.

1
<button type="button" id="changeModeButton" class="navbar-item" />

버튼을 커스텀하기

./themes/icarus/include/style 폴더엔 여러 css들이 작성되어있다.

확장자가 styl인데 기존에 작성된 파일을 보니 들여쓰기로 depth를 관리하는 듯 했다.

그리고 이 파일들은 ./themes/source/css/style.styl에서 모두 import되고 이를 빌드하여 ./public/css에 하나의 css파일을 생성한다.

./themes/icarus/include/style/navbar.styl 파일에 다음과 같은 내용을 추가하였다.

1
2
3
4
5
6
#changeModeButton {
background: none;
border: none;
cursor: pointer;
font-size: 2rem;
}

버튼에 로직 추가하기

./themes/source/js 폴더엔 여러 js파일들이 있다.

이는 ./themes/icarus/layout/common/scripts.js에서 한 번에 모아서 ./public/index.js에 작성된다.

여기에 다크모드 버튼 관련 파일을 작성하였다.

페이지에 접속 시 로컬스토리지를 확인하고 darkMode가 true일경우 html의 body에 darkMode 태그를 추가하고 버튼의 모양을 달 모양으로 해주었다.

값이 존재하지 않을경우 버튼의 모양을 해 모양으로 해주었다. 기본 값은 다크모드의 미적용이기 때문에 따로 처리를 해주진 않았다.

그리고 버튼에 이벤트를 등록해주었다.

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
const darkModeButton = document.querySelector("#changeModeButton");

function init() {
loadDarkModeData();
darkModeButton.addEventListener("click", clickEvent);
}

const loadDarkModeData = () => {
const data = localStorage.getItem("darkMode");
const body = document.querySelector("body");
if (data === "true") {
darkModeButton.innerText = "🌙";
body.classList.add("darkMode");
} else {
darkModeButton.innerText = "🌞";
}
};

const setDarkMode = () => {
const body = document.querySelector("body");
if (body.classList.contains("darkMode")) {
darkModeButton.innerText = "🌞";
body.classList.remove("darkMode");
localStorage.setItem("darkMode", false);
} else {
darkModeButton.innerText = "🌙";
body.classList.add("darkMode");
localStorage.setItem("darkMode", true);
}
};

const clickEvent = () => {
setDarkMode();
console.log(localStorage.getItem("darkMode"));
};

init();

그리고 ./themes/icarus/layout/common/scripts.js에 아래 내용을 추가해주었다.

정확히 어떤 구조인지는 모르지만 main.js가 이런 식으로 작성되어 있길래 똑같이 적었다.

defer는 script로딩시 버튼이 없으면 안되기 때문에 추가해주었다.

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

다크모드 css 추가하기

다음으로 darkmode.styl 파일을 작성해주었다.

처음 보는 확장자명이기도 했고 ./themes/include/style에 작성할지 ./themes/source/css에 작성할지 고민되었다.

고민끝에 include폴더는 컴포넌트마다의 css를 작성하는 느낌이 강해서 ./themes/source/css/darkmode.styl에 작성하였다.

css는 개발자 도구를 여러 하나하나 클래스명을 확인하며 적어주었다.

빌드과정에서 압축될 것이여서 하나씩 풀어 적었다.

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
body.darkMode
background-color: #171717;
color:#F9F9F9;
.navbar ,& .navbar-menu
background-color: #444444;
section,article,.card{
background-color: inherit;
color:inherit;
}
& .navbar-item
color:#F9F9F9;
&:hover{
color:#3273dc;
background-color:#393E46;
}
& .menu-list>li *{
color:rgba(255,255,255,0.9);
}

& .menu-list a:hover{
color:#3273dc;
background-color:#393E46;
}

& .menu-list a.is-active{
background:#141010;
}
& .is-active
color:#3273dc;
& article
& > * {
color:rgba(255,255,255,0.7);
};
& h1,h2,h3,h4,h5,h6{
color:rgba(255,255,255,0.9);
}
& .article-meta{
color:rgba(255,255,255,0.38);
}
& .card-content
& > * {
color:rgba(255,255,255,0.7);
};

& .title, .location{
color:rgba(255,255,255,0.9);
}

& .menu{
color:rgba(255,255,255,0.7);
}

& .level-start{
color:rgba(255,255,255,0.7);
}

& .menu-label{
color:rgba(255,255,255,0.9);
}

& .tag{
background:#444444;
color:rgba(255,255,255,0.7);
}
.searchbox-container
background:#444444;
.searchbox-input, .searchbox-close{
background:#444444;
color:rgb(255,255,255);
}
.footer{
background:#444444;
}
.hljs {
display: block;
overflow-x: auto;
padding: 0.5em;
color: #abb2bf;
background: #282c34;
}

.hljs-comment,
.hljs-quote {
color: #5c6370;
font-style: italic;
}

.hljs-doctag,
.hljs-keyword,
.hljs-formula {
color: #c678dd;
}

.hljs-section,
.hljs-name,
.hljs-selector-tag,
.hljs-deletion,
.hljs-subst {
color: #e06c75;
}

.hljs-literal {
color: #56b6c2;
}

.hljs-string,
.hljs-regexp,
.hljs-addition,
.hljs-attribute,
.hljs-meta-string {
color: #98c379;
}

.hljs-built_in,
.hljs-class .hljs-title {
color: #e6c07b;
}

.hljs-attr,
.hljs-variable,
.hljs-template-variable,
.hljs-type,
.hljs-selector-class,
.hljs-selector-attr,
.hljs-selector-pseudo,
.hljs-number {
color: #d19a66;
}

.hljs-symbol,
.hljs-bullet,
.hljs-link,
.hljs-meta,
.hljs-selector-id,
.hljs-title {
color: #61aeee;
}

.hljs-emphasis {
font-style: italic;
}

.hljs-strong {
font-weight: bold;
}

.hljs-link {
text-decoration: underline;
}


그리고 ./themes/source/css/style.styl에 import 해주었다.

1
@import './darkmode.styl'

정리

전체적인 로직은 다음과 같다.

  1. body.darkMode의 하위 태그들에 적용이 되도록 여러 css를 작성해둔다.
  2. 첫 접속시에 로컬스토리지에 darkMode 관련 값이 저장되어있는지 확인하고 알맞은 처리를 해준다. 새로고침시에 상태가 유지되게 하기 위함이다.
  3. 버튼 클릭시 다크모드가 적용되도록 이벤트를 추가해준다.(body태그에 darkMode 클래스를 toggle하도록)

완성본

다크모드가 적용될 경우 적용되지 않을경우의 버튼이 다르고 글자 색과 배경 색도 다르다.

다크모드 미적용 다크모드 적용

느낀 점

  1. styl 확장자 파일을 작성해보았다.
    • Stylus CSS파일이라고 하는데, 작성해보니 의외로 굉장히 편하였다.
  2. REACT가 아닌 jsx로 작성되어 이러한 파일들을 중앙에서 제어하여 빌드시 html을 만드는 과정에 대한 이해가 늘었다.
    • jsx문법을 이용하여 컴포넌트 단위로 파일을 작성하고 관리하는 것이 얼마나 가독성이 좋은지 다시 한 번 느꼈다.
  3. 역시 다크모드가 눈이 편하다
    • 다크모드가 최고다 ㅠ

아쉬운 점

  1. 구현을 하고 보니 코드블록이나 위젯에는 다크모드가 적용이 안 되었다.
    • 이것도 개발자 도구를 이용하여 css를 까보고 수정해야겠다.
  2. 모바일에서 레이아웃이 아쉽다.
    • 버튼을 추가하다보니 상단바에 스크롤바가 생겼다.
    • 이것도 수정해보아야겠다. 레이아웃에 손을 대거나 전체적으로 크기를 줄이는 방향으로 갈 것 같다.
    • 모바일
  3. 정신없이 작업하고 복기하며 메모하다보니 빠진 부분이 있을까봐 걱정된다.
    • 다음부턴 간략한 개요든지 사진을 틈틈히 찍어두어야겠다 ㅠ

+++ 수정

다크모드를 끄고 게시글에 접근시 다크모드가 적용되는 버그가 있었다.
로컬스토리지에 데이터가 string으로 저장되는데 if(data)의 형태로 확인해서 발생한 버그였다.

1
2
3
4
5
6
7
8
9
10
const loadDarkModeData = () => {
const data = localStorage.getItem("darkMode");
const body = document.querySelector("body");
if (data) {
darkModeButton.innerText = "🌙";
body.classList.add("darkMode");
} else {
darkModeButton.innerText = "🌞";
}
};

위 부분을 다음과 같이 변경하였다.

1
2
3
4
5
6
7
8
9
10
const loadDarkModeData = () => {
const data = localStorage.getItem("darkMode");
const body = document.querySelector("body");
if (data === "true") {
darkModeButton.innerText = "🌙";
body.classList.add("darkMode");
} else {
darkModeButton.innerText = "🌞";
}
};

댓글