hELLO 는 티도리 프레임워크로 개발되었으나 자체 커스텀을 위해 강제하지는 않습니다. 물론 티도리 프레임워크를 사용해서 원본 소스를 수정하면 조금 더 개발 편의성에서 우위를 가질 수는 있지만, 이에 대한 선행지식이 더 필요합니다. 단지 스킨을 커스텀하기위해 선행지식을 모두 공부하는 것은 상당한 수고를 들이는 일이므로 이보다는 원본 소스코드를 참고하면 좋은 때를 중점으로 작성하였습니다.
*이 가이드를 이해하려면 HTML, CSS, Javascript 에 대한 선행지식이 필요합니다.
원본 소스코드를 왜 봐야할까요?
일반적으로 커스텀을 위해서는 skin.html, style.css, images/script.js 와 같은 배포용 파일을 직접 편집하겠지만, 그 특성상 코드의 가독성이 고려되지 않아 직접 편집하기 불편한 점이 있습니다. 또한 하나의 파일에 코드의 중복을 동반한 수천 줄의 코드가 집약되어 있기 때문에 읽기 난감할 수 있습니다. 본질적으로 배포용 파일은 사람이 읽고 편집하기 위해 작성되는 파일은 아니기 때문입니다.
원본 소스코드는 스킨의 기능 별로 각각 파일이 나뉘어 있고 편집에 용이하도록 코드의 가독성이 고려되어 작성되어 있습니다. 레퍼런스 또한 준비되어 있기 때문에 스킨을 구성하는 특정 요소의 마크업을 살펴보거나, 아이디와 클래스와 같은 CSS 요소를 찾을 때, 스크롤스파이와 같은 자바스크립트가 사용되는 컴포넌트 코드 구성을 살펴볼 때 이 가이드를 유용하게 참고할 수 있습니다. 원본 소스코드에서 사용된 프론트엔드 기술스택은 PugJS, TailwindCSS, AlpineJS 입니다.
마크업
skin.html 에 해당하는 마크업은 원본 소스코드에서 PugJS 로 구성되어 있습니다. 사이드바, 본문 등의 주요 구성요소들은 https://github.com/pronist/hello/tree/master/views 에 위치합니다. 파일의 이름만으로도 각 요소가 어떤 부분에 해당하는지 알 수 있도록 하였습니다.
PugJS 템플릿
티도리 프레임워크의 사양으로써 하나의 PugJS 템플릿에는 마크업, 자바스크립트, 스타일이 모두 들어갈 수 있습니다. 이는 각각 skin.html, images/script.js, style.css 로 분리됩니다.
//- skin.html
template(x-if='$store.header.show')
header#global-header(
x-ref='globalHeader'
class='mb-10 lg:mb-16')
.info(class='border-b border-solid border-b-h-300 dark:border-b-h-600 h-h min-h-[theme(spacing.h)] flex flex-col justify-center items-center text-center gap-y-1 text-h-800 dark:text-h-200')
h1.tit(
data-empty='검색어가 없어요 :('
x-init='$store.header.title = `[##_page_title_##]`'
x-text='$store.header.title'
class='font-bold text-3xl')
span.desc(class='font-bold text-sm') [##_title_##]
//- images/script.js
script.
document.addEventListener('alpine:init', () => Alpine.store('header', {
/**
* @type {string}
*/
title: '',
/**
* @type {boolean}
*/
show: true
}))
//- style.css
style
:postcss
#global-header {
.tit {
@apply empty:before:content-[attr(data-empty)];
}
}
믹스인
믹스인(Mixins)은 마크업을 함수처럼 분리하여 코드의 중복을 줄이기 위한 PugJS 의 기능입니다. 원본 소스코드에서 페이지를 담당하는 코드를 보면 다음과 같이 작성되어 있습니다.
s_page_rep
section#page
+permalink('article', false)
여기에서 +permalink('article', false)
라고 되어 있는 것이 믹스인을 사용한 것입니다. 이러한 믹스인은 https://github.com/pronist/hello/tree/master/assets/pug/Mixins 에 정의되어 있습니다.
cover()
,coverTitle()
,coverPost()
,coverPosts()
: 커버를 구성하기 위한 믹스인입니다.group()
: 방명록과 댓글 목록을 구성하기 위한 믹스인입니다.index()
: 커버 아이템, 공지사항 목록, 리스트를 구성하기 위한 믹스인입니다.permalink()
: 본문, 페이지, 공지사항 본문을 구성하기 위한 믹스인입니다.popup()
: 본문의 이전 글/다음 글 팝업을 구성하기 위한 믹스인입니다.
이름 | 경로 | 믹스인 |
커버/타이포그래피 커버/포트폴리오 커버/빈 줄 커버/베이직 |
views/Main/Cover/Typography.pug views/Main/Cover/Portfolio.pug views/Main/Cover/EmptyLine.pug views/Main/Cover/Basic.pug |
cover() , coverTitle() , coverPost() , coverPosts() |
글 공지사항 페이지 |
views/Main/Entry.pug views/Main/Notice.pug views/Main/Page.pug |
permalink() |
글/팝업 | views/Main/Entry/Popup.pug | popup() |
댓글 방명록 |
views/Main/Entry/Replies.pug views/Main/Guestbook.pug |
group() |
리스트 공지사항 |
views/Main/List.pug views/Main/Notice.pug |
index() |
템플릿 레퍼런스
자바스크립트
자바스크립트가 사용되는 영역의 구분은 크게 전역적 기능, AlpineJS 컴포넌트, 웹팩, 부트스트래핑으로 구성됩니다. 일반적으로 기능을 추가하는 일은 전역적 기능으로 하기 때문에 그 부분을 중점으로 살펴보시면 되겠습니다.
전역적 기능
자바스크립트를 사용하여 본문에 코드 라인넘버와 같은 새로운 기능들을 추가해서 사용하고 싶을 수도 있습니다. 가장 단순한 방법은 skin.html 에 <script defer>
로 스크립트를 불러오고 load
이벤트에 등록하는 것입니다. 다음은 코드 라인넘버 기능을 추가하는 예시 코드입니다. 일반적인 기능 추가의 경우 이 코드만으로도 좋습니다.
<script defer src='//cdn.jsdelivr.net/npm/highlightjs-line-numbers.js@2.8.0/dist/highlightjs-line-numbers.min.js'></script>
<script>
window.addEventListener('load', () => {
hljs.initLineNumbersOnLoad()
})
</script>
이 방법은 전역적 기능을 추가합니다. 간단하게 일반적인 기능을 추가할 수 있지만, 코드가 범위화 되지 않고 스킨 전반적으로 영향을 미칠 뿐만 아니라, 기능에 따라 이 방법은 때때로 기대하는 것과는 달리 동작하지 않을 수도 있습니다.
AlpineJS 컴포넌트
자바스크립트 프레임워크로 AlpineJS 를 사용하여 구성하고 있습니다. AlpineJS 는 경량급 자바스크립트 프레임워크이므로 뷰, 리액트, 앵귤러와 같은 다른 프레임워크와는 그 결이 다르며 과거 프론트엔드 생태계를 지배하던 JQuery 에 가깝습니다. 따라서 별도의 빌드과정이 필요하지 않습니다. 진입장벽 또한 낮은 편이죠. 그에 더해 다른 모던 프레임워크들 처럼 새로운 컴포넌트를 생성하거나 이미 만들어놓은 컴포넌트를 엘리먼트에 지정하는 것이 가능합니다. x-data, Alpine.data()
를 사용한 컴포넌트 정의는 AlpineJS 의 공식문서를 살펴보시면 자세하게 알 수 있습니다. 스킨에서 사용하고 있는 예시로는 https://github.com/pronist/hello/blob/master/views/Sidebar/Category.pug 가 있습니다.
AlpineJS 컴포넌트는 원본 소스코드에서 목차와 스크롤스파이 같은 주요 요소에 해당하는 기능 구현을 담당합니다. 본문에 기능을 추가할 때 본문을 담당하는 AlpineJS 컴포넌트를 수정하면 전역적으로 작성하는 것보다 조금 더 깔끔한 방법으로 처리할 수 있습니다. 그러나 AlpineJS 에 대한 선행 지식이 없다면 어려울 수 있습니다. AlpineJS 컴포넌트는 images/script.js 에 ES5 로 바벨(Babel)처리 되어 작성되어 있습니다.
https://github.com/pronist/hello/discussions/215
이름 | 경로 | 컴포넌트 |
문서 레이어 | app.pug | app |
리스트 | views/Main/List.pug | { listStyle: [##_list_style_##] } |
글/컨텐츠 | assets/pug/Mixins/Permalink/Content.pug | content |
글/컨텐츠/스크롤스파이 | assets/pug/Mixins/Permalink/Content/Scrollspy.pug | scrollspy |
글/푸터/태그 | assets/pug/Mixins/Permalink/Footer/Tag.pug | tag |
글/팝업 | views/Main/Entry/Popup.pug | { open: true } |
스킨 레이어 | index.pug | { sidebar: false } |
바텀 | views/Bottom.pug | bottom |
사이드바/카테고리 | views/Sidebar/Category.pug | category |
사이드바/카테고리/메뉴 | views/Sidebar/Category.pug | { open: false } |
탑 | views/Top.pug | top |
웹팩
원본 소스코드는 웹팩(Webpack)으로 번들링됩니다. 웹팩(Webpack) 엔트리는 두 파일이며 https://github.com/pronist/hello/tree/master/assets/js 에서 찾아볼 수 있습니다. 이는 images/app.js, images/vendor.js 로 번들링됩니다.
이름 | 경로 |
웹팩 엔트리 | assets/js/app.js assets/js/vendor.js |
부트스트래핑
부트스트래핑(Bootstrapping)은 스킨의 콘텐츠를 렌더링하기 전에 작업할 일련의 과정입니다. https://github.com/pronist/hello/blob/master/images/bootstrap.js 에 자바스크립트 코드가 작성되어 있으며 주로 다크모드를 설정하기 위한 코드가 작성되어 있습니다. 부트스트랩은 images/bootstrap.js 에 해당합니다. <head>
에서 블로킹으로 실행하므로 오래 걸리는 작업은 배치하지 않는 것이 좋습니다.
스타일
v4.4 부터 스킨 코드에 커스텀 편의를 위한 아이디 및 클래스가 대량 추가되었습니다. 따라서 이제는 조금 더 나은 방법으로 CSS 에서 아이디와 클래스를 셀렉트하여 속성을 부여할 수 있게 됩니다. 기존에는 커스텀하려면 skin.html 에서 TailwindCSS 및 HTML 을 직접 수정해야 했지만 이제는 style.css 에서 셀렉터를 지정하거나 새로운 스타일시트를 작성하여 skin.html 에 포함시키는 것만으로도 스킨을 커스텀할 수 있게 됩니다.
이름 | 경로 |
스타일시트 | assets/css/app.css assets/css/shortcutLayer.css |
원본 소스코드에서 아이디와 클래스 찾기
아이디 및 클래스를 찾을 때 skin.html 을 보면서 하기에는 코드가 너무 많고 복잡해 보일 수 있습니다. 따라서 원본 소스코드를 참고하여 커스텀 위한 아이디 및 클래스를 찾아보는 것이 좋습니다. 예를 들면 테마를 변경하고, 상단으로 이동할 수 있는 버튼이 있는 하단(Bottom)의 경우 다음과 같은 코드가 있습니다.
#bottom(
x-data='bottom'
class='flex justify-end px-4 w-full box-border fixed text-sm z-30 bottom-3 <s_if_var_sidebar>xl:w-c</s_if_var_sidebar>')
.wrap(class='flex gap-x-2.5')
button.theme.btn(@click='toggleTheme')
span.txt 테마
i.ico(class='fa-solid fa-moon')
a.top.btn(href='#hELLO')
span.txt 상단으로
i.ico(class='fa-solid fa-chevron-up')
#bottom, .wrap, .theme, .top
과 같은 아이디와 클래스가 커스텀 편의를 위해 추가된 것입니다. class
속성에 명시된 클래스들은 대체로 Font Awesome, TailwindCSS 클래스에 해당하는 것들이므로 건드리지 않아도 됩니다. 다른 파일들 또한 커스텀 편의를 위한 아이디 및 클래스는 구분되도록 표현법을 달리 함으로써 직관적으로 명시하여 알아보기 쉽게 구성하였습니다. 하지만 .wrap, .txt, .ico
와 같은 클래스의 이름은 단순하고 짧은 대신에 고유하지 않기 때문에 #bottom > .wrap
과 같이 아이디의 자식 또는 손자 셀렉터로 지정하여 사용하는 것을 권장합니다.
TailwindCSS 테마 설정
hELLO 에서 사용한 CSS 프레임워크는 TailwindCSS 입니다. 커스텀을 위해 TailwindCSS 설정 파일을 참고해야 하는 경우, https://github.com/pronist/hello/blob/master/tailwind.config.js 에서 테마 설정을 살펴볼 수 있습니다. TailwindCSS 에서 사용하는 @base
와 같은 디렉티브는 https://github.com/pronist/hello/blob/master/assets/css/app.css 에서 찾을 수 있습니다.
전역 변수
hELLO 에서는 너비와 높이 같은 부분을 조금 더 손쉽게 조절할 수 있게 하기 위해 주요 레이아웃 요소에 대해서는 전역 변수로 지정을 해둔바 있습니다. 인덱스, 본문, 사이드바, 컨텐츠, 헤더를 조절할 수 있습니다.
:root {
--h-idx: 1100px; /* index */
--h-pem: [##_var_width_##]px; /* permalink */
--h-s: 256px; /* sidebar */
--h-c: calc(100% - var(--h-s)); /* content */
--h-h: 256px; /* header */
}
--h-idx
: 리스트, 공지사항(리스트), 커버, 태그 클라우드 너비입니다.--h-pem
: 방명록, 글, 보호글 너비입니다.--h-s
: 사이드바 너비입니다.--h-c
: 전체 화면에서 사이드바의 너비를 제외한 컨텐츠 영역의 너비입니다.--h-h
: 글로벌 공통 헤더, 글 헤더, 커버 제목의 높이입니다.
이러한 전역 변수들은 https://github.com/pronist/hello/blob/master/tailwind.config.js 에서 spacing
으로 TailwindCSS 에 테마로 지정되어 넓이, 높이로 클래스로써 직접 사용되거나 특정 클래스에 @apply
처리되어 사용될 수 있습니다.
spacing: {
idx: 'var(--h-idx)',
pem: 'var(--h-pem)',
s: 'var(--h-s)',
c: 'var(--h-c)',
h: 'var(--h-h)'
}
이름 | 경로 | 전역변수 | 클래스 |
태그 클라우드 커버/베이직 리스트/빈 리스트 |
views/Main/Tag.pug views/Main/Cover/Basic.pug views/Main/List/Empty.pug |
--h-idx |
index-container |
방명록 글/컨텐츠 글/푸터 글/댓글 보호글 |
views/Main/Guestbook.pug assets/pug/Mixins/Permalink/Content.pug assets/pug/Mixins/Permalink/Footer.pug views/Main/Entry/Replies.pug views/Main/Protected.pug |
--h-pem |
permalink-container |
스킨 레이어 사이드바 |
index.pug views/Sidebar.pug |
--h-s |
pl-s w-s |
탑 바텀 |
views/Top.pug views/Bottom.pug |
--h-c |
w-c |
글로벌 공통 헤더 믹스인/홈 커버 글/헤더 |
views/Main/Header.pug assets/pug/Mixins/Cover.pug assets/pug/Mixins/Permalink/Header.pug |
--h-h |
h-h |
index-container
, permalink-container
와 같은 클래스들은 https://github.com/pronist/hello/blob/master/assets/css/app.css 에서 @layer components
에 정의되어 있습니다. 내부에서 테마로 지정된 전역 변수를 사용합니다.
@layer components {
.index-container {
@apply w-full px-4 lg:px-0 lg:mx-auto lg:w-idx;
}
.permalink-container {
@apply w-full px-4 lg:px-0 lg:max-w-screen-lg lg:w-pem lg:mx-auto;
}
}
테마 클래스 추가
새로운 스타일의 경우 기존의 스타일을 덮어쓰는 일이 대부분입니다. CSS 에서 엘리먼트를 바로 셀렉트하기보다는 최상위 테마 클래스를 추가하여 처리하는 것이 유지보수와 가독성에 좋습니다. 새로운 스타일의 이름이 new style 인 경우, 최상위 클래스 이름을 new-style
로 가정하고, skin.html 에서 <html>
에 다음과 같이 클래스를 추가해 볼 수 있습니다.
<html class="new-style" id="hELLO">
그다음 new_style.css 에서 다음과 같이 처리하여 기존의 스타일을 덮어보시기 바랍니다. 예를 들어 본문의 스타일을 변경하고 싶다면 다음과 같이 셀렉트할 수 있습니다.
.new-style #article {
/* ... */
}
*다크모드: 다크모드의 경우<html>
에dark
클래스가 추가되기 때문에.new-style
이 아닌.dark.new-style
아래에 별도로 작업하시기 바랍니다.
새로운 스타일시트 작성
새로운 스타일시트를 작성하는 경우 skin.html 에서 <link rel="stylesheet" href="./style.css">
아래에 CSS 파일을 포함하는 코드를 작성하면 됩니다. 티스토리에서는 정적파일을 images 에서 관리하므로 새로운 스타일시트 또한 images 에 포함되어야 하며 파일의 이름이 new_style.css 인 경우 skin.html 에는 다음과 같이 작성하면 됩니다. 원본 코드가 어떤 CSS 프레임워크로 작성되어 있든지와는 관계없이 그 결과물인 CSS 파일이 skin.html 에 포함되면 됩니다.
<link rel="stylesheet" href="./images/new_style.css">
스킨 옵션
스킨 옵션과 같은 주요 설정은 티스토리 스킨 가이드에 따라 index.xml 에 작성되는데, https://github.com/pronist/hello/tree/master/docs 에 위치하고 있습니다. 스킨 옵션의 경우 자바스크립트, 마크업, 스타일시트에서 다양하게 사용되며 값에 접근하기 위해서는 [##_var_<Name>_##]
와 같은 형태로 구성되는데, 예를 들어 사이드바의 기본 디스플레이 상태의 경우 [##_var_sidebar_##]
에 해당합니다. https://github.com/pronist/hello/blob/master/app.pug 를 살펴보면 자바스크립트로 다음과 같이 값을 참고하도록 작성되어 있습니다.
window.skinOptions = {
sidebar: '[##_var_sidebar_##]', // 사이드바
foldableCategory: '[##_var_foldable-category_##]', // 카테고리 접기
width: '[##_var_width_##]', // 글 너비
toc: '[##_var_toc_##]', // toc
scrollspy: '[##_var_scrollspy_##]', // 스크롤스파이
hljs: '[##_var_hljs_##]', // 코드블록 (라이트)
hljsDark: '[##_var_hljs-dark_##]', // 코드블록 (다크)
headerStyle: '[##_var_header-style_##]' // 헤더 스타일
}
이렇게 정의된 스킨 옵션은 주로 AlpineJS 컴포넌트 내부에서 사용하게 되는데, 예시를 살펴보면, skinOptions.hljs
, skinOptions. hljsDark
를 사용하여 코드 블록의 테마를 설정하는 경우에 사용이 되기도 하고,
link(rel='stylesheet' :href='`//cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.7.0/build/styles/${dark ? skinOptions.hljsDark : skinOptions.hljs}.min.css`')
카테고리 접기를 설정하기 위해 skinOptions.foldableCategory
가 사용되기도 합니다.
document.addEventListener('alpine:init', () => Alpine.data('category', () => ({
/**
* @var {Boolean} foldableCategory
*/
foldableCategory: skinOptions.foldableCategory,
/**
* Init
*/
init () {
if (this.foldableCategory) {
this.$subCategories.forEach(this.foldable.bind(this))
}
},
...
})))
마크업에서 태그로 사용될 때에는 s_if_var_<Name>
와 같은 형태로 처리됩니다. 예를 들어 다음과 같이 설정에 따라 s_if_var_toc
로 목차의 디스플레이 상태를 표현합니다.
#content(
x-data='content'
class='permalink-container relative')
s_if_var_toc
include Content/TOC
include Content/Article
s_if_var_scrollspy
include Content/Scrollspy
또한 속성 내부에서도 스킨 옵션을 사용할 수 있는데, 예를 들어 <s_if_var_sidebar>
를 사용하여 사이드바가 기본 디스플레이 상태인 경우에만 클래스를 부여합니다.
extends app
block TIDORY
include views/Loader
s_t3
#index(
x-data='{ sidebar: false }'
class='pl-0 break-all <s_if_var_sidebar>xl:pl-s</s_if_var_sidebar>')
include views/Main
include views/Sidebar
include views/Indicator
include views/Top
include views/Bottom
스타일시트에서는 전역 변수에 다음과 같이 스킨 옵션이 지정되어 있습니다.
:root {
--h-pem: [##_var_width_##]px;
}
스킨 옵션을 추가하는 등의 작업이 하고 싶은 경우에, 이는 모든 티스토리 스킨에 해당하는 공통적인 사항이므로 티스토리 스킨 가이드를 참고하셔서 진행하셔야합니다.
이름 | 경로 | 스킨 옵션 |
문서 레이어 | app.pug | width , hljs , hljsDark |
스킨 레이어 탑 바텀 사이드바 |
index.pug views/Top.pug views/Bottom.pug views/Sidebar.pug |
sidebar |
사이드바/카테고리 | views/Sidebar/Category.pug | foldableCategory |
글/헤더 | assets/pug/Mixins/Permalink/Header.pug | headerStyle |
글/컨텐츠 | assets/pug/Mixins/Permalink/Content.pug | toc , scrollspy |
그 밖에 커스텀에 대한 문의 사항은 댓글로 남겨주시면 되겠습니다. 다만, 특정 블로그처럼 하고 싶다거나, "이미지의 테두리를 둥글게 하고싶어요" 와 같은 글자, 이미지와 같은 요소의 색상, 간격, 여백, 크기, 넓이의 변경과 같은 개별 블로거의 미적취향과 관련된 질문은 답변해드리지 않습니다.