이 글은 개발 블로그 개발 여정의 게시글이예요
해당 포스트는 NextJS를 이용하여 개발 블로그를 만들며 작성한 포스트입니다.
기술 블로그의 전체 코드는 🪢 yonglog github 에서 확인 하실 수 있습니다.
Velog의 사이드바 예시
Velog
의 사이드바처럼 인터렉티브하게 사용자가 보고 있는 영역에 맞춰 활성화 되고
클릭하면 해당 타이틀로 이동하는 사이드바를 만들어보자
사이드바 프로토타입 만들기
Sidebar 프로토타입
미디어 쿼리를 이용해 사이드바의 프로토타입을 구현해봤다.
-
뷰포트의 너비가 1024px
이상일 때 : 포스트를 담고 있는 너비를 150%
로 키운 후 사이드바를 display : block
으로 변경해 렌더링 되도록 한다. 이후 사이드바를 sticky
로 고정한다.
-
뷰포트의 너비가 1024px
이하일 때 : 포스트를 담고 있는 너비를 줄이고 사이드바를 display : hidden
으로 렌더링 하지 않는다.
잘 작동한다. :)
해당 프로토타입을 토대로 기능을 하나씩 추가해보자
Heading 텍스트 파싱하기
다음과 같이 단순히 mdx
텍스트를 배열로 변환 후 # , ## , ###
를 기준으로
HeadingSize,HeadingText[]
를 담은 배열을 반환하는 parsingHeaders
메소드를 정의해준다.
해당 메소드의 반환값 예시는 다음과 같다.
파싱한 Heading 텍스트를 이용해 계층적 ul , li 만들기
이후 재귀함수인 createList
메소드를 이용해 계층적인 ul , li
태그로 이뤄진 배열을 생성해주자
createList
는 호출시 생성된 list
를 반환하는데 해당 list
에는 li
태그로만 이뤄진 컴포넌트들만 존재하거나 ul , li
태그들로 이뤄진 컴포넌트를 반환한다.
createList
는 본인보다 headingSize
가 큰 값을 만날 때 까지 재귀적으로 호출되며 본인과 같은 경우엔 list
에 li
태그를 담고
본인보다 작은 경우엔 createList_2
를 호출하여 재귀적으로 탐색한 list_2
값들을 이용해 <ul>{list_2}</ul>
를 list
에 담는다.
편의상 두 번째 호출된 createList
를 createList_2
, createList_2
가 반환하는 list
를 list_2
라고 하였다.
이후 클라이언트 컴포넌트로 PostSideBar
컴포넌트를 생성해주자
계층적으로 구성된 사이드바의 모습
라우팅은 어떻게 할까?
라우팅 자체는 그닥 어려운 문제가 아니다.
MDX 파일을 변환 할 때 remarkPlugins
에 remarkGfm
을 이용해주고 있는데 해당 플러그인을 사용하면 자동으로 h1~h6
까지의 children
을 id
로 적용시켜놔준다.
remarkGfm으로 인해 heading tag의 id는 children과 같다.
원활한 id
설정을 위해선 h1~h6
태그 내부의 children
은 순수한 문자열로 적는 것이 좋다.
나는 헤딩 태그 내부에서 백틱을 이용한 '<code>..</code>
어쩌구 저쩌구' 이런식의 단어를 자주 쓰곤했는데 그렇게 되면 id
값이 [Object object] 어쩌구 저쩌구
이런 식으로 된다.
그렇게 되면 각 heading
태그들의 id
값과 Link
값이 가리키는 주소가 다르기 때문에 라우팅이 되지 않는다.
그래서 포스트들을 파싱 해올 때 #
가 들어간 텍스트들의 백틱들을 정규표현식을 이용해 제거해주는 filterContent
메소드를 정의해 적용해주었다.
PostSideBar
에 들어가는 li
태그의 내용과 모든 h1~h3
들의 id
가 동일하기 때문에
Link
컴포넌트의 href
에 해당 id
값을 넣어 라우팅을 시켜주자
사이드바를 이용해 라우팅을 하는 모습
Context와 IntersectionObserver 이용하여 Interactivity 추가하기
현재의 사이드바는 라우팅 기능이 있는 정적인 서버 컴포넌트이다.
이번에 추가하고자 하는 기능은 내가 어느 파트를 읽고 있는지를 확인 할 수 있는 인터렉티브한 기능을 만들고자 한다.
읽고 있는 파트에 대해서 스타일링을 다르게 해주도록 하자
다만 이 때 단순히 현재 읽고 있는 파트 뿐 아니라 상위 파트까지 같이 스타일링을 다르게 해줄 예정이다.
어떻게 읽고 있는 곳을 확인 할 수 있을까 ?
window API
인 IntersectionObserver
를 활용하려고 한다.
IntersectionObserver
는 실제 Actual DOM
의 태그를 선택하여 해당 태그가 뷰포트 혹은 상위 노드를 기준으로 하여
말 그대로 Intersection
한지를 확인하는 API
이다.
자세한 설명은 MDN
을 참고하도록 하자
🪢 MDN - Intersection Obsserver
사이드바에 적용되는 li
태그의 경우에 li
태그가 렌더링 하는 글자인 text
는 실제 Actual DOM
에 존재하는 h1~h3
태그들의 id
와 같다.
그렇기 때문에 해당 id
에 해당하는 heading
태그가 뷰포트에서 눈에 보이는지를 확인하여
현재 어느 파트를 읽고 있는지에 대해 확인 할 수 있을 것이다.
본문에서 heading1 ~heading3
까지의 글들을 파싱하여 Headers
배열을 생성하던 parsingHeaders
메소드에서 index
도 배열에 같이 담아주도록 수정해주었다.
순서 역할을 하는 index
를 이용해 현재 읽고 있는 N
번째 heading
태그를 만난다면 동일하게 N
번쨰 li
태그를 활성화시켜주기 위함이다.
ActiveContext 생성
다음과 같이 컨텍스트를 생성해주었다. 해당 컨텍스트는 현재 활성화 되어 있는 index
를 상태 값으로 생성하고 관리한다.
여기서 활성화 되어 있는 index
란 사이드바에 존재하는 li
태그 혹은 heading
태그의 순서를 의미한다.
이전에 말했듯 포스트 사이드바에 존재하는 li
태그들의 순서는 heading 1~3
태그들의 순서와 정확히 일치한다.
이후 ActiveContext
의 상태를 구독하는 ActiveHeader
컴포넌트를 생성해준다.
해당 컴포넌트에선 li
태그의 children
이자 heading
태그들의 id
인 headingTitle
를 props
로 받는다.
해당 값을 이용해 Actual DOM
에 존재하는 태그를 찾아 옵저버에게 등록한다.
이를 통해 해당 heading
태그가 뷰포트에 보이면 ActiveContext
의 activeIndex
를 현재의 HeadingNumber
로 변경한다.
여기서 HeadingNumber
는 li
태그의 순서이자 heading1~heading3
태그들의 순서를 의미한다.
즉, 만약 4번째 heading
태그가 뷰포트에 나타난다면 activeIndex
는 3이 된다.
이후 li
태그에 isActive , isUnderActive
값을 이용해 어트리뷰트를 추가해준다.
isActive
: 현재의 li
태그가 가장 최근에 뷰포트에 나타난 heading
태그를 가리키는가?
isUnderAcgtive
: 현재의 li
태그가 가리키는 heading
태그는 뷰포트에서 지나갔는가 ?
이후 기존 interactive
하지 않던 li
태그에서 옵저버가 장착된 ActiveHeader
컴포넌트로 변경해준다.
li 태그들 스타일 추가해주기
각 active , underActive
상태에 따른 li
태그들의 스타일을 global.css
에 정의해준다.
global.css
에서 정의해준 이유는 테마 별 색상을 다르게 하기 위해서 css variable
을 활용하기 위함이다.
최종 결과물은 다음과 같다.