본문 바로가기
IT

티스토리 자동 목차 만들기: JS & CSS 코드로 5분 완성!

by knowhackking 2025. 9. 8.

티스토리 블로그에 방문자를 사로잡는 자동 목차를 만드는 방법을 알려드립니다. JS, CSS 코드를 활용해 손쉽게 적용하고, 어떤 스킨이든 응용할 수 있는 팁까지 담았습니다. 블로그 가독성을 높여 체류 시간을 늘리고 애드센스 수익까지 극대화해보세요.

티스토리 자동 목차 만들기 메인 이미지

 

블로그 포스팅, 길고 복잡한 글일수록 독자들이 쉽게 읽기 어렵습니다. 이럴 때 '자동 목차'는 글의 가독성을 확 높여주고 독자 이탈을 막는 강력한 도구가 됩니다. 심지어 구글 검색엔진 최적화(SEO)에도 큰 도움이 되는데요.

이번 글에서는 여러분의 티스토리 블로그에 JS(자바스크립트)와 CSS 코드를 활용해 자동으로 목차를 생성하는 방법을 아주 쉽고 간단하게 알려드리겠습니다. 필자는 티스토리 Book Club 스킨을 사용 중이지만, 다른 스킨에서도 약간의 수정만으로 충분히 적용 가능하니 걱정 마세요!

 

왜 티스토리 블로그에 자동 목차를 만들어야 할까요?

목차는 단순히 보기 좋으라고 만드는 것이 아닙니다. 블로그 운영에 있어 실질적인 이점을 제공합니다.

사용자 경험(UX) 개선: 긴 글을 스크롤하며 필요한 정보를 찾는 독자에게 목차는 길잡이 역할을 합니다. 원하는 섹션으로 바로 이동할 수 있어 편의성이 극대화되고, 이는 곧 독자의 블로그 체류 시간 증가로 이어집니다.

SEO 효과 증대: 구글 검색 봇은 목차를 글의 구조와 핵심 내용을 파악하는 중요한 요소로 인식합니다. 잘 만들어진 목차는 검색 결과에 '색인' 형태로 노출될 확률을 높여 클릭률을 높이는 효과를 줍니다.

광고 수익 극대화: 사용자가 블로그에 오래 머물수록 애드센스 광고를 볼 기회가 많아집니다. 가독성이 높아지면 자연스럽게 글을 끝까지 읽게 되고, 이 과정에서 광고 클릭으로 이어질 확률도 높아집니다.

 

티스토리 자동 목차 적용 방법: HTML & 스크립트 수정

자, 이제 본격적으로 코드를 적용해볼 차례입니다. 티스토리 '스킨 편집' 메뉴에서 간단하게 코드를 추가하면 됩니다.

1. 스킨 편집 > HTML 편집 메뉴로 이동하기
먼저 티스토리 블로그 관리 페이지로 이동하여 꾸미기 > 스킨 편집 > html 편집 메뉴를 클릭합니다. 이 곳에서 블로그의 HTML과 CSS 코드를 직접 수정할 수 있습니다.

티스토리 관리 메뉴 아래 꾸미기 메뉴 이미지
자동 스킨 편집 화면 이미지



2. JS(자바스크립트) 코드 추가하기
아래 코드는 블로그 글의 소제목(H2, H3, H4 등)을 자동으로 인식하여 목차를 생성하는 스크립트입니다. 이 코드를 </body> 태그 바로 위에 붙여넣어 주세요.

스킨 편집에서 html 수정 탭 이미지

<script>
document.addEventListener("DOMContentLoaded", function() {
    const tocContainer = document.querySelector('.contents_style');
    if (!tocContainer) return;

    let toc; // 원본 목차 엘리먼트
    let floatingToc; // 복제된 플로팅 목차 엘리먼트
    let headings; // 제목 엘리먼트 목록

    /**
     * 목차(Table of Contents)를 생성하고 페이지에 삽입합니다.
     */
    function createToc() {
        headings = tocContainer.querySelectorAll('h2, h3, h4');
        if (headings.length < 2) return;

        toc = document.createElement('nav');
        toc.id = 'table-of-contents';
        toc.setAttribute('aria-label', 'Table of Contents');

        const tocHeader = document.createElement('div');
        tocHeader.id = 'toc-header';

        const tocLabel = document.createElement('div');
        tocLabel.id = 'toc-label';
        tocLabel.innerText = '목차';
        tocHeader.appendChild(tocLabel);

        const toggleButton = document.createElement('button');
        toggleButton.id = 'toc-toggle-button';
        toggleButton.setAttribute('aria-label', '목차 접기/펴기');
        const iconUp = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="18 15 12 9 6 15"></polyline></svg>`;
        const iconDown = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>`;
        toggleButton.innerHTML = iconUp;
        tocHeader.appendChild(toggleButton);

        toc.appendChild(tocHeader);

        const ul = document.createElement('ul');

        toggleButton.addEventListener('click', () => {
            toc.classList.toggle('collapsed');
            if (toc.classList.contains('collapsed')) {
                toggleButton.innerHTML = iconDown;
            } else {
                toggleButton.innerHTML = iconUp;
            }
        });

        const counter = { h2: 0, h3: 0, h4: 0 };

        headings.forEach(heading => {
            if (!heading.innerText || heading.innerText.trim() === '') {
                return;
            }
            const level = heading.tagName.toLowerCase();
            
            if (level === 'h2') {
                counter.h2++;
                counter.h3 = 0;
                counter.h4 = 0;
            } else if (level === 'h3') {
                counter.h3++;
                counter.h4 = 0;
            } else if (level === 'h4') {
                counter.h4++;
            }

            const sectionNumber = `${counter.h2}${counter.h3 ? `.${counter.h3}` : ''}${counter.h4 ? `.${counter.h4}` : ''}`;
            const headingId = `heading-${sectionNumber.replace(/\./g, '-')}`;
            
            heading.id = headingId;
            heading.setAttribute('itemprop', 'name');

            const li = document.createElement('li');
            li.setAttribute('itemprop', 'name');
            if (level === 'h2') li.classList.add('toc-h2');
            if (level === 'h3') li.classList.add('toc-h3');
            if (level === 'h4') li.classList.add('toc-h4');

            const anchor = document.createElement('a');
            anchor.href = `#${headingId}`;
            anchor.innerText = `${sectionNumber} ${heading.innerText}`;
            anchor.setAttribute('itemprop', 'url');
            anchor.addEventListener('click', function(e) {
                e.preventDefault();
                document.querySelector(this.getAttribute('href')).scrollIntoView({ behavior: 'smooth' });
            });

            li.appendChild(anchor);
            ul.appendChild(li);
        });

        toc.appendChild(ul);

        const firstH2 = tocContainer.querySelector('h2');
        if (firstH2) {
            firstH2.parentNode.insertBefore(toc, firstH2);
        }
    }

    /**
     * 스크롤 위치에 따라 현재 활성화된 목차 링크를 업데이트합니다.
     */
    function updateActiveTocLink() {
        if (!headings) return;

        let currentHeadingId = '';
        const offset = window.innerHeight * 0.2; // 화면 상단 20% 지점을 기준으로

        headings.forEach(heading => {
            const rect = heading.getBoundingClientRect();
            if (rect.top <= offset) {
                currentHeadingId = heading.id;
            }
        });

        const allTocLinks = document.querySelectorAll('#table-of-contents a, #floating-table-of-contents a');
        allTocLinks.forEach(link => {
            const li = link.parentElement;
            const href = link.getAttribute('href');
            if (href === `#${currentHeadingId}`) {
                li.classList.add('active');
            } else {
                li.classList.remove('active');
            }
        });
    }

    /**
     * 플로팅 목차 버튼 및 관련 이벤트를 설정합니다.
     */
    function setupFloatingToc() {
        if (!toc) return;

        floatingToc = toc.cloneNode(true);
        floatingToc.id = 'floating-table-of-contents';

        // 기존 헤더 제거
        const originalHeader = floatingToc.querySelector('#toc-header');
        if (originalHeader) {
            originalHeader.remove();
        }

        // 새로운 플로팅 TOC 헤더 생성
        const floatingTocHeader = document.createElement('div');
        floatingTocHeader.id = 'floating-toc-header';

        const contentsLabel = document.createElement('span');
        contentsLabel.textContent = 'Contents';
        floatingTocHeader.appendChild(contentsLabel);

        const closeButton = document.createElement('button');
        closeButton.id = 'floating-toc-close-button';
        closeButton.innerHTML = '&times;'; // 'X' 모양
        closeButton.setAttribute('aria-label', 'Close Table of Contents');
        closeButton.onclick = () => floatingToc.classList.remove('is-visible');
        floatingTocHeader.appendChild(closeButton);

        // ul 목록 앞에 새로운 헤더 삽입
        const ul = floatingToc.querySelector('ul');
        if (ul) {
            floatingToc.insertBefore(floatingTocHeader, ul);
        } else {
            floatingToc.appendChild(floatingTocHeader);
        }

        document.body.appendChild(floatingToc);

        const tocButton = document.createElement('div');
        tocButton.id = 'toc-button';
        tocButton.innerHTML = `<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><g stroke="currentColor" stroke-width="10" fill="none"><path d="M 25,30 H 75" /><path d="M 25,50 H 75" /><path d="M 25,70 H 75" /></g><g font-family="monospace" font-size="30" stroke="none" fill="currentColor" text-anchor="middle" dominant-baseline="central"><text x="12" y="30">1</text><text x="12" y="50">2</text><text x="12" y="70">3</text></g></svg>`;
        document.body.appendChild(tocButton);

        const updateTocButtonPosition = () => {
            const rect = tocContainer.getBoundingClientRect();
            if (window.innerWidth > 1024) {
                tocButton.style.left = `${rect.right + 10}px`;
                tocButton.style.right = 'auto';
            } else {
                tocButton.style.left = 'auto';
                tocButton.style.right = '';
            }
        };

        const updateFloatingTocPosition = () => {
            if (floatingToc.classList.contains('is-visible')) {
                const buttonRect = tocButton.getBoundingClientRect();
                if (window.innerWidth > 1024) {
                    // PC: 버튼 오른쪽에 위치
                    floatingToc.style.left = `${buttonRect.right + 10}px`;
                    floatingToc.style.top = `${buttonRect.top}px`;
                    floatingToc.style.right = 'auto';
                    floatingToc.style.bottom = 'auto';
                    floatingToc.style.transform = 'none';
                } else {
                    // 모바일: 화면 오른쪽에 붙고, 버튼 위쪽에 위치
                    floatingToc.style.right = '10px';
                    floatingToc.style.left = 'auto';
                    floatingToc.style.bottom = `${window.innerHeight - buttonRect.top + 10}px`; // 버튼 상단에서 10px 위
                    floatingToc.style.top = 'auto';
                    floatingToc.style.transform = 'none';
                }
            }
        };

        tocButton.addEventListener('click', (e) => {
            e.preventDefault();
            e.stopPropagation();
            floatingToc.classList.toggle('is-visible');
            updateFloatingTocPosition();
        });

        window.addEventListener('scroll', () => {
            const tocRect = toc.getBoundingClientRect();
            if (tocRect.bottom < 0) {
                tocButton.style.display = 'flex';
            } else {
                tocButton.style.display = 'none';
                floatingToc.classList.remove('is-visible');
            }
            updateFloatingTocPosition();
            updateActiveTocLink(); // 스크롤 시 하이라이트 업데이트
        });
        
        const updateAllPositions = () => {
            updateTocButtonPosition();
            updateFloatingTocPosition();
        };

        window.addEventListener('resize', updateAllPositions);
        updateAllPositions();
        updateActiveTocLink(); // 초기 로드 시 하이라이트 설정
    }

    /**
     * 초기화 함수
     */
    function init() {
        createToc();
        setupFloatingToc();
    }

    init();
});
</script>

 

3. CSS 코드 추가하기
목차를 보기 좋게 꾸며주는 CSS 코드입니다. CSS 탭을 클릭한 후 아래 코드를 맨 하단에 추가해 주세요. 색상이나 여백 등은 자유롭게 수정해서 사용하세요.

스킨 편집에서 css 수정 탭 이미지

/* ==========================================================================
   Table of Contents (TOC) - 기본 스타일
   ========================================================================== */

#table-of-contents {
  background-color: #f8f9fa;
  border-radius: 10px;
  padding: 20px;
  margin-bottom: 30px;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
  transition: padding 0.3s ease, margin-bottom 0.3s ease;
}

#table-of-contents.collapsed {
  padding-top: 15px;
  padding-bottom: 15px;
  margin-bottom: 20px;
}

#toc-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 15px;
  transition: margin-bottom 0.3s ease;
}

#table-of-contents.collapsed #toc-header {
  margin-bottom: 0;
}

#toc-label {
  font-weight: 700;
  font-size: 22px;
  color: #333;
  padding-bottom: 5px;
  border-bottom: 4px solid #007bff;
}

#toc-toggle-button {
  background: transparent;
  border: none;
  cursor: pointer;
  padding: 5px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #6c757d;
}

#toc-toggle-button:hover {
  color: #343a40;
}

#toc-toggle-button svg {
  width: 20px;
  height: 20px;
}

#table-of-contents.collapsed ul {
  display: none;
}

#table-of-contents ul,
#floating-table-of-contents ul {
  list-style: none;
  padding: 0;
  margin-left: 0; /* ul 전체 좌측 마진 제거 */
  margin-top: 0; /* 헤더와의 간격 조절 */
}

/* 플로팅 목차의 ul에는 margin-top을 다시 설정 */
/* 플로팅 목차의 ul에는 margin-top을 다시 설정 */
#floating-table-of-contents ul {
  margin-top: 15px;
}

/* 플로팅 목차 헤더 */
#floating-toc-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 15px;
  font-weight: bold;
  font-size: 1.2em;
}

/* 플로팅 목차 닫기 버튼 */
#floating-toc-close-button {
  background: none;
  border: none;
  font-size: 24px;
  cursor: pointer;
  color: #888;
  padding: 0 5px;
  line-height: 1;
}

#floating-toc-close-button:hover {
  color: #333;
}


#table-of-contents ul li,
#floating-table-of-contents ul li {
  margin-bottom: 10px;
  position: relative;
}

#table-of-contents ul li a,
#floating-table-of-contents ul li a {
  color: #333;
  text-decoration: none;
  transition: color 0.3s ease, background-color 0.3s ease;
  display: block;
  padding: 4px 8px;
  border-radius: 4px;
  margin: 0 -8px;
}

#table-of-contents ul li a:hover,
#floating-table-of-contents ul li a:hover {
  color: #007bff;
  background-color: #e9ecef;
}

#table-of-contents ul li.active > a,
#floating-table-of-contents ul li.active > a {
  background-color: #dee2e6;
  color: #212529;
  font-weight: 600;
}

/* 레벨별 들여쓰기 (padding-left 사용) */
#table-of-contents ul li.toc-h2,
#floating-table-of-contents ul li.toc-h2 {
  padding-left: 0;
}

#table-of-contents ul li.toc-h3,
#floating-table-of-contents ul li.toc-h3 {
  padding-left: 20px;
}

#table-of-contents ul li.toc-h4,
#floating-table-of-contents ul li.toc-h4 {
  padding-left: 40px;
}

/* ==========================================================================
   플로팅 TOC 및 리모컨 버튼
   ========================================================================== */

/* 리모컨 버튼 */
#toc-button {
  position: fixed;
  top: 40%;
  right: 10px;
  width: 45px;
  height: 45px;
  background: #ffffff;
  border: 2px solid #555;
  border-radius: 4px;
  padding: 5px;
  color: #333;
  cursor: pointer;
  z-index: 9999;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
  display: none; /* JS로 제어 */
  justify-content: center;
  align-items: center;
}

/* 플로팅 목차 (복제본) */
#floating-table-of-contents {
  display: none; /* 기본적으로 숨김 */
  position: fixed;
  width: 250px;
  max-height: 60%;
  overflow-y: auto;
  z-index: 9998;
  background-color: #f8f9fa;
  border-radius: 8px;
  padding: 20px;
  box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
  border: 1px solid #e9ecef;
  /* 위치는 JS로 동적 제어 */
}

#floating-table-of-contents.is-visible {
  display: block; /* JS로 제어하여 표시 */
}


/* ==========================================================================
   미디어 쿼리 - 반응형 스타일
   ========================================================================== */

/* 태블릿 (1023px 이하) */
@media (max-width: 1023px) {
  #toc-button {
    top: 35%;
    right: 8px;
    width: 40px;
    height: 40px;
    padding: 4px;
  }
}

/* 모바일 (767px 이하) */
@media (max-width: 767px) {
  #toc-button {
    top: auto;
    bottom: 20%;
    right: 5px;
    width: 38px;
    height: 38px;
    padding: 3px;
  }

  #floating-table-of-contents {
    width: 280px; /* 모바일에서 너비 조정 */
  }
}

수정을 모두 마쳤으면 '적용' 버튼을 누르세요.

 

마무리 & 추가 팁

이제 블로그 글을 작성할 때 H2, H3, H4 등의 제목 태그만 잘 사용하면 글 상단에 자동으로 멋진 목차가 생성됩니다. 목차가 정상적으로 작동하는지 확인하고, 혹시 문제가 있다면 '캐시 삭제''새로고침'을 해보세요.

티스토리 자동 목차는 블로그의 전문성을 높이고 독자에게 좋은 인상을 남깁니다. 오늘 알려드린 방법으로 블로그 가독성과 SEO를 모두 잡으시길 바랍니다.

도움이 되셨다면 이 글을 주변 블로거들에게 공유하는 것도 좋은 방법입니다.

 

참고 사이트

티스토리 목차 생성기(TTG) 공식 웹사이트

이 사이트에서 TTG 확장 프로그램의 주요 기능, 사용법, 스타일(CSS) 적용 방법 등을 확인할 수 있습니다.

 

티스토리 목차 생성기(TTG) 소개(확장 프로그램)

티스토리 목차 생성기(TTG)는 티스토리 글쓰기 에디터에서 목차를 손쉽게 생성할 수 있도록 도와주는 크롬 및 웨일, 엣지 브라우저 확장 프로그램입니다.

jioscon.com

 

함께 보면 좋은 글

 

구글 나노바나나 정체가 밝혀졌다? 제미나이 2.5 플래시 이미지의 놀라운 기능

'구글 나노바나나'의 정체가 마침내 밝혀졌습니다. 구글의 최신 AI 이미지 모델인 '제미나이 2.5 플래시 이미지'의 놀라운 기능과 사용법을 알아보고, 이 모델이 가져올 AI 시장의 변화를 심층적으

knowhackking.tistory.com

 

 

초보자도 가능한 무료 AI 동영상 제작 방법 (4가지 툴 완벽 가이드) - knowhackking.com

복잡한 프로그램 없이 AI로 동영상을 만드는 법을 알려드립니다. Qwen, Vheer, Google AI 스튜디오, Whisk 등 4가지 무료 AI 툴을 활용한 제작 방법을 단계별로 상세히 소개합니다. 초보자도 쉽게 따라 할

knowhackking.com