lifecarelog
프론트엔드

긴 법률 문서 목차 스크롤스파이, 깜빡임 없이 하이라이트하기

조항이 수십 개인 긴 문서에서 목차 활성 항목이 튀는 깜빡임을 IntersectionObserver의 rootMargin 밴드로 잡는 프론트엔드 구현 방법을 정리했어요.

5분 읽기

조항이 40개 넘는 긴 문서를 화면에 띄우고, 옆에 목차를 붙여 지금 읽는 위치를 표시하려 한 적 있나요? 스크롤을 조금만 굴려도 목차의 활성 항목이 위아래로 튀면서 깜빡이는 경험, 프론트엔드 화면을 만들다 보면 한 번쯤 만나게 돼요.

왜 목차가 깜빡일까요

가장 흔한 방식은 스크롤 이벤트에서 각 섹션의 위치를 재는 거예요. 문제는 스크롤 이벤트가 프레임마다 여러 번 튀고, 그때마다 활성 섹션을 다시 계산하니 상태가 쉴 새 없이 바뀐다는 점이에요. 직접 붙여 보니 40개짜리 목차에선 손가락을 살짝만 움직여도 하이라이트가 두세 칸씩 널뛰더라고요.

계산 위치도 애매해요. 섹션 경계가 화면 어디를 지날 때 "활성"으로 볼지 기준이 없으면, 두 섹션이 동시에 화면에 걸친 순간 둘 사이를 오가며 깜빡여요.

IntersectionObserver로 바꾸기

여기서 스크롤 이벤트 대신 IntersectionObserver를 쓰면 절반은 해결돼요. 이 API는 요소가 화면에 들어오고 나가는 그 순간에만 콜백을 부르거든요. 매 프레임 위치를 재던 걸, 섹션이 실제로 교차할 때 몇 번만 부르는 구조로 바꾸는 셈이에요.

스크롤 이벤트 폴링IntersectionObserver 교차 감지활성 항목만 갱신

깜빡임을 없애는 핵심은 rootMargin

관찰만 붙인다고 깜빡임이 사라지진 않아요. 여러 섹션이 동시에 화면에 보이면 어느 걸 활성으로 볼지 여전히 흔들리거든요. 여기서 rootMargin으로 감지 영역을 화면 상단의 얇은 띠로 좁히는 게 핵심이에요.

const observer = new IntersectionObserver(onEntry, {
  rootMargin: "0px 0px -80% 0px", // 화면 상단 20% 구간만 감지
  threshold: 0,
});

위처럼 아래쪽 여백을 -80%로 주면, 섹션 제목이 화면 위쪽 20% 구간에 들어올 때만 활성으로 잡혀요. 값을 바꿔 가며 테스트해 보면 이 띠가 좁을수록 활성 항목이 한 번에 하나로 딱 떨어져요.

여기에 한 가지를 더했어요. 새로 감지한 id가 지금 활성 id와 같으면 상태 갱신을 건너뛰는 거예요. 리액트라면 불필요한 리렌더가 사라지니, 매 프레임 수십 번 바뀌던 게 섹션이 실제로 넘어갈 때 몇 번으로 줄어요.

  • rootMargin 상단 밴드로 좁히기
  • 같은 id면 상태 갱신 스킵
  • threshold는 0

마지막 섹션 처리도 잊지 마세요

짧은 마지막 조항은 화면 상단 띠까지 못 올라와서 영영 활성이 안 되는 경우가 있어요. 돌려 보니 문서 끝에 여백 블록을 하나 두거나, 스크롤이 바닥에 닿으면 마지막 항목을 강제로 켜는 처리로 자연스러워졌어요.

플랜엘(plan-l)에서 법령과 판례를 검색하다 보면 조문이 수십 개인 문서를 자주 다루게 되는데, 이런 목차 하이라이트 하나만 매끄러워도 읽는 흐름이 확 안정돼요. 만들면서 느낀 건, 화려한 애니메이션보다 "지금 여기를 읽고 있다"는 표시가 흔들리지 않는 게 더 중요하다는 점이었어요. 긴 문서를 다루는 화면이라면 한번 손봐 두면 두고두고 편해요.

#IntersectionObserver#스크롤스파이#프론트엔드

라이프케어로그 서비스가 궁금하신가요?

AI 기반 건강·일정·재활 관리 앱을 직접 써보세요.

서비스 살펴보기

관련 글

댓글

아직 댓글이 없어요. 첫 댓글을 남겨주세요.