티스토리 뷰
나만 없는 다크모드,,,
본 포스팅은 티스토리 #1을 기준으로 합니다.
velog나 notion 그리고 github.io를 이용하는 블로거분들의 페이지를 방문할 때가 있습니다. 그리고 이 분들과 저의 차이점, 그리고 부러운 점이 하나 있었습니다. 바로 다크모드 테마가 있다는 것이었습니다...
평소 tistory에서 제공하는 #1 스킨을 사용하는 저는 다크모드 그리고 다크모드와 라이트모드를 사용자 기호에 맞게 사용할 수 있는 토글도 없었습니다.
그래서 저의 티스토리 블로그 테마에 다크모드와 라이트모드 토글을 구현하였습니다.
FOCU 현상
문제
<body>
<div>
<!-- 블로그 컨텐츠 내용 -->
...
</div>
<!-- 테마 및 토글 관련 코드 -->
<script src="themeToggle.js"></script>
</body>
처음에 구현할 때 theme.js
와 themeToggle.js
의 내용을 모두 합하여 코드를 작성하고 <body>
태그 제일 하단에 <srcipt>
태그를 추가하고 테스트를 하였습니다.
그랬더니 lihgt 테마와 dark 테마에서 페이지를 로딩할 때 화면이 번쩍이는 현상을 발견했습니다. 알고보니 이 현상은 FOCU(Flash Of Unstyled Content)였습니다.
테마 및 토글 관련 제어 코드를 담은 <srcipt>
태그가 마지막에 실행되어서 화면이 반짝이는 것이었습니다.
해결
<body>
<!-- body 태그 생성 이후 블로그 컨텐츠 내용을 받기 전에 삽입 -->
<script src="theme.js"></script>
<div>
<!-- 블로그 컨텐츠 내용 -->
...
</div>
<script src="themeToggle.js"></script>
</body>
문제를 해결하기 위해 통합된 코드를 테마 관련 코드인 theme.js
, 테마 토글 관련 코드인 themeToggle.js
로 나누어 리펙토링하였습니다. 그리고 위와 같이 script
태그들을 삽입하였습니다.
브라우저 랜더링 과정은 간단하게 설명하면 다음과 같습니다.
<body>
태그가 생성되자마자 theme.js
가 포함된 스크립트를 삽입하여 html 파싱을 중지하고 제어권을 js엔진에 넘긴 후 js엔진이 파싱합니다. js엔진이 파싱을 마친 후 html 파싱은 다시 시작되고 모두 끝난 후 마지막에 themeToggle.js
가 포함된 스크립트 코드를 만나 js엔진이 파싱을 처리하게 됩니다.
위와 같은 과정으로 테마 관련 스크립트를 중간에 미리 처리하였기 때문에 화면 깜빡임 현상(FOCU)은 발생하지 않게 되었습니다.
구성
theme 파트
- themes.css
- theme.js
toggle 파트
- html
- theme-toggle.css
- themeToggle.js
크게 theme에 대한 코드와 toggle에 대한 코드로 구성하였습니다.
light, dark 테마에 대한 css 설정을 다룬 themes.css
, 사용자나 브라우저가 설정한 것에 의하여 light, dark 테마 중 하나를 적용시키는 theme.js
.
toggle을 구현하는 html 코드, toggle을 구현하는 html 코드를 꾸민 css인 theme-toggle.css
, 마지막으로 light, dark 테마에 따른 toggle의 반응을 나타낸 themeToggle.js
입니다.
themes.css
html[theme="dark"] {
--background-color-body: #0d1117;
--background-color-head-foot: #161b22;
--border-bottom-h2: 1px solid #21262d;
--color: #c9d1d9;
--color-accent: #58a6ff;
--blockquote-border-left: 5px solid #30363d !important;
--tag-background-color: #222;
--color-a: #8b949e;
--background-color-inline-code: rgba(110, 118, 129, 0.4);
}
html[theme="light"] {
--background-color-body: #fff;
--background-color-head-foot: #f6f8fa;
--border-bottom-h2: 1px solid #d8dee4;
--color: #24292f;
--color-accent: #0969da;
--blockquote-border-left: 5px solid #d0d7de !important;
--tag-background-color: #f0f0f0;
--color-a: #57606a;
--background-color-inline-code: rgba(175, 184, 193, 0.2);
}
/* utterance */
html[theme="dark"] .utterance-light {
display: none;
}
html[theme="light"] .utterance-dark {
display: none;
}
/* scrollbar */
html,
.skin_view .area_view pre code.hljs {
scrollbar-width: thin;
scrollbar-color: var(--color-a) var(--background-color-body);
}
.skin_view .area_view pre code.hljs::-webkit-scrollbar {
background-color: var(--background-color-body);
height: 6px;
}
body::-webkit-scrollbar {
background-color: var(--background-color-body);
width: 6px;
}
body::-webkit-scrollbar-thumb,
.skin_view .area_view pre code.hljs::-webkit-scrollbar-thumb {
background-color: var(--color-a);
border-radius: 6px;
}
html[theme="COLOR"]
html[theme="dark"] {
--background-color-body: #0d1117;
--background-color-head-foot: #161b22;
--border-bottom-h2: 1px solid #21262d;
--color: #c9d1d9;
--color-accent: #58a6ff;
--blockquote-border-left: 5px solid #30363d !important;
--tag-background-color: #222;
--color-a: #8b949e;
--background-color-inline-code: rgba(110, 118, 129, 0.4);
}
html[theme="light"] {
--background-color-body: #fff;
--background-color-head-foot: #f6f8fa;
--border-bottom-h2: 1px solid #d8dee4;
--color: #24292f;
--color-accent: #0969da;
--blockquote-border-left: 5px solid #d0d7de !important;
--tag-background-color: #f0f0f0;
--color-a: #57606a;
--background-color-inline-code: rgba(175, 184, 193, 0.2);
}
dark 테마와 light 테마에서 적용하기 원하는 색상을 예를 들어 --color: #c9d1d9
, --color: #24292f
와 같이 정의 한 후 var(--color)
로 css에서 사용합니다.
자세한 사항은 Custom properties (--*): CSS variables에서 확인할 수 있습니다.
댓글(utterances) 테마 설정
선택사항입니다. 만약 utterances를 사용중이라면 참고하시면 되겠습니다.
utterances html and css code
html
<div class="utterance-light">
<script
src="https://utteranc.es/client.js"
repo="sukvvon/blog-comments"
issue-term="pathname"
theme="github-light"
crossorigin="anonymous"
async
></script>
</div>
<div class="utterance-dark">
<script
src="https://utteranc.es/client.js"
repo="sukvvon/blog-comments"
issue-term="pathname"
theme="dark-blue"
crossorigin="anonymous"
async
></script>
</div>
만약 utterances를 사용중이라면 간단하게 light, dark 테마별로 적용시킬 수 있습니다.
<div class="area_reply ">
태그 밑에 위와 같은 html 코드를 추가합니다.
theme="github-light"
가 포함된 항목은 utterance-light
클래스가 포함된 div 태그로 감싸주고, "theme="dark-blue"
가 포함된 항목은 utterance-dark
클래스가 포함된 div 태그로 감싸주면 테마 별로 댓글 창을 테마별로 구현할 수 있습니다.
css
/* utterance */
html[theme="dark"] .utterance-light {
display: none;
}
html[theme="light"] .utterance-dark {
display: none;
}
display: none
속성을 부여하여 dark 테마일 경우엔 light utterances 테마가 보이지 않게 하고 light 테마일 경우 dark utterances 테마가 보이지 않게 합니다.
scrollbar
/* scrollbar */
html,
.skin_view .area_view pre code.hljs {
scrollbar-width: thin;
scrollbar-color: var(--color-a) var(--background-color-body);
}
.skin_view .area_view pre code.hljs::-webkit-scrollbar {
background-color: var(--background-color-body);
height: 6px;
}
body::-webkit-scrollbar {
background-color: var(--background-color-body);
width: 6px;
}
body::-webkit-scrollbar-thumb,
.skin_view .area_view pre code.hljs::-webkit-scrollbar-thumb {
background-color: var(--color-a);
border-radius: 6px;
}
dark 테마와 light 테마에 맞게 scrollbar 또한 테마를 적용하였습니다.
firefox(gaeko)
html,
.skin_view .area_view pre code.hljs {
scrollbar-width: thin;
scrollbar-color: var(--color-a) var(--background-color-body);
}
.skin_view .area_view pre code.hljs::-webkit-scrollbar {
background-color: var(--background-color-body);
height: 6px;
}
영향을 받는 대상인 전체 화면을 의미하는 html
과 블로그 내 코드블럭코드 .skin_view .area_view pre code.hljs
를 기준으로
scrollbar-width: thin으로 설정하여 스크롤바가 두껍지 않게 하여 가독성을 높여주도록 하고
scrollbar-color: var(--color-a) var(--background-color-body)으로 설정하여 light, dark 테마에 따라서 firefox(gaeko) 브라우저에서 동작할 수 있도록 합니다.
chrome, safari, edge, etc...(webkit)
body::-webkit-scrollbar {
background-color: var(--background-color-body);
width: 6px;
}
body::-webkit-scrollbar-thumb,
.skin_view .area_view pre code.hljs::-webkit-scrollbar-thumb {
background-color: var(--color-a);
border-radius: 6px;
}
firefox(gaeko)와는 다르게 chrome, safari, edge 등 webkit 기반의 엔진을 기반으로 삼는 브라우저는 html
이 아닌 body
태그를 중심으로 ::-webkit-scrollbar 선택자를 활용하여 width: 6px
, background-color: var(--color-a)
즉 전체 스크롤바의 너비는 6px, 배경색상은 var(--background-color-body)
로 설정합니다.
body
뿐만이 아닌 .skin_view .area_view pre code.hljs
도 포함해 ::-webkit-scrollbar-thumb 선택자를 활용하여 background-color: var(--color-a)
, border-radius: 6px
즉 스크롤 핸들의 배경 색상을 var(--color-a)
로 설정하고, border-radius를 6px로 설정해 꼭짓점을 6px 정도 둥글게 합니다.
theme.js
const DARK = "dark";
const LIGHT = "light";
const THEME_KEY = "theme";
const userTheme = sessionStorage.getItem(THEME_KEY);
const osTheme = window.matchMedia("(prefers-color-scheme: light)").matches
? LIGHT
: DARK;
const getTheme = () => {
const Theme = userTheme ? userTheme : osTheme;
return Theme;
};
const setTheme = (color) => {
sessionStorage.setItem(THEME_KEY, color);
document.documentElement.setAttribute(THEME_KEY, color);
};
if (getTheme() === DARK) {
setTheme(DARK);
} else {
setTheme(LIGHT);
}
페이지에 접속했을 때 light 테마와 dark 테마 중 컴퓨터나 사용자가 선호하는 색상 테마를 적용하는 코드입니다.
자주 쓰이는 변수 선언
const DARK = "dark";
const LIGHT = "light";
const THEME_KEY = "theme";
dark
, light
, theme
와 같은 자주 사용하는 단어들을 DARK
, LIGHT
, THEME_KEY
로 선언하여 코드를 적을 때 오타를 방지합니다.
현재 설정된 theme 확인
const userTheme = sessionStorage.getItem(THEME_KEY);
const osTheme = window.matchMedia("(prefers-color-scheme: light)").matches
? LIGHT
: DARK;
userTheme
const userTheme = sessionStorage.getItem(THEME_KEY);
sessionStorage에 THEME_KEY
즉 "theme"
에 저장된 값이 있는지 확인합니다. 그리고 그 값을 userTheme
변수로 선언합니다.
osTheme
const osTheme = window.matchMedia("(prefers-color-scheme: light)").matches
? LIGHT
: DARK;
prefer-colors-scheme
"(prefers-color-scheme: light)" css 요소를 통하여 현재 사용자가 시스템에 light
테마를 사용하는 것을 선호하는지를 window.matchMedia().matches를 통해 확인합니다.
dark 테마를 페이지의 기본 설정으로 할 것이므로 prefers-color-scheme: light
을 기준으로 삼항연산자를 활용하여 true
라면 LIGHT
즉 "light"
를,
false
라면 DARK
즉 "dark"
와 선호하는 테마가 없는 경우인 no-preference을 osTheme
에 넣으며 변수로 선언합니다.
getTheme()
const getTheme = () => {
const theme = userTheme ? userTheme : osTheme;
return theme;
};
삼항연산자를 활용하여 theme
을 기준으로 userTheme
이 참(true), 존재한다면 userTheme
을
반대로 거짓(false), 존재하지 않는다면 osTheme
를 택하게 하여 theme
변수를 선언합니다.
getTheme()
의 순환 과정을 자세히 정리하자면 다음과 같습니다.
처음 사이트에 들어오게 되어 theme.js
가 실행이 되어 getTheme()
함수를 호출하면 theme
값을 반환(return)하게 되는데 sessionStorage
의 키 값은 없어 userTheme
값은 존재하지 않으므로 삼항연산자에 의하여 theme = osTheme
이 됩니다.
블로그 내에 페이지를 이동하는 등 새로고침이 되었을 때 sessionStorage
값은 존재하므로 getTheme()
함수 내의 theme = userTheme
이 되어 getTheme()
함수는 userTheme
을 반환하게 됩니다.
페이지를 빠져 나갈 경우 session은 초기화가 됩니다.
setTheme()
const setTheme = (color) => {
sessionStorage.setItem(THEME_KEY, color);
document.documentElement.setAttribute(THEME_KEY, color);
};
sessionStorage.setItem을 통해 키 이름과 키 값을 THEME_KEY
즉 "theme"
와 매개변수인 color로 초기화합니다.
document.documentElement가 가리키는 <html>
태그에 setAttribute을 통해 THEME_KEY
즉 theme
속성 이름과 color
라는 매개 변수로 값을 초기화합니다.
함수를 사용할 때 color
라는 매개변수를 통해 DARK
나 LIGHT
로 값을 입력합니다.
if else
if (getTheme() === DARK) {
setTheme(DARK);
} else {
setTheme(LIGHT);
}
getTheme()
가 DARK
즉 "dark"
일 경우와 선호하는 테마가 없는 경우인 no-preferece
인 경우 setTheme(DARK)
함수를 실행하고,
반대로 LIGHT
즉 "lignt"
일 경우 setTheme(LIGNT)
함수를 실행합니다.
theme toggle에 해당하는 html
<!--toggle button-->
<label class="switch-button">
<input class="check" type="checkbox" />
<span class="onoff-switch"></span>
</label>
<!--toggle button-->
label 태그를 통해 토글 버튼 관련 항목의 공간을 만듭니다. 그 안에 checkbox
type의 <input>
, 그리고 토글 버튼의 역활을 하는 <span>
태그를 추가합니다.
<label>
태그의 특성에 의해서 <label>
태그 안에 있는 부분에 이벤트가 발생하였을 때 <input>
태그에 이벤트가 발생한 것과 같은 효과를 볼 수 있습니다.
theme-toggle.css
.switch-button {
position: relative;
display: inline-block;
width: 56px;
height: 38px;
bottom: 5.22px;
margin: 0 2px;
}
.switch-button input {
opacity: 0;
}
.onoff-switch {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: 20px;
background-color: #ddd;
-webkit-transition: 0.4s;
transition: 0.4s;
}
.onoff-switch::before {
position: absolute;
content: "";
height: 24px;
width: 22px;
top: 7px;
left: 4px;
border-radius: 20px;
background-color: #fff;
-webkit-transition: 0.5s;
transition: 0.5s;
}
.switch-button input:checked + .onoff-switch {
background-color: #238636;
}
.switch-button input:checked + .onoff-switch::before {
-webkit-transform: translateX(26px);
-ms-transform: translateX(26px);
transform: translateX(26px);
}
@media only screen and (max-width: 820px) {
.switch-button {
margin: 21px 0 -6px 15px;
}
}
theme에 대한 toggle 버튼에 관한 css입니다.
.swich-button input
.switch-button input {
opacity: 0;
}
opacity: 0 을 통해서 checkbox가 표시되지 않게 합니다.
.onoff-switch
.onoff-switch {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: 20px;
background-color: #ddd;
-webkit-transition: 0.4s;
transition: 0.4s;
}
.switch-button input:checked + .onoff-switch {
background-color: #238636;
}
-webkit-transition: 0.4s
, transition: 0.4s
을 통해 input
이 checked
즉 체크박스가 체크가 된 경우 토글 버튼의 바탕화면 색상이 #ddd
에서 #238636
으로 바뀔 때 0.4초가 걸리는 효과를 줍니다.
.onoff-switch::before
.onoff-switch::before {
position: absolute;
content: "";
height: 24px;
width: 22px;
top: 7px;
left: 4px;
border-radius: 20px;
background-color: #fff;
-webkit-transition: 0.5s;
transition: 0.5s;
}
.switch-button input:checked + .onoff-switch::before {
-webkit-transform: translateX(26px);
-ms-transform: translateX(26px);
transform: translateX(26px);
}
마찬가지로 -webkit-transition: 0.5s, transition: 0.5s을 통해 switch-button
class의 input
이 checked
즉 체크박스가 체크가 된 경우 토글 버튼의 위치가 -webkit-transform: translateX(26px)
, -ms-transform: translateX(26px)
, transform: translateX(26px)
에 의해서 26px만큼 우로 이동하고 이동 할 때 0.5초가 걸리는 효과를 줍니다.
@media
@media only screen and (max-width: 820px) {
.switch-button {
margin: 21px 0 -6px 15px;
}
}
max-width: 820px으로 820px로 최대 너비를 설정하여 모바일 환경에서의 토글 버튼의 위치를 설정합니다.
themeToggle.js
const checkbox = document.querySelector(".check");
const themeToggle = (event) => {
if (event.target.checked) {
setTheme(DARK);
} else {
setTheme(LIGHT);
}
};
checkbox.addEventListener("click", themeToggle);
if (getTheme() === DARK) {
checkbox.setAttribute("checked", "");
}
theme에 관한 toggle 버튼에 대한 js 코드 설명입니다.
checkbox
const checkbox = document.querySelector(".check");
checkbox.addEventListener("click", themeToggle);
check
클래스가 포함된 태그를 선택하여 checkbox
로 선언합니다. 그리고 addEventListener() 메서드를 통해서 click 이벤트 발생 시 themeToggle
함수를 실행하도록 합니다.
themeToggle()
const themeToggle = (event) => {
if (event.target.checked) {
setTheme(DARK);
} else {
setTheme(LIGHT);
}
};
toggle 버튼에 관한 함수입니다. event
매개변수를 통하여 event.target.checked
즉 html 코드가 <input type="checkbox" checked>
라면 setTheme(DARK)
를
반대로 <input type="checkbox">
라면 setTheme(LIGHT)
를 호출하도록 합니다.
if
if (getTheme() === DARK) {
checkbox.setAttribute("checked", "");
}
getTheme()
가 DARK
즉 "dark"
일 경우와 선호하는 테마가 없는 경우인 no-preferece
인 경우 토글은 checked
가 되게 합니다.
참고
'JavaScript' 카테고리의 다른 글
Tistory(#1) 스크롤시 상단바 숨기고 보이게 하기 (0) | 2022.03.12 |
---|