본문 바로가기

자바스크립트

[JS] 흔히 블로그에서 자동으로 생성되는 목차는 어떻게 만드는 걸까?

반응형
기존에 작성된 포스트가 갱신되어 최신 날짜를 기준으로 업데이트 되었습니다(2024/03/10 기준)

자동 생성 목차란?

임의로 이름을 붙였지만, 이름 그대로 h1,h2,h3 등의 제목 태그를 이용해서 헤드를 작성하면, 해당 헤드로 이동하는 리스트가 생성되고, 사용자는 이 목차를 이용해서 해당 콘텐츠로 즉시 이동할 수 있도록하는 기능이다.

 

만들게 된 배경

개발 공부를 하면서, 이러한 자동 생성 목차를 사용하는 블로그나 공식 사이트를 많이 봤지만, 블로그의 글을 작성하면서 굳이 적용할 필요성은 느끼지 못했다. 그러나,  내가 작성한 글을 다시금 참고하려고 하는 일상이 반복될 때, 어느 순간 마우스 휠을 이용해서 일일이 스크롤 이동하는 것이 너무 불편하게 느껴졌다. 

 

따라서 편의성을 높이기 위해 해당 기능을 한 번 구현해 보기로 결정하였다.

 

자동 생성 목차의 원리

막상 어렵게 생각했던거에 비해서 단순하게 만든다고 가정 할 때는 구현이 어렵지는 않은 것 같다. 우선, 사용자가 h1,h2,h3 등의 태그를 사용해서 제목을 작성하면, 해당 태그의 id 값으로 입력한 제목을 값으로 넣어준다. 

예를 들어, <h1>안녕하세요</h1> 라는 태그가 존재한다면, <h1 id="안녕하세요">안녕하세요</h1> 와 같이 id 값에 입력한 값을 넣어주는 것이다.

 

그리고, 이 때 a 태그를 새롭게 생성하고, 해당 a 태그의 href 속성에 앞서 heading 태그에 추가한 id 속성의 값을 넣어준다. 

예를 들어, <a href="#안녕하세요">안녕하세요</a> 이런식이 될 것이다.

 

마지막으로, 생성된 a 태그를 보기 좋게 만들어서 우측 등의 여백이 있는 곳에 보이도록 만들면 단순한 자동생성목차의 구현은 끝이다.

 

그럼 이러한 원리를 기반으로 구현해보도록 하자.

 

코딩

html 이나 css 는 현재 포스트에서는 중요하지 않다. 따라서 대략적으로 사용된 태그와 스타일이 무엇인지 코드 스니펫으로 언급만 하고 넘어갈 것이다.

 

HTML

ul 는 목차가 표시되는 영역이다.  h1와 h2 는 사용자가 에디터를 사용해 입력한 각 콘텐츠 내용의 제목에 해당하는 텍스트를 의미한다.

    <ul id="contents"></ul>
    <h1>안녕하세요</h1>
    <h2>반갑습니다.</h2>

 

CSS

      /* 예제 스타일 */
      h1,
      h2,
      h3 {
        font-size: 16px;
        font-weight: bold;
        min-height: 100vh;
        color: #333;
      }

      h1 {
        background-color: #eee;
      }

      h2 {
        background-color: #f5f5f5;
      }

     /* 목차 컨테이너 */
      #contents {
        width: 200px;
        position: fixed;
        right: 0;
        min-height: 300px;
        background-color: rgba(0, 0, 0, 0.371);
      }

    /* 목차를 구성하는 내용(링크) */
      #contents a {
        display: inline-block;
        color: white;
        margin: 2px 0;
        text-decoration: none;
      }

 

여기 까지 했다면, 다음과 같은 UI 가 보여질 것이다.

 

JS

이제 본격적으로 코드를 작성할 것이다. 앞서 언급한 원리에 따라서 각 단계로 구분하여 작성한다.

1. 사용자가 헤드 태그를 이용하여 텍스트를 작성하면, 해당 텍스트가 헤드 태그의 id 값으로 셋팅되도록 한다.
// 헤드와 관련한 태그를 모두 선택한다.
const headings = document.querySelectorAll("h1, h2, h3");

/* 각 헤드 태그를 forEach 문을 통해 순회하면서 id 속성에 사용자가 입력한 텍스트를 값으로 설정한다. */
      headings.forEach((heading) => {
        const li = document.createElement('li') // ui>li>a 형식으로 태그를 추가하기 위함
        const id =
          // 기존에 존재하는 id 속성이 있다면 그걸 들고 오고, 
          // 아니면 사용자가 입력한 텍스트를 가져와서 id 변수에 담기 위한 코드
          heading.getAttribute("id") ||
          // 공백이 존재하면 -으로 대체
          heading.textContent.toLowerCase().replace(/\s/g, "-"); 
          
        // 사용자 입력값을 헤드태그의  id 속성의 값으로 지정
        heading.setAttribute("id", id);

 

2.  헤드 태그와 연결할 링크 태그로 a 를 생성하고, a 태그를 li 태그의 자식요소로, li 요소를 ui 태그의 자식요소로 추가한다.
         // 각 헤드태그와 링크로 연결하기 위해 a 태그를 생성
        const link = document.createElement("a");
        // a 태그의 href 속성의 값으로 '#헤드태그id속성의값' 을 지정한다.
        link.href = `#${id}`;
        link.textContent = heading.textContent; 
        li.appendChild(link)

        // 생성한 a 태그를 li 태그의 자식 요소로 추가하고,
        // a태그가 추가된 li 태그를 ui 태그인 contents 의 자식요소로 추가한다.
        document.getElementById("contents").appendChild(li);
      });

 

3. 사용자가 입력한 제목에 따라 목차가 추가된 UI 가 화면에 렌더링 된다.

 

아래는 앞서 작성한 스크립트 전체 코드이다(로직 자체가 단순해서 짧다).
      const headings = document.querySelectorAll("h1, h2, h3");

      headings.forEach((heading) => {
        const li = document.createElement('li')
        const id =
          heading.getAttribute("id") ||
          heading.textContent.toLowerCase().replace(/\s/g, "-");
        heading.setAttribute("id", id);

        const link = document.createElement("a");
        link.href = `#${id}`;
        link.textContent = heading.textContent;
        li.appendChild(link)

        // 여기서 목차에 링크 추가
        document.getElementById("contents").appendChild(li);
      });

 

나가는 길

ui 가 빈약하게 만들어지긴 했는데, 현재 로직을 이 블로그에 적용한 결과가 모니터 상에서 우측의 목차 부분이다. 해당 목차의 각 링크를 클릭하면, 자동으로 해당 태그의 상단 위치로 이동하게 되어 있다. 

 

구현 혈과 시연

 

이 로직을 조금 더 응용한다면, offsetTop 등의 레이아웃 관련 속성을 활용하여 특정 위치에 제목(헤딩) 태그가 있으면 해당 목차목록에서 자동으로 타겟 목록이 포커스 되도록 하거나 , 흔히 1.~~ 1-1. ~~ 이런 식으로 메인제목 안의 소제목 형식으로 작성할 수 있겠으나, 현재 까지는 굳이 그럴 필요가 없다고 판단하여 여기서 마무리 하였다.

 

반응형