개발
Laravel 6.x

동적 목차(ToC) 구현

132 views as of November 4, 2024.
우리가 일반적으로 글을 쓸때 글의 내용이 길어지면 쉽게 접근할 수 있게 도와주는 페이지, 즉 목차(Table of contents)라는 것이 있다.

쉽게 위키에서 볼수 있는 페이지 상단의 그걸 말한다.


목차는 쉽게 해당 문서의 '구조'를 알 수 있으며 독자가 원하는 파트로의 이동을 돕기도한다.

그래서 짧은 글들이 주로이루는 커뮤니티, SNS 등에는 존재하지않고 정보 전달페이지나 기록용 페이지등에서 자주 보인다.

이외에는 발표자료 등에서도 제목 페이지 다음으로 많이 위치한다.

근데 실제로 이런 목차를 작성해보았다면 여간 귀찮은 부분이 아닐 수 없다.
무슨 이야기나면 목차는 본문의 구조와 일치해야하기때문에 본문 작성 후 목차를 작성해주는 이중 관리가 필수라는 것이다.

이런 부분을 어떻게 동적으로 처리할 수 없을까 해서 고민을 해보았다.


백단 - 동적 목차 생성

HTML 문서, 즉 웹에 적히는 대부분의 문서는 태그로 감싸져있다.
그렇기 때문에 스타일을 적용하기쉽고 본문의 구조를 파악하기 쉽다.


실제로도 웹 크롤러는 이런식으로 구조화된 문서를 더 잘 수집해가며 수집자또한 그렇게 구조화된 글이면 더욱 높은 점수를 매겨 검색 엔진에 잘 노출시켜준다.

그럼 이렇게 구조화된 문서를 h1, h2, h3 태그를 기준으로 파싱할 수 있다면 쉽게 문서의 구조를 얻어낼 수 있고 이런 헤딩 태그를 이용해서 목차를 동적으로 생성하는것도 충분히 가능할 것이다.

단 이런 목차 생성은 유저단에서 스크립트로 진행하면 클라이언트 성능에 영향을 끼칠수도 있으니까 백단에서 구현하는것이 일반 적일 것이다.

나같은 경우는 PHP 기반의 라라벨 프레임워크로 이 과정을 구현해 보았다.

public function getHeadingsWithOrder()
{
    $headings = [];
 
    // DOMDocument를 사용하여 HTML 파싱
    $dom = new DOMDocument();
    @$dom->loadHTML(mb_convert_encoding($this->body, 'HTML-ENTITIES', 'UTF-8'));
 
    // XPath를 사용하여 h2 및 h3 태그를 순서대로 선택
    $xpath = new DOMXPath($dom);
    $headingTags = $xpath->query('//h2 | //h3'); // h2 또는 h3 태그만 선택
 
    // h2 및 h3 태그의 내용을 순서대로 배열에 저장
    foreach ($headingTags as $index => $tag) {
        // 각 태그에 고유 ID 설정
        $id = 'heading-' . ($index + 1);
        $tag->setAttribute('id', $id);
 
        $headings[] = [
            'tag' => $tag->nodeName, // 'h2' 또는 'h3'
            'text' => trim($tag->textContent),
            'id' => $id // 고유 ID 추가
        ];
    }
 
    // 변경된 HTML을 반환 (ID가 추가된 HTML)
    $this->body = $dom->saveHTML();
 
    return $headings;
}Copy

위의 코드는 Post 모델에 메소드로 선언해준것으로 현재 모델의 body 값을 가져와서 DOMDocumentDOMXPath를 통해 내용을 파싱하는 방식이다.

목차를 클릭했을때 해당 헤딩 태그로 이동해야하므로 임의의 ID를 동적으로 할당하는 부분도 코드에 포함되어있다.

이 과정은 만약에 본인이 본문의 내용에 직접 id를 부여했다면 넘어가도 되는 부분이지만, 일반적으로 일일히 heading 태그에 넘버링을 해두진 않을 것이다. (본문의 추가, 수정을 통해 언제든 넘버링 순서가 뒤틀릴 수 있기 때문에)

그러므로 파싱한 순서대로 동적ID를 부여하고 이 ID 정보와 함께 태그명, 텍스트정보를 배열로 만들어 반환한다.

그러면 $headings 태그는 아래와 같이 정보가 가공되어 담기게 될 것이다.


마지막으로 이 정보를 본문 요청시에 같이 담아 반환하면 백단 구현은 완료될 것이다.

$headings = $post->getHeadingsWithOrder();
return view('view', compact('post', 'headings'));Copy


프론트단 - 목차 구현

유저단은 백단의 정보를 이용해 아래와 같이 구현해주면 된다.

물론 각자의 환경에 따라 뷰의 구현은 사뭇 달라질 수 있겠지만 포인트는 백단에서 정렬한 배열의 구조에 맞게 목차 태그를 구현하면 될것이다.

@if($headings ?? false)
    <div class="grp_toc">
        <div class="tit_toc">목차</div>
        <ul>
            @foreach ($headings as $heading)
                <li class="{{ $heading['tag'] }}">
                    <a href="#{{ $heading['id'] }}" class="link">{{ $heading['text'] }}</a>
                </li>
            @endforeach
        </ul>
    </div>
@endifCopy


실제로 이렇게 구현된 목차는 앵커 태그의 #id 경로로 바로 이동하는 기능까지 탑재되어 훌륭히 목차의 기능을 수행하는것을 알 수 있다.

실제로 이 글 또한 동적으로 생성된 목차가 보여지고 있으므로 본 게시글을 분석해봐도 좋을것이다.


결론

물론 이런 목차는 고정형 정보를 다루는 곳에서는 이런식으로 동적으로 구현하기보다 정적으로 직접 퍼블리싱해 구현하는것이 훨씬 디자인이나 UI, UX적으로 효율적일 수 있다.

단, 블로그나 위키처럼 같은 형태의 글이 반복적으로 쓰이는곳에서 이런 동적기능은 글쓴이의 노동을 비약적으로 줄일 수 있으며 이런 동적 생성물은 SEO 최적화에도 도움을 주며 특히 본문 내용을 기반으로 필요한 정보를 파싱해 무한이 이용할 수 있는 첫 단추가 되기도 한다.

웹을 구현하는 방법은 위에 예제로 든 PHP 방식말고도 Java, Node 등 수많은 방법을 동반하고있기때문에 본 글에선 목차를 동적으로 생성해 제어할 수 도 있다는것에 의의를 가졌으면 한다.
#개발 #라라벨 #Laravel #목차 #Table of contents #동적 ID
0 개의 댓글
×