티스토리 블로그 글도 위키백과처럼 목차가 쭉 나오면 좋겠다는 생각이 들었다. 그렇게 되면 원하는 부분에 바로 가서 볼 수 있으니까.
먼저 찾아보기
내가 만들어도 좋지만, 누군가 이미 만들지 않았을까. github에서 찾아보니 있었다!
Tistory TOC(Table Of Contents) by wbluke박우빈님
아쉬운 점
아래 예처럼 화면이 클 때 왼쪽 편에 뜨는 형태였다.
모바일 화면에서도 차례가 나오면 좋겠다는 생각이 들었다. 모바일 화면에서는 글 시작할 때 뜨고, 큰 화면에서는 글 시작할 때 뜬 목차는 사라지고, 왼쪽 편에 뜨고.
개선 결과
아래 예처럼 화면을 작게 하면 글 안에 차례가 나타난다.
화면을 크게 하면 글 안의 차례는 사라지고, 왼쪽 편에 차례가 나타난다.
소스 파일
Tistory TOC(Table Of Contents) by wbluke님 코드를 바탕으로 약간 수정했다.
위 파일을 내려받자.
그리고 관리자 - 스킨편집 - html편집 - 파일업로드로 들어가서 추가 버튼을 눌러서 추가하자.
추가하고 나면 아래처럼 된다.
적용 방법
현재 적용한 스킨은 odessey이다.
스킨에 따라 적용 결과가 다를 수 있다.
HTML 편집 화면에 들어간다.
관리자 - 스킨편집 - html편집 - HTML이다.
1. head끝 지점 찾아서 css파일 추가
아래 예처럼 추가한다.
<link rel="stylesheet" href="./images/toc.css">
</head>
2. toc-elements div를 추가
<div id="container">를 찾아서 그 아랫줄에 <div id="toc-elements"></div>를 추가한다.
110~120번째 줄 쯤에 있다.
스킨마다 다른데 대체로 main 같은 이름으로 되어 있을 거다. 그 근처에 추가해보고 예상 결과와 다르다면 위치를 조금씩 달리해서 추가해보면 된다. 잘 모르겠다면 그냥 odessey스킨을 적용한 뒤에 따라하는 게 좋다.
<div id="container">
<div id="toc-elements"></div>
3. toc-elements-in-article 추가
<div id="wrap_toc"><div id="toc-elements-in-article"></div></div>를 추가해야 한다.
<div class="area-view">를 찾자.
아래 쪽으로 좀 내려가면
<div class="article-view">
티스토리 블로그 글도 위키백과처럼 목차가 쭉 나오면 좋겠다는 생각이 들었다. 그렇게 되면 원하는 부분에 바로 가서 볼 수 있으니까.
먼저 찾아보기
내가 만들어도 좋지만, 누군가 이미 만들지 않았을까. github에서 찾아보니 있었다!
Tistory TOC(Table Of Contents) by wbluke박우빈님
아쉬운 점
아래 예처럼 화면이 클 때 왼쪽 편에 뜨는 형태였다.
모바일 화면에서도 차례가 나오면 좋겠다는 생각이 들었다. 모바일 화면에서는 글 시작할 때 뜨고, 큰 화면에서는 글 시작할 때 뜬 목차는 사라지고, 왼쪽 편에 뜨고.
개선 결과
아래 예처럼 화면을 작게 하면 글 안에 차례가 나타난다.
화면을 크게 하면 글 안의 차례는 사라지고, 왼쪽 편에 차례가 나타난다.
소스 파일
Tistory TOC(Table Of Contents) by wbluke님 코드를 바탕으로 약간 수정했다.
위 파일을 내려받자.
그리고 관리자 - 스킨편집 - html편집 - 파일업로드로 들어가서 추가 버튼을 눌러서 추가하자.
추가하고 나면 아래처럼 된다.
적용 방법
현재 적용한 스킨은 odessey이다.
스킨에 따라 적용 결과가 다를 수 있다.
HTML 편집 화면에 들어간다.
관리자 - 스킨편집 - html편집 - HTML이다.
1. head끝 지점 찾아서 css파일 추가
아래 예처럼 추가한다.
<link rel="stylesheet" href="./images/toc.css">
</head>
2. toc-elements div를 추가
<div id="container">를 찾아서 그 아랫줄에 <div id="toc-elements"></div>를 추가한다.
110~120번째 줄 쯤에 있다.
스킨마다 다른데 대체로 main 같은 이름으로 되어 있을 거다. 그 근처에 추가해보고 예상 결과와 다르다면 위치를 조금씩 달리해서 추가해보면 된다. 잘 모르겠다면 그냥 odessey스킨을 적용한 뒤에 따라하는 게 좋다.
<div id="container">
<div id="toc-elements"></div>
3. toc-elements-in-article 추가
<div id="wrap_toc"><div id="toc-elements-in-article"></div></div>를 추가해야 한다.
<div class="area-view">를 찾자.
아래 쪽으로 좀 내려가면
<div class="article-view">
</div>
가 나타난다.
이 위에 추가한다.
<div id="wrap_toc"><div id="toc-elements-in-article"></div></div>
<div class="article-view">
</div>
210~220번째 줄에 있다
4. script 추가
</body>위에 <script src="./images/toc.js"></script>를 추가한다.
맨 끝에 있다.
<script src="./images/toc.js"></script>
</body>
끝이다.
이제 각 블로그 글마다 차례가 나타날 것이다.
차례가 나타나지 않는 경우는 글 작성할 때 제목1, 제목2, 제목3을 사용하지 않은 경우다.
제목 스타일을 적용해야지만 차례가 나타난다.
코드
toc.js
추가한 부분
const TOC_CARD = (function () {
const TocCardController = function () {
const registerHTagsOnTocCard = function () {
tocCardService.registerTagsOnTocInArticle(levelMap);
}
const onresize = function () {
const width = document.body.clientWidth;
if(width > 1420){
tocCardService.removeTOCInArticle();
}else{
tocCardService.addTOCInArticle();
}
}
return {
onresize
};
};
const TocCardService = function () {
const tocElementsCardInArticle = document.querySelector('#toc-elements-in-article');
const wrapToc = document.querySelector('#wrap_toc');
const removeTOCInArticle = function(){
if(wrapToc.childNodes.length > 0){
wrapToc.removeChild(tocElementsCardInArticle);
}
}
const addTOCInArticle = function(){
if(wrapToc.childNodes.length === 0){
wrapToc.appendChild(tocElementsCardInArticle);
}
}
const registerTagsOnTocInArticle = function (levelMap) {
if(hTags.length > 0){
const tocH = document.createElement("h2");
tocH.append("차례");
tocElementsCardInArticle.appendChild(tocH);
}
hTags.forEach((hTag, indexOfHTag) => {
let hTagItem;
levelMap.forEach((level, key) => {
if (hTag.matches(`h${key}`)) {
hTagItem = createTagItemByLevel(level, hTag, indexOfHTag);
}
})
tocElementsCardInArticle.appendChild(hTagItem);
});
const width = document.body.clientWidth;
if(width > 1420){
removeTOCInArticle();
}
}
return {
registerTagsOnTocInArticle,
removeTOCInArticle,
addTOCInArticle
}
};
const onresize = function () {
tocCardController.onresize();
}
return {
onresize
}
})();
window.onresize = function(){
TOC_CARD.onresize();
}
전체
/*
* Tistory TOC (Table Of Contents)
* dev by wbluke (wbluke.com)
* last update 2020.05.06
* version 0.1.5
* https://github.com/wbluke/tistory-table-of-contents
* https://www.wbluke.com/21
*/
const CLASS_OF_MAIN_CONTENTS = '.article-view';
const CONSTANTS = (function () {
const KEY_OF_H1 = 1;
const KEY_OF_H2 = 2;
const KEY_OF_H3 = 3;
const KEY_OF_H4 = 4;
const LEVEL_1 = 1;
const LEVEL_2 = 2;
const LEVEL_3 = 3;
const LEVEL_4 = 4;
/* 최상위 태그에 따른 레벨 Map */
const levelsByH1 = function () {
return new Map([[KEY_OF_H1, LEVEL_1], [KEY_OF_H2, LEVEL_2], [KEY_OF_H3, LEVEL_3], [KEY_OF_H4, LEVEL_4]])
}
const levelsByH2 = function () {
return new Map([[KEY_OF_H2, LEVEL_1], [KEY_OF_H3, LEVEL_2], [KEY_OF_H4, LEVEL_3]])
}
const levelsByH3 = function () {
return new Map([[KEY_OF_H3, LEVEL_1], [KEY_OF_H4, LEVEL_2]])
}
const levelsByH4 = function () {
return new Map([[KEY_OF_H4, LEVEL_1]])
}
return {
indexOfH1: KEY_OF_H1,
indexOfH2: KEY_OF_H2,
indexOfH3: KEY_OF_H3,
indexOfH4: KEY_OF_H4,
levelsByH1: levelsByH1(),
levelsByH2: levelsByH2(),
levelsByH3: levelsByH3(),
levelsByH4: levelsByH4(),
}
})();
const TOC_CARD = (function () {
const TocCardController = function () {
const tocCardService = new TocCardService();
const initTocElementsCard = function () {
tocCardService.initTocElementsCard();
}
const giveIdToHTags = function () {
tocCardService.giveIdToHTags();
}
const registerHTagsOnTocCard = function () {
const levelMap = tocCardService.getLevelsByHighestTag();
tocCardService.registerTagsOnToc(levelMap);
tocCardService.registerTagsOnTocInArticle(levelMap);
}
const init = function () {
const existsHTags = tocCardService.checkExistenceOfHTags();
if (existsHTags) {
initTocElementsCard();
giveIdToHTags();
registerHTagsOnTocCard();
}
};
const onscroll = function () {
const tocTag = tocCardService.findCurrentHTag();
if (tocTag) {
tocCardService.markCurrentHTag(tocTag);
tocCardService.scrollToMainTocTag(tocTag);
tocCardService.detectTocCardPosition();
}
}
const onresize = function () {
const width = document.body.clientWidth;
if(width > 1420){
tocCardService.removeTOCInArticle();
}else{
tocCardService.addTOCInArticle();
}
}
return {
init,
onscroll,
onresize
};
};
const TocCardService = function () {
const tocElementsCard = document.querySelector('#toc-elements');
const tocElementsCardInArticle = document.querySelector('#toc-elements-in-article');
const wrapToc = document.querySelector('#wrap_toc');
const removeTOCInArticle = function(){
if(wrapToc.childNodes.length > 0){
wrapToc.removeChild(tocElementsCardInArticle);
}
}
const addTOCInArticle = function(){
if(wrapToc.childNodes.length === 0){
wrapToc.appendChild(tocElementsCardInArticle);
}
}
const mainContents = document.querySelector(CLASS_OF_MAIN_CONTENTS);
const hTags = (function () {
const foundHTags = mainContents.querySelectorAll('h1, h2, h3, h4');
/* 글 내용 밑에 있는 [...카테고리의 다른 글] h4 제거 */
return [...foundHTags].filter(hTag =>
!hTag.parentElement.classList.contains('another_category') &&
!hTag.parentElement.classList.contains('tags') &&
!hTag.parentElement.classList.contains('related-articles'));
})();
/* h1, h2, h3, h4 태그가 있는지 확인한다 */
const checkExistenceOfHTags = function () {
if (mainContents === undefined) {
return false;
}
return hTags.length !== 0;
}
const initTocElementsCard = function () {
tocElementsCard.classList.add('toc-app-common', 'toc-app-items', 'toc-app-basic');
}
/** 최상위 태그에 따른 레벨 Map 받아오기
*
* h1 ~ h4 태그 중 가장 높은 태그를 찾아서 그에 맞게 Level을 설정한다.
* 예를 들어, h1 태그가 없고 h2, h3 태그만 있는 경우
* h2가 가장 높은 태그이며, 해당 태그 h2에 LEVEL_1을 부여하고 그 다음 태그인 h3에는 LEVEL_2를 부여한다.
*
* 부여된 Level에 따라 적용되는 CSS가 달라진다.
* */
const getLevelsByHighestTag = function () {
const levelMapByHighestTag = {
'H1': CONSTANTS.levelsByH1,
'H2': CONSTANTS.levelsByH2,
'H3': CONSTANTS.levelsByH3,
};
return levelMapByHighestTag[findHighestHTag().tagName] || CONSTANTS.levelsByH4;
}
/* 최상위 태그 판별 작업 */
const findHighestHTag = function () {
return [...hTags].reduce((pre, cur) => {
const tagNumOfPre = parseInt(pre.tagName[1]);
const tagNumOfCur = parseInt(cur.tagName[1]);
return (tagNumOfPre < tagNumOfCur) ? pre : cur;
});
}
/* TOC에 태그 삽입 */
const registerTagsOnToc = function (levelMap) {
hTags.forEach((hTag, indexOfHTag) => {
let hTagItem;
levelMap.forEach((level, key) => {
if (hTag.matches(`h${key}`)) {
hTagItem = createTagItemByLevel(level, hTag, indexOfHTag);
}
})
tocElementsCard.appendChild(hTagItem);
});
}
const registerTagsOnTocInArticle = function (levelMap) {
if(hTags.length > 0){
const tocH = document.createElement("h2");
tocH.append("차례");
tocElementsCardInArticle.appendChild(tocH);
}
hTags.forEach((hTag, indexOfHTag) => {
let hTagItem;
levelMap.forEach((level, key) => {
if (hTag.matches(`h${key}`)) {
hTagItem = createTagItemByLevel(level, hTag, indexOfHTag);
}
})
tocElementsCardInArticle.appendChild(hTagItem);
});
const width = document.body.clientWidth;
if(width > 1420){
removeTOCInArticle();
}
}
const createTagItemByLevel = function (level = CONSTANTS.NUM_OF_H1, hTag, indexOfHTag) {
const basicItem = createBasicItemBy(hTag, indexOfHTag);
appendScrollEventsOn(basicItem, indexOfHTag);
basicItem.classList.add(`toc-level-${level}`);
return basicItem;
}
const createBasicItemBy = function (hTag, indexOfHTag) {
const basicItem = document.createElement('a');
basicItem.innerHTML += hTag.innerText;
basicItem.id = `toc-${indexOfHTag}`;
basicItem.classList = 'toc-common';
return basicItem;
}
const generateIdOfHTag = function (indexOfHTag) {
return 'h-tag-' + indexOfHTag;
}
const appendScrollEventsOn = function (basicItem, indexOfHTag) {
const target = document.querySelector('#' + generateIdOfHTag(indexOfHTag));
basicItem.addEventListener('click', () => window.scrollTo({
top: target.offsetTop - 10,
behavior: 'smooth'
}));
}
const giveIdToHTags = function () {
hTags.forEach((hTag, indexOfHTag) => {
hTag.id = generateIdOfHTag(indexOfHTag);
});
}
const findCurrentHTag = function () {
if (hTags.length == 0) {
return undefined;
}
const currentHTag = findCurrentMainHTag();
return findTocTagCorrespondingToHTag(currentHTag);
}
const findCurrentMainHTag = function () {
const headArea = document.querySelector('.area_head');
let headAreaHeight = 0;
if (headArea) {
headAreaHeight = headArea.offsetHeight;
}
const middleHeight = window.scrollY + (window.innerHeight / 2) - headAreaHeight;
return [...hTags].reduce((pre, cur) => {
if (middleHeight < pre.offsetTop && middleHeight < cur.offsetTop) {
return pre;
}
if (pre.offsetTop < middleHeight && middleHeight <= cur.offsetTop) {
return pre;
}
return cur;
});
}
const findTocTagCorrespondingToHTag = function (currentHTag) {
const indexOfHTag = parseIndexOfTag(currentHTag);
return document.querySelector(`#toc-${indexOfHTag}`);
}
const parseIndexOfTag = function (hTag) {
const tokens = hTag.id.split('-');
return parseInt(tokens[tokens.length - 1]);
}
const markCurrentHTag = function (tocTag) {
removeAllClassOnTocTags('toc-active');
tocTag.classList.add('toc-active');
markParentHTagOf(tocTag);
}
const removeAllClassOnTocTags = function (className) {
Array.prototype.slice.call(tocElementsCard.children).forEach(child => {
child.classList.remove(className);
});
}
const markParentHTagOf = function (tocTag) {
const indexOfTocTag = parseIndexOfTag(tocTag);
const levelOfBaseTocTag = findLevelOfTocTag(tocTag);
removeAllClassOnTocTags('toc-parent-active');
compareLevelAndMark(levelOfBaseTocTag, indexOfTocTag);
}
/**
* 현재 active 태그의 부모 레벨 태그를 표시
* 기준 태그(active 태그)애서 하나씩 위로 올라가면서 부모 태그를 탐색 (재귀)
* */
const compareLevelAndMark = function (levelOfBaseTocTag, indexOfCurrentTocTag) {
if (levelOfBaseTocTag <= 1 || indexOfCurrentTocTag < 0) {
return;
}
const currentTocTag = document.querySelector(`#toc-${indexOfCurrentTocTag}`);
const levelOfCurrentTocTag = findLevelOfTocTag(currentTocTag);
if (levelOfBaseTocTag <= levelOfCurrentTocTag) {
return compareLevelAndMark(levelOfBaseTocTag, indexOfCurrentTocTag - 1);
}
currentTocTag.classList.add('toc-parent-active')
compareLevelAndMark(levelOfBaseTocTag - 1, indexOfCurrentTocTag - 1);
}
const findLevelOfTocTag = function (tocTag) {
const classes = tocTag.classList;
if (classes.contains('toc-level-4')) {
return 4;
}
if (classes.contains('toc-level-3')) {
return 3;
}
if (classes.contains('toc-level-2')) {
return 2;
}
return 1;
}
/**
* TOC 항목이 너무 많아 TOC Card에 스크롤이 생길 경우,
* 스크롤 이벤트에 따라 활성화된 TOC 태그가 보이도록 TOC Card의 스크롤도 함께 이동한다.
*/
const scrollToMainTocTag = function (tocTag) {
tocElementsCard.scroll({
top: tocTag.offsetTop - (tocTag.offsetParent.offsetHeight * 0.3),
behavior: 'smooth'
});
}
const detectTocCardPosition = function () {
const currentScrollTop = document.documentElement.scrollTop;
const footer = document.querySelector('#mEtc');
let footerTop = Number.MAX_SAFE_INTEGER;
if (footer) {
footerTop = footer.offsetTop;
}
const elementsCardBottom = currentScrollTop + tocElementsCard.offsetHeight;
tocElementsCard.classList.remove('toc-app-basic', 'toc-app-bottom');
if (elementsCardBottom >= footerTop) {
tocElementsCard.classList.add('toc-app-bottom');
return;
}
tocElementsCard.classList.add('toc-app-basic');
}
return {
checkExistenceOfHTags,
initTocElementsCard,
getLevelsByHighestTag,
registerTagsOnToc,
giveIdToHTags,
findCurrentHTag,
markCurrentHTag,
scrollToMainTocTag,
detectTocCardPosition,
registerTagsOnTocInArticle,
removeTOCInArticle,
addTOCInArticle
}
};
const tocCardController = new TocCardController();
const init = function () {
tocCardController.init();
};
const onscroll = function () {
tocCardController.onscroll();
}
const onresize = function () {
tocCardController.onresize();
}
return {
init,
onscroll,
onresize
}
})();
TOC_CARD.init();
/**
* scroll 시 현재 내용의 위치를 스크롤 이벤트를 통해 TOC에 표시해주기
*/
window.onscroll = function () {
TOC_CARD.onscroll();
}
window.onresize = function(){
TOC_CARD.onresize();
}
toc.css
추가한 부분
#wrap_toc{
padding-bottom: 1.3rem;
}
전체
/* custom card style */
.toc-app-common {
display: inline-block;
padding: 1rem;
}
.toc-app-basic {
position: fixed;
}
.toc-app-bottom {
position: absolute;
}
/* custom item style */
.toc-app-items {
height: 80vh;
width: 15.3rem;
overflow-y: scroll;
-ms-overflow-style: none;
}
.toc-app-items::-webkit-scrollbar {
display: none !important;
}
.toc-common {
display: block;
cursor: pointer;
color: grey;
}
.toc-level-1 {
font-size: 1.25rem;
margin-top: 0.5rem;
font-weight: bold;
}
.toc-level-2 {
font-size: 1.1rem;
margin-top: 0.3rem;
margin-left: 0.8rem;
}
.toc-level-3 {
font-size: 1.0rem;
margin-top: 0.3rem;
margin-left: 1.5rem;
}
.toc-level-4 {
font-size: 0.9rem;
margin-top: 0.3rem;
margin-left: 2.2rem;
}
.toc-active, .toc-common:hover {
color: #000;
}
.toc-parent-active {
color: #333;
}
@media (max-width: 90rem) {
.toc-app-common {
visibility: hidden;
}
}
#wrap_toc{
padding-bottom: 1.3rem;
}
</div>
가 나타난다.
이 위에 추가한다.
<div id="wrap_toc"><div id="toc-elements-in-article"></div></div>
<div class="article-view">
티스토리 블로그 글도 위키백과처럼 목차가 쭉 나오면 좋겠다는 생각이 들었다. 그렇게 되면 원하는 부분에 바로 가서 볼 수 있으니까.
먼저 찾아보기
내가 만들어도 좋지만, 누군가 이미 만들지 않았을까. github에서 찾아보니 있었다!
Tistory TOC(Table Of Contents) by wbluke박우빈님
아쉬운 점
아래 예처럼 화면이 클 때 왼쪽 편에 뜨는 형태였다.
모바일 화면에서도 차례가 나오면 좋겠다는 생각이 들었다. 모바일 화면에서는 글 시작할 때 뜨고, 큰 화면에서는 글 시작할 때 뜬 목차는 사라지고, 왼쪽 편에 뜨고.
개선 결과
아래 예처럼 화면을 작게 하면 글 안에 차례가 나타난다.
화면을 크게 하면 글 안의 차례는 사라지고, 왼쪽 편에 차례가 나타난다.
소스 파일
Tistory TOC(Table Of Contents) by wbluke님 코드를 바탕으로 약간 수정했다.
위 파일을 내려받자.
그리고 관리자 - 스킨편집 - html편집 - 파일업로드로 들어가서 추가 버튼을 눌러서 추가하자.
추가하고 나면 아래처럼 된다.
적용 방법
현재 적용한 스킨은 odessey이다.
스킨에 따라 적용 결과가 다를 수 있다.
HTML 편집 화면에 들어간다.
관리자 - 스킨편집 - html편집 - HTML이다.
1. head끝 지점 찾아서 css파일 추가
아래 예처럼 추가한다.
<link rel="stylesheet" href="./images/toc.css">
</head>
2. toc-elements div를 추가
<div id="container">를 찾아서 그 아랫줄에 <div id="toc-elements"></div>를 추가한다.
110~120번째 줄 쯤에 있다.
스킨마다 다른데 대체로 main 같은 이름으로 되어 있을 거다. 그 근처에 추가해보고 예상 결과와 다르다면 위치를 조금씩 달리해서 추가해보면 된다. 잘 모르겠다면 그냥 odessey스킨을 적용한 뒤에 따라하는 게 좋다.
<div id="container">
<div id="toc-elements"></div>
3. toc-elements-in-article 추가
<div id="wrap_toc"><div id="toc-elements-in-article"></div></div>를 추가해야 한다.
<div class="area-view">를 찾자.
아래 쪽으로 좀 내려가면
<div class="article-view">
</div>
가 나타난다.
이 위에 추가한다.
<div id="wrap_toc"><div id="toc-elements-in-article"></div></div>
<div class="article-view">
</div>
210~220번째 줄에 있다
4. script 추가
</body>위에 <script src="./images/toc.js"></script>를 추가한다.
맨 끝에 있다.
<script src="./images/toc.js"></script>
</body>
끝이다.
이제 각 블로그 글마다 차례가 나타날 것이다.
차례가 나타나지 않는 경우는 글 작성할 때 제목1, 제목2, 제목3을 사용하지 않은 경우다.
제목 스타일을 적용해야지만 차례가 나타난다.
코드
toc.js
추가한 부분
const TOC_CARD = (function () {
const TocCardController = function () {
const registerHTagsOnTocCard = function () {
tocCardService.registerTagsOnTocInArticle(levelMap);
}
const onresize = function () {
const width = document.body.clientWidth;
if(width > 1420){
tocCardService.removeTOCInArticle();
}else{
tocCardService.addTOCInArticle();
}
}
return {
onresize
};
};
const TocCardService = function () {
const tocElementsCardInArticle = document.querySelector('#toc-elements-in-article');
const wrapToc = document.querySelector('#wrap_toc');
const removeTOCInArticle = function(){
if(wrapToc.childNodes.length > 0){
wrapToc.removeChild(tocElementsCardInArticle);
}
}
const addTOCInArticle = function(){
if(wrapToc.childNodes.length === 0){
wrapToc.appendChild(tocElementsCardInArticle);
}
}
const registerTagsOnTocInArticle = function (levelMap) {
if(hTags.length > 0){
const tocH = document.createElement("h2");
tocH.append("차례");
tocElementsCardInArticle.appendChild(tocH);
}
hTags.forEach((hTag, indexOfHTag) => {
let hTagItem;
levelMap.forEach((level, key) => {
if (hTag.matches(`h${key}`)) {
hTagItem = createTagItemByLevel(level, hTag, indexOfHTag);
}
})
tocElementsCardInArticle.appendChild(hTagItem);
});
const width = document.body.clientWidth;
if(width > 1420){
removeTOCInArticle();
}
}
return {
registerTagsOnTocInArticle,
removeTOCInArticle,
addTOCInArticle
}
};
const onresize = function () {
tocCardController.onresize();
}
return {
onresize
}
})();
window.onresize = function(){
TOC_CARD.onresize();
}
전체
/*
* Tistory TOC (Table Of Contents)
* dev by wbluke (wbluke.com)
* last update 2020.05.06
* version 0.1.5
* https://github.com/wbluke/tistory-table-of-contents
* https://www.wbluke.com/21
*/
const CLASS_OF_MAIN_CONTENTS = '.article-view';
const CONSTANTS = (function () {
const KEY_OF_H1 = 1;
const KEY_OF_H2 = 2;
const KEY_OF_H3 = 3;
const KEY_OF_H4 = 4;
const LEVEL_1 = 1;
const LEVEL_2 = 2;
const LEVEL_3 = 3;
const LEVEL_4 = 4;
/* 최상위 태그에 따른 레벨 Map */
const levelsByH1 = function () {
return new Map([[KEY_OF_H1, LEVEL_1], [KEY_OF_H2, LEVEL_2], [KEY_OF_H3, LEVEL_3], [KEY_OF_H4, LEVEL_4]])
}
const levelsByH2 = function () {
return new Map([[KEY_OF_H2, LEVEL_1], [KEY_OF_H3, LEVEL_2], [KEY_OF_H4, LEVEL_3]])
}
const levelsByH3 = function () {
return new Map([[KEY_OF_H3, LEVEL_1], [KEY_OF_H4, LEVEL_2]])
}
const levelsByH4 = function () {
return new Map([[KEY_OF_H4, LEVEL_1]])
}
return {
indexOfH1: KEY_OF_H1,
indexOfH2: KEY_OF_H2,
indexOfH3: KEY_OF_H3,
indexOfH4: KEY_OF_H4,
levelsByH1: levelsByH1(),
levelsByH2: levelsByH2(),
levelsByH3: levelsByH3(),
levelsByH4: levelsByH4(),
}
})();
const TOC_CARD = (function () {
const TocCardController = function () {
const tocCardService = new TocCardService();
const initTocElementsCard = function () {
tocCardService.initTocElementsCard();
}
const giveIdToHTags = function () {
tocCardService.giveIdToHTags();
}
const registerHTagsOnTocCard = function () {
const levelMap = tocCardService.getLevelsByHighestTag();
tocCardService.registerTagsOnToc(levelMap);
tocCardService.registerTagsOnTocInArticle(levelMap);
}
const init = function () {
const existsHTags = tocCardService.checkExistenceOfHTags();
if (existsHTags) {
initTocElementsCard();
giveIdToHTags();
registerHTagsOnTocCard();
}
};
const onscroll = function () {
const tocTag = tocCardService.findCurrentHTag();
if (tocTag) {
tocCardService.markCurrentHTag(tocTag);
tocCardService.scrollToMainTocTag(tocTag);
tocCardService.detectTocCardPosition();
}
}
const onresize = function () {
const width = document.body.clientWidth;
if(width > 1420){
tocCardService.removeTOCInArticle();
}else{
tocCardService.addTOCInArticle();
}
}
return {
init,
onscroll,
onresize
};
};
const TocCardService = function () {
const tocElementsCard = document.querySelector('#toc-elements');
const tocElementsCardInArticle = document.querySelector('#toc-elements-in-article');
const wrapToc = document.querySelector('#wrap_toc');
const removeTOCInArticle = function(){
if(wrapToc.childNodes.length > 0){
wrapToc.removeChild(tocElementsCardInArticle);
}
}
const addTOCInArticle = function(){
if(wrapToc.childNodes.length === 0){
wrapToc.appendChild(tocElementsCardInArticle);
}
}
const mainContents = document.querySelector(CLASS_OF_MAIN_CONTENTS);
const hTags = (function () {
const foundHTags = mainContents.querySelectorAll('h1, h2, h3, h4');
/* 글 내용 밑에 있는 [...카테고리의 다른 글] h4 제거 */
return [...foundHTags].filter(hTag =>
!hTag.parentElement.classList.contains('another_category') &&
!hTag.parentElement.classList.contains('tags') &&
!hTag.parentElement.classList.contains('related-articles'));
})();
/* h1, h2, h3, h4 태그가 있는지 확인한다 */
const checkExistenceOfHTags = function () {
if (mainContents === undefined) {
return false;
}
return hTags.length !== 0;
}
const initTocElementsCard = function () {
tocElementsCard.classList.add('toc-app-common', 'toc-app-items', 'toc-app-basic');
}
/** 최상위 태그에 따른 레벨 Map 받아오기
*
* h1 ~ h4 태그 중 가장 높은 태그를 찾아서 그에 맞게 Level을 설정한다.
* 예를 들어, h1 태그가 없고 h2, h3 태그만 있는 경우
* h2가 가장 높은 태그이며, 해당 태그 h2에 LEVEL_1을 부여하고 그 다음 태그인 h3에는 LEVEL_2를 부여한다.
*
* 부여된 Level에 따라 적용되는 CSS가 달라진다.
* */
const getLevelsByHighestTag = function () {
const levelMapByHighestTag = {
'H1': CONSTANTS.levelsByH1,
'H2': CONSTANTS.levelsByH2,
'H3': CONSTANTS.levelsByH3,
};
return levelMapByHighestTag[findHighestHTag().tagName] || CONSTANTS.levelsByH4;
}
/* 최상위 태그 판별 작업 */
const findHighestHTag = function () {
return [...hTags].reduce((pre, cur) => {
const tagNumOfPre = parseInt(pre.tagName[1]);
const tagNumOfCur = parseInt(cur.tagName[1]);
return (tagNumOfPre < tagNumOfCur) ? pre : cur;
});
}
/* TOC에 태그 삽입 */
const registerTagsOnToc = function (levelMap) {
hTags.forEach((hTag, indexOfHTag) => {
let hTagItem;
levelMap.forEach((level, key) => {
if (hTag.matches(`h${key}`)) {
hTagItem = createTagItemByLevel(level, hTag, indexOfHTag);
}
})
tocElementsCard.appendChild(hTagItem);
});
}
const registerTagsOnTocInArticle = function (levelMap) {
if(hTags.length > 0){
const tocH = document.createElement("h2");
tocH.append("차례");
tocElementsCardInArticle.appendChild(tocH);
}
hTags.forEach((hTag, indexOfHTag) => {
let hTagItem;
levelMap.forEach((level, key) => {
if (hTag.matches(`h${key}`)) {
hTagItem = createTagItemByLevel(level, hTag, indexOfHTag);
}
})
tocElementsCardInArticle.appendChild(hTagItem);
});
const width = document.body.clientWidth;
if(width > 1420){
removeTOCInArticle();
}
}
const createTagItemByLevel = function (level = CONSTANTS.NUM_OF_H1, hTag, indexOfHTag) {
const basicItem = createBasicItemBy(hTag, indexOfHTag);
appendScrollEventsOn(basicItem, indexOfHTag);
basicItem.classList.add(`toc-level-${level}`);
return basicItem;
}
const createBasicItemBy = function (hTag, indexOfHTag) {
const basicItem = document.createElement('a');
basicItem.innerHTML += hTag.innerText;
basicItem.id = `toc-${indexOfHTag}`;
basicItem.classList = 'toc-common';
return basicItem;
}
const generateIdOfHTag = function (indexOfHTag) {
return 'h-tag-' + indexOfHTag;
}
const appendScrollEventsOn = function (basicItem, indexOfHTag) {
const target = document.querySelector('#' + generateIdOfHTag(indexOfHTag));
basicItem.addEventListener('click', () => window.scrollTo({
top: target.offsetTop - 10,
behavior: 'smooth'
}));
}
const giveIdToHTags = function () {
hTags.forEach((hTag, indexOfHTag) => {
hTag.id = generateIdOfHTag(indexOfHTag);
});
}
const findCurrentHTag = function () {
if (hTags.length == 0) {
return undefined;
}
const currentHTag = findCurrentMainHTag();
return findTocTagCorrespondingToHTag(currentHTag);
}
const findCurrentMainHTag = function () {
const headArea = document.querySelector('.area_head');
let headAreaHeight = 0;
if (headArea) {
headAreaHeight = headArea.offsetHeight;
}
const middleHeight = window.scrollY + (window.innerHeight / 2) - headAreaHeight;
return [...hTags].reduce((pre, cur) => {
if (middleHeight < pre.offsetTop && middleHeight < cur.offsetTop) {
return pre;
}
if (pre.offsetTop < middleHeight && middleHeight <= cur.offsetTop) {
return pre;
}
return cur;
});
}
const findTocTagCorrespondingToHTag = function (currentHTag) {
const indexOfHTag = parseIndexOfTag(currentHTag);
return document.querySelector(`#toc-${indexOfHTag}`);
}
const parseIndexOfTag = function (hTag) {
const tokens = hTag.id.split('-');
return parseInt(tokens[tokens.length - 1]);
}
const markCurrentHTag = function (tocTag) {
removeAllClassOnTocTags('toc-active');
tocTag.classList.add('toc-active');
markParentHTagOf(tocTag);
}
const removeAllClassOnTocTags = function (className) {
Array.prototype.slice.call(tocElementsCard.children).forEach(child => {
child.classList.remove(className);
});
}
const markParentHTagOf = function (tocTag) {
const indexOfTocTag = parseIndexOfTag(tocTag);
const levelOfBaseTocTag = findLevelOfTocTag(tocTag);
removeAllClassOnTocTags('toc-parent-active');
compareLevelAndMark(levelOfBaseTocTag, indexOfTocTag);
}
/**
* 현재 active 태그의 부모 레벨 태그를 표시
* 기준 태그(active 태그)애서 하나씩 위로 올라가면서 부모 태그를 탐색 (재귀)
* */
const compareLevelAndMark = function (levelOfBaseTocTag, indexOfCurrentTocTag) {
if (levelOfBaseTocTag <= 1 || indexOfCurrentTocTag < 0) {
return;
}
const currentTocTag = document.querySelector(`#toc-${indexOfCurrentTocTag}`);
const levelOfCurrentTocTag = findLevelOfTocTag(currentTocTag);
if (levelOfBaseTocTag <= levelOfCurrentTocTag) {
return compareLevelAndMark(levelOfBaseTocTag, indexOfCurrentTocTag - 1);
}
currentTocTag.classList.add('toc-parent-active')
compareLevelAndMark(levelOfBaseTocTag - 1, indexOfCurrentTocTag - 1);
}
const findLevelOfTocTag = function (tocTag) {
const classes = tocTag.classList;
if (classes.contains('toc-level-4')) {
return 4;
}
if (classes.contains('toc-level-3')) {
return 3;
}
if (classes.contains('toc-level-2')) {
return 2;
}
return 1;
}
/**
* TOC 항목이 너무 많아 TOC Card에 스크롤이 생길 경우,
* 스크롤 이벤트에 따라 활성화된 TOC 태그가 보이도록 TOC Card의 스크롤도 함께 이동한다.
*/
const scrollToMainTocTag = function (tocTag) {
tocElementsCard.scroll({
top: tocTag.offsetTop - (tocTag.offsetParent.offsetHeight * 0.3),
behavior: 'smooth'
});
}
const detectTocCardPosition = function () {
const currentScrollTop = document.documentElement.scrollTop;
const footer = document.querySelector('#mEtc');
let footerTop = Number.MAX_SAFE_INTEGER;
if (footer) {
footerTop = footer.offsetTop;
}
const elementsCardBottom = currentScrollTop + tocElementsCard.offsetHeight;
tocElementsCard.classList.remove('toc-app-basic', 'toc-app-bottom');
if (elementsCardBottom >= footerTop) {
tocElementsCard.classList.add('toc-app-bottom');
return;
}
tocElementsCard.classList.add('toc-app-basic');
}
return {
checkExistenceOfHTags,
initTocElementsCard,
getLevelsByHighestTag,
registerTagsOnToc,
giveIdToHTags,
findCurrentHTag,
markCurrentHTag,
scrollToMainTocTag,
detectTocCardPosition,
registerTagsOnTocInArticle,
removeTOCInArticle,
addTOCInArticle
}
};
const tocCardController = new TocCardController();
const init = function () {
tocCardController.init();
};
const onscroll = function () {
tocCardController.onscroll();
}
const onresize = function () {
tocCardController.onresize();
}
return {
init,
onscroll,
onresize
}
})();
TOC_CARD.init();
/**
* scroll 시 현재 내용의 위치를 스크롤 이벤트를 통해 TOC에 표시해주기
*/
window.onscroll = function () {
TOC_CARD.onscroll();
}
window.onresize = function(){
TOC_CARD.onresize();
}
toc.css
추가한 부분
#wrap_toc{
padding-bottom: 1.3rem;
}
전체
/* custom card style */
.toc-app-common {
display: inline-block;
padding: 1rem;
}
.toc-app-basic {
position: fixed;
}
.toc-app-bottom {
position: absolute;
}
/* custom item style */
.toc-app-items {
height: 80vh;
width: 15.3rem;
overflow-y: scroll;
-ms-overflow-style: none;
}
.toc-app-items::-webkit-scrollbar {
display: none !important;
}
.toc-common {
display: block;
cursor: pointer;
color: grey;
}
.toc-level-1 {
font-size: 1.25rem;
margin-top: 0.5rem;
font-weight: bold;
}
.toc-level-2 {
font-size: 1.1rem;
margin-top: 0.3rem;
margin-left: 0.8rem;
}
.toc-level-3 {
font-size: 1.0rem;
margin-top: 0.3rem;
margin-left: 1.5rem;
}
.toc-level-4 {
font-size: 0.9rem;
margin-top: 0.3rem;
margin-left: 2.2rem;
}
.toc-active, .toc-common:hover {
color: #000;
}
.toc-parent-active {
color: #333;
}
@media (max-width: 90rem) {
.toc-app-common {
visibility: hidden;
}
}
#wrap_toc{
padding-bottom: 1.3rem;
}
</div>
210~220번째 줄에 있다
4. script 추가
</body>위에 <script src="./images/toc.js"></script>를 추가한다.
맨 끝에 있다.
<script src="./images/toc.js"></script>
</body>
끝이다.
이제 각 블로그 글마다 차례가 나타날 것이다.
차례가 나타나지 않는 경우는 글 작성할 때 제목1, 제목2, 제목3을 사용하지 않은 경우다.
제목 스타일을 적용해야지만 차례가 나타난다.
코드
toc.js
추가한 부분
const TOC_CARD = (function () {
const TocCardController = function () {
const registerHTagsOnTocCard = function () {
tocCardService.registerTagsOnTocInArticle(levelMap);
}
const onresize = function () {
const width = document.body.clientWidth;
if(width > 1420){
tocCardService.removeTOCInArticle();
}else{
tocCardService.addTOCInArticle();
}
}
return {
onresize
};
};
const TocCardService = function () {
const tocElementsCardInArticle = document.querySelector('#toc-elements-in-article');
const wrapToc = document.querySelector('#wrap_toc');
const removeTOCInArticle = function(){
if(wrapToc.childNodes.length > 0){
wrapToc.removeChild(tocElementsCardInArticle);
}
}
const addTOCInArticle = function(){
if(wrapToc.childNodes.length === 0){
wrapToc.appendChild(tocElementsCardInArticle);
}
}
const registerTagsOnTocInArticle = function (levelMap) {
if(hTags.length > 0){
const tocH = document.createElement("h2");
tocH.append("차례");
tocElementsCardInArticle.appendChild(tocH);
}
hTags.forEach((hTag, indexOfHTag) => {
let hTagItem;
levelMap.forEach((level, key) => {
if (hTag.matches(`h${key}`)) {
hTagItem = createTagItemByLevel(level, hTag, indexOfHTag);
}
})
tocElementsCardInArticle.appendChild(hTagItem);
});
const width = document.body.clientWidth;
if(width > 1420){
removeTOCInArticle();
}
}
return {
registerTagsOnTocInArticle,
removeTOCInArticle,
addTOCInArticle
}
};
const onresize = function () {
tocCardController.onresize();
}
return {
onresize
}
})();
window.onresize = function(){
TOC_CARD.onresize();
}
전체
/*
* Tistory TOC (Table Of Contents)
* dev by wbluke (wbluke.com)
* last update 2020.05.06
* version 0.1.5
* https://github.com/wbluke/tistory-table-of-contents
* https://www.wbluke.com/21
*/
const CLASS_OF_MAIN_CONTENTS = '.article-view';
const CONSTANTS = (function () {
const KEY_OF_H1 = 1;
const KEY_OF_H2 = 2;
const KEY_OF_H3 = 3;
const KEY_OF_H4 = 4;
const LEVEL_1 = 1;
const LEVEL_2 = 2;
const LEVEL_3 = 3;
const LEVEL_4 = 4;
/* 최상위 태그에 따른 레벨 Map */
const levelsByH1 = function () {
return new Map([[KEY_OF_H1, LEVEL_1], [KEY_OF_H2, LEVEL_2], [KEY_OF_H3, LEVEL_3], [KEY_OF_H4, LEVEL_4]])
}
const levelsByH2 = function () {
return new Map([[KEY_OF_H2, LEVEL_1], [KEY_OF_H3, LEVEL_2], [KEY_OF_H4, LEVEL_3]])
}
const levelsByH3 = function () {
return new Map([[KEY_OF_H3, LEVEL_1], [KEY_OF_H4, LEVEL_2]])
}
const levelsByH4 = function () {
return new Map([[KEY_OF_H4, LEVEL_1]])
}
return {
indexOfH1: KEY_OF_H1,
indexOfH2: KEY_OF_H2,
indexOfH3: KEY_OF_H3,
indexOfH4: KEY_OF_H4,
levelsByH1: levelsByH1(),
levelsByH2: levelsByH2(),
levelsByH3: levelsByH3(),
levelsByH4: levelsByH4(),
}
})();
const TOC_CARD = (function () {
const TocCardController = function () {
const tocCardService = new TocCardService();
const initTocElementsCard = function () {
tocCardService.initTocElementsCard();
}
const giveIdToHTags = function () {
tocCardService.giveIdToHTags();
}
const registerHTagsOnTocCard = function () {
const levelMap = tocCardService.getLevelsByHighestTag();
tocCardService.registerTagsOnToc(levelMap);
tocCardService.registerTagsOnTocInArticle(levelMap);
}
const init = function () {
const existsHTags = tocCardService.checkExistenceOfHTags();
if (existsHTags) {
initTocElementsCard();
giveIdToHTags();
registerHTagsOnTocCard();
}
};
const onscroll = function () {
const tocTag = tocCardService.findCurrentHTag();
if (tocTag) {
tocCardService.markCurrentHTag(tocTag);
tocCardService.scrollToMainTocTag(tocTag);
tocCardService.detectTocCardPosition();
}
}
const onresize = function () {
const width = document.body.clientWidth;
if(width > 1420){
tocCardService.removeTOCInArticle();
}else{
tocCardService.addTOCInArticle();
}
}
return {
init,
onscroll,
onresize
};
};
const TocCardService = function () {
const tocElementsCard = document.querySelector('#toc-elements');
const tocElementsCardInArticle = document.querySelector('#toc-elements-in-article');
const wrapToc = document.querySelector('#wrap_toc');
const removeTOCInArticle = function(){
if(wrapToc.childNodes.length > 0){
wrapToc.removeChild(tocElementsCardInArticle);
}
}
const addTOCInArticle = function(){
if(wrapToc.childNodes.length === 0){
wrapToc.appendChild(tocElementsCardInArticle);
}
}
const mainContents = document.querySelector(CLASS_OF_MAIN_CONTENTS);
const hTags = (function () {
const foundHTags = mainContents.querySelectorAll('h1, h2, h3, h4');
/* 글 내용 밑에 있는 [...카테고리의 다른 글] h4 제거 */
return [...foundHTags].filter(hTag =>
!hTag.parentElement.classList.contains('another_category') &&
!hTag.parentElement.classList.contains('tags') &&
!hTag.parentElement.classList.contains('related-articles'));
})();
/* h1, h2, h3, h4 태그가 있는지 확인한다 */
const checkExistenceOfHTags = function () {
if (mainContents === undefined) {
return false;
}
return hTags.length !== 0;
}
const initTocElementsCard = function () {
tocElementsCard.classList.add('toc-app-common', 'toc-app-items', 'toc-app-basic');
}
/** 최상위 태그에 따른 레벨 Map 받아오기
*
* h1 ~ h4 태그 중 가장 높은 태그를 찾아서 그에 맞게 Level을 설정한다.
* 예를 들어, h1 태그가 없고 h2, h3 태그만 있는 경우
* h2가 가장 높은 태그이며, 해당 태그 h2에 LEVEL_1을 부여하고 그 다음 태그인 h3에는 LEVEL_2를 부여한다.
*
* 부여된 Level에 따라 적용되는 CSS가 달라진다.
* */
const getLevelsByHighestTag = function () {
const levelMapByHighestTag = {
'H1': CONSTANTS.levelsByH1,
'H2': CONSTANTS.levelsByH2,
'H3': CONSTANTS.levelsByH3,
};
return levelMapByHighestTag[findHighestHTag().tagName] || CONSTANTS.levelsByH4;
}
/* 최상위 태그 판별 작업 */
const findHighestHTag = function () {
return [...hTags].reduce((pre, cur) => {
const tagNumOfPre = parseInt(pre.tagName[1]);
const tagNumOfCur = parseInt(cur.tagName[1]);
return (tagNumOfPre < tagNumOfCur) ? pre : cur;
});
}
/* TOC에 태그 삽입 */
const registerTagsOnToc = function (levelMap) {
hTags.forEach((hTag, indexOfHTag) => {
let hTagItem;
levelMap.forEach((level, key) => {
if (hTag.matches(`h${key}`)) {
hTagItem = createTagItemByLevel(level, hTag, indexOfHTag);
}
})
tocElementsCard.appendChild(hTagItem);
});
}
const registerTagsOnTocInArticle = function (levelMap) {
if(hTags.length > 0){
const tocH = document.createElement("h2");
tocH.append("차례");
tocElementsCardInArticle.appendChild(tocH);
}
hTags.forEach((hTag, indexOfHTag) => {
let hTagItem;
levelMap.forEach((level, key) => {
if (hTag.matches(`h${key}`)) {
hTagItem = createTagItemByLevel(level, hTag, indexOfHTag);
}
})
tocElementsCardInArticle.appendChild(hTagItem);
});
const width = document.body.clientWidth;
if(width > 1420){
removeTOCInArticle();
}
}
const createTagItemByLevel = function (level = CONSTANTS.NUM_OF_H1, hTag, indexOfHTag) {
const basicItem = createBasicItemBy(hTag, indexOfHTag);
appendScrollEventsOn(basicItem, indexOfHTag);
basicItem.classList.add(`toc-level-${level}`);
return basicItem;
}
const createBasicItemBy = function (hTag, indexOfHTag) {
const basicItem = document.createElement('a');
basicItem.innerHTML += hTag.innerText;
basicItem.id = `toc-${indexOfHTag}`;
basicItem.classList = 'toc-common';
return basicItem;
}
const generateIdOfHTag = function (indexOfHTag) {
return 'h-tag-' + indexOfHTag;
}
const appendScrollEventsOn = function (basicItem, indexOfHTag) {
const target = document.querySelector('#' + generateIdOfHTag(indexOfHTag));
basicItem.addEventListener('click', () => window.scrollTo({
top: target.offsetTop - 10,
behavior: 'smooth'
}));
}
const giveIdToHTags = function () {
hTags.forEach((hTag, indexOfHTag) => {
hTag.id = generateIdOfHTag(indexOfHTag);
});
}
const findCurrentHTag = function () {
if (hTags.length == 0) {
return undefined;
}
const currentHTag = findCurrentMainHTag();
return findTocTagCorrespondingToHTag(currentHTag);
}
const findCurrentMainHTag = function () {
const headArea = document.querySelector('.area_head');
let headAreaHeight = 0;
if (headArea) {
headAreaHeight = headArea.offsetHeight;
}
const middleHeight = window.scrollY + (window.innerHeight / 2) - headAreaHeight;
return [...hTags].reduce((pre, cur) => {
if (middleHeight < pre.offsetTop && middleHeight < cur.offsetTop) {
return pre;
}
if (pre.offsetTop < middleHeight && middleHeight <= cur.offsetTop) {
return pre;
}
return cur;
});
}
const findTocTagCorrespondingToHTag = function (currentHTag) {
const indexOfHTag = parseIndexOfTag(currentHTag);
return document.querySelector(`#toc-${indexOfHTag}`);
}
const parseIndexOfTag = function (hTag) {
const tokens = hTag.id.split('-');
return parseInt(tokens[tokens.length - 1]);
}
const markCurrentHTag = function (tocTag) {
removeAllClassOnTocTags('toc-active');
tocTag.classList.add('toc-active');
markParentHTagOf(tocTag);
}
const removeAllClassOnTocTags = function (className) {
Array.prototype.slice.call(tocElementsCard.children).forEach(child => {
child.classList.remove(className);
});
}
const markParentHTagOf = function (tocTag) {
const indexOfTocTag = parseIndexOfTag(tocTag);
const levelOfBaseTocTag = findLevelOfTocTag(tocTag);
removeAllClassOnTocTags('toc-parent-active');
compareLevelAndMark(levelOfBaseTocTag, indexOfTocTag);
}
/**
* 현재 active 태그의 부모 레벨 태그를 표시
* 기준 태그(active 태그)애서 하나씩 위로 올라가면서 부모 태그를 탐색 (재귀)
* */
const compareLevelAndMark = function (levelOfBaseTocTag, indexOfCurrentTocTag) {
if (levelOfBaseTocTag <= 1 || indexOfCurrentTocTag < 0) {
return;
}
const currentTocTag = document.querySelector(`#toc-${indexOfCurrentTocTag}`);
const levelOfCurrentTocTag = findLevelOfTocTag(currentTocTag);
if (levelOfBaseTocTag <= levelOfCurrentTocTag) {
return compareLevelAndMark(levelOfBaseTocTag, indexOfCurrentTocTag - 1);
}
currentTocTag.classList.add('toc-parent-active')
compareLevelAndMark(levelOfBaseTocTag - 1, indexOfCurrentTocTag - 1);
}
const findLevelOfTocTag = function (tocTag) {
const classes = tocTag.classList;
if (classes.contains('toc-level-4')) {
return 4;
}
if (classes.contains('toc-level-3')) {
return 3;
}
if (classes.contains('toc-level-2')) {
return 2;
}
return 1;
}
/**
* TOC 항목이 너무 많아 TOC Card에 스크롤이 생길 경우,
* 스크롤 이벤트에 따라 활성화된 TOC 태그가 보이도록 TOC Card의 스크롤도 함께 이동한다.
*/
const scrollToMainTocTag = function (tocTag) {
tocElementsCard.scroll({
top: tocTag.offsetTop - (tocTag.offsetParent.offsetHeight * 0.3),
behavior: 'smooth'
});
}
const detectTocCardPosition = function () {
const currentScrollTop = document.documentElement.scrollTop;
const footer = document.querySelector('#mEtc');
let footerTop = Number.MAX_SAFE_INTEGER;
if (footer) {
footerTop = footer.offsetTop;
}
const elementsCardBottom = currentScrollTop + tocElementsCard.offsetHeight;
tocElementsCard.classList.remove('toc-app-basic', 'toc-app-bottom');
if (elementsCardBottom >= footerTop) {
tocElementsCard.classList.add('toc-app-bottom');
return;
}
tocElementsCard.classList.add('toc-app-basic');
}
return {
checkExistenceOfHTags,
initTocElementsCard,
getLevelsByHighestTag,
registerTagsOnToc,
giveIdToHTags,
findCurrentHTag,
markCurrentHTag,
scrollToMainTocTag,
detectTocCardPosition,
registerTagsOnTocInArticle,
removeTOCInArticle,
addTOCInArticle
}
};
const tocCardController = new TocCardController();
const init = function () {
tocCardController.init();
};
const onscroll = function () {
tocCardController.onscroll();
}
const onresize = function () {
tocCardController.onresize();
}
return {
init,
onscroll,
onresize
}
})();
TOC_CARD.init();
/**
* scroll 시 현재 내용의 위치를 스크롤 이벤트를 통해 TOC에 표시해주기
*/
window.onscroll = function () {
TOC_CARD.onscroll();
}
window.onresize = function(){
TOC_CARD.onresize();
}
toc.css
추가한 부분
#wrap_toc{
padding-bottom: 1.3rem;
}
전체
/* custom card style */
.toc-app-common {
display: inline-block;
padding: 1rem;
}
.toc-app-basic {
position: fixed;
}
.toc-app-bottom {
position: absolute;
}
/* custom item style */
.toc-app-items {
height: 80vh;
width: 15.3rem;
overflow-y: scroll;
-ms-overflow-style: none;
}
.toc-app-items::-webkit-scrollbar {
display: none !important;
}
.toc-common {
display: block;
cursor: pointer;
color: grey;
}
.toc-level-1 {
font-size: 1.25rem;
margin-top: 0.5rem;
font-weight: bold;
}
.toc-level-2 {
font-size: 1.1rem;
margin-top: 0.3rem;
margin-left: 0.8rem;
}
.toc-level-3 {
font-size: 1.0rem;
margin-top: 0.3rem;
margin-left: 1.5rem;
}
.toc-level-4 {
font-size: 0.9rem;
margin-top: 0.3rem;
margin-left: 2.2rem;
}
.toc-active, .toc-common:hover {
color: #000;
}
.toc-parent-active {
color: #333;
}
@media (max-width: 90rem) {
.toc-app-common {
visibility: hidden;
}
}
#wrap_toc{
padding-bottom: 1.3rem;
}