Scrollspy

Scrollspy (스크롤스파이)

스크롤 위치에 따라 Bootstrap 네비게이션이나 list group 컴포넌트를 자동으로 업데이트해서, 뷰포트에서 현재 어떤 링크가 활성 상태인지 나타내는 방법을 다룹니다.

출처: 문서

본문

동작 원리 (How it works)

Scrollspy는 앵커(<a>) 요소의 href가 가리키는 id를 가진 요소가 스크롤되어 뷰에 들어올 때 해당 앵커 요소에 .active 클래스를 토글합니다. Scrollspy는 Bootstrap nav 컴포넌트나 list group과 함께 쓰는 것이 가장 좋지만, 현재 페이지의 어떤 앵커 요소와도 동작합니다. 동작 방식은 다음과 같습니다.

  • 먼저 scrollspy에는 두 가지가 필요합니다: 네비게이션, list group, 또는 간단한 링크 집합과, 스크롤 가능한 컨테이너입니다. 스크롤 가능한 컨테이너는 <body>이거나, height와 overflow-y: scroll이 설정된 커스텀 요소일 수 있습니다.
  • 스크롤 가능한 컨테이너에 data-bs-spy="scroll"과 data-bs-target="#navId"를 추가하세요. 여기서 navId는 관련 네비게이션의 고유한 id입니다. 요소 안에 포커스 가능한 요소가 없다면 키보드 접근을 보장하기 위해 tabindex="0"도 포함해야 합니다.
  • "spied" 컨테이너를 스크롤하면 관련 네비게이션 안의 앵커 링크에 .active 클래스가 추가되고 제거됩니다. 링크는 해석 가능한 id 대상이 있어야 합니다. 그렇지 않으면 무시됩니다. 예를 들어 <a href="#home">home</a>은 <div id="home"></div>처럼 DOM에 있는 무언가와 대응해야 합니다.
  • 보이지 않는 대상 요소는 무시됩니다. 아래의 Non-visible elements 섹션을 참고하세요.

예시 (Examples)

Navbar

navbar 아래 영역을 스크롤하면서 active 클래스가 변하는 것을 보세요. 드롭다운 메뉴를 열면 드롭다운 항목들도 강조 표시되는 것을 볼 수 있습니다.

<nav id="navbar-example2" class="navbar bg-body-tertiary px-3 mb-3">
  <a class="navbar-brand" href="#">Navbar</a>
  <ul class="nav nav-pills">
    <li class="nav-item">
      <a class="nav-link" href="#scrollspyHeading1">First</a>
    </li>
    <li class="nav-item">
      <a class="nav-link" href="#scrollspyHeading2">Second</a>
    </li>
    <li class="nav-item dropdown">
      <a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#" role="button" aria-expanded="false">Dropdown</a>
      <ul class="dropdown-menu">
        <li><a class="dropdown-item" href="#scrollspyHeading3">Third</a></li>
        <li><a class="dropdown-item" href="#scrollspyHeading4">Fourth</a></li>
        <li><hr class="dropdown-divider"></li>
        <li><a class="dropdown-item" href="#scrollspyHeading5">Fifth</a></li>
      </ul>
    </li>
  </ul>
</nav>
<div data-bs-spy="scroll" data-bs-target="#navbar-example2" data-bs-root-margin="0px 0px -40%" data-bs-smooth-scroll="true" class="scrollspy-example bg-body-tertiary p-3 rounded-2" tabindex="0">
  <h4 id="scrollspyHeading1">First heading</h4>
  <p>...</p>
  <h4 id="scrollspyHeading2">Second heading</h4>
  <p>...</p>
  <h4 id="scrollspyHeading3">Third heading</h4>
  <p>...</p>
  <h4 id="scrollspyHeading4">Fourth heading</h4>
  <p>...</p>
  <h4 id="scrollspyHeading5">Fifth heading</h4>
  <p>...</p>
</div>

중첩 nav (Nested nav)

Scrollspy는 중첩된 .navs에서도 동작합니다. 중첩된 .nav가 .active이면 그 부모들도 .active가 됩니다. navbar 옆의 영역을 스크롤하면서 active 클래스가 변하는 것을 보세요.

<div class="row">
  <div class="col-4">
    <nav id="navbar-example3" class="h-100 flex-column align-items-stretch pe-4 border-end">
      <nav class="nav nav-pills flex-column">
        <a class="nav-link" href="#item-1">Item 1</a>
        <nav class="nav nav-pills flex-column">
          <a class="nav-link ms-3 my-1" href="#item-1-1">Item 1-1</a>
          <a class="nav-link ms-3 my-1" href="#item-1-2">Item 1-2</a>
        </nav>
        <a class="nav-link" href="#item-2">Item 2</a>
        <a class="nav-link" href="#item-3">Item 3</a>
        <nav class="nav nav-pills flex-column">
          <a class="nav-link ms-3 my-1" href="#item-3-1">Item 3-1</a>
          <a class="nav-link ms-3 my-1" href="#item-3-2">Item 3-2</a>
        </nav>
      </nav>
    </nav>
  </div>

  <div class="col-8">
    <div data-bs-spy="scroll" data-bs-target="#navbar-example3" data-bs-smooth-scroll="true" class="scrollspy-example-2" tabindex="0">
      <div id="item-1">
        <h4>Item 1</h4>
        <p>...</p>
      </div>
      <div id="item-1-1">
        <h5>Item 1-1</h5>
        <p>...</p>
      </div>
      <div id="item-1-2">
        <h5>Item 1-2</h5>
        <p>...</p>
      </div>
      <div id="item-2">
        <h4>Item 2</h4>
        <p>...</p>
      </div>
      <div id="item-3">
        <h4>Item 3</h4>
        <p>...</p>
      </div>
      <div id="item-3-1">
        <h5>Item 3-1</h5>
        <p>...</p>
      </div>
      <div id="item-3-2">
        <h5>Item 3-2</h5>
        <p>...</p>
      </div>
    </div>
  </div>
</div>

JavaScript 플러그인은 보이는 모든 요소 중에서 올바른 요소를 고르려고 한다는 점을 기억하세요. 동시에 여러 scrollspy 대상이 보이면 문제가 생길 수 있습니다.

List group

Scrollspy는 .list-groups에서도 동작합니다. list group 옆의 영역을 스크롤하면서 active 클래스가 변하는 것을 보세요.

<div class="row">
  <div class="col-4">
    <div id="list-example" class="list-group">
      <a class="list-group-item list-group-item-action" href="#list-item-1">Item 1</a>
      <a class="list-group-item list-group-item-action" href="#list-item-2">Item 2</a>
      <a class="list-group-item list-group-item-action" href="#list-item-3">Item 3</a>
      <a class="list-group-item list-group-item-action" href="#list-item-4">Item 4</a>
    </div>
  </div>
  <div class="col-8">
    <div data-bs-spy="scroll" data-bs-target="#list-example" data-bs-smooth-scroll="true" class="scrollspy-example" tabindex="0">
      <h4 id="list-item-1">Item 1</h4>
      <p>...</p>
      <h4 id="list-item-2">Item 2</h4>
      <p>...</p>
      <h4 id="list-item-3">Item 3</h4>
      <p>...</p>
      <h4 id="list-item-4">Item 4</h4>
      <p>...</p>
    </div>
  </div>
</div>

단순 앵커 (Simple anchors)

Scrollspy는 nav 컴포넌트와 list group에 국한되지 않으므로 현재 문서의 어떤 <a> 앵커 요소에서도 동작합니다. 영역을 스크롤하면서 .active 클래스가 변하는 것을 보세요.

<div class="row">
  <div class="col-4">
    <div id="simple-list-example" class="d-flex flex-column gap-2 simple-list-example-scrollspy text-center">
      <a class="p-1 rounded" href="#simple-list-item-1">Item 1</a>
      <a class="p-1 rounded" href="#simple-list-item-2">Item 2</a>
      <a class="p-1 rounded" href="#simple-list-item-3">Item 3</a>
      <a class="p-1 rounded" href="#simple-list-item-4">Item 4</a>
      <a class="p-1 rounded" href="#simple-list-item-5">Item 5</a>
    </div>
  </div>
  <div class="col-8">
    <div data-bs-spy="scroll" data-bs-target="#simple-list-example" data-bs-offset="0" data-bs-smooth-scroll="true" class="scrollspy-example" tabindex="0">
      <h4 id="simple-list-item-1">Item 1</h4>
      <p>...</p>
      <h4 id="simple-list-item-2">Item 2</h4>
      <p>...</p>
      <h4 id="simple-list-item-3">Item 3</h4>
      <p>...</p>
      <h4 id="simple-list-item-4">Item 4</h4>
      <p>...</p>
      <h4 id="simple-list-item-5">Item 5</h4>
      <p>...</p>
    </div>
  </div>
</div>

보이지 않는 요소 (Non-visible elements)

보이지 않는 대상 요소는 무시되며 해당 nav 항목은 .active 클래스를 받지 않습니다. 보이지 않는 래퍼에서 초기화된 Scrollspy 인스턴스는 모든 대상 요소를 무시합니다. 래퍼가 보이게 되면 관찰 가능한 요소를 확인하기 위해 refresh 메서드를 사용하세요.

document.querySelectorAll('#nav-tab>[data-bs-toggle="tab"]').forEach(el => {
  el.addEventListener('shown.bs.tab', () => {
    const target = el.getAttribute('data-bs-target')
    const scrollElem = document.querySelector(`${target} [data-bs-spy="scroll"]`)
    bootstrap.ScrollSpy.getOrCreateInstance(scrollElem).refresh()
  })
})

사용법 (Usage)

data 속성으로 (Via data attributes)

탑바 네비게이션에 scrollspy 동작을 쉽게 추가하려면, 감시하고 싶은 요소(대개는 <body>)에 data-bs-spy="scroll"을 추가하세요. 그런 다음 어떤 Bootstrap .nav 컴포넌트의 부모 요소의 id나 클래스 이름과 함께 data-bs-target 속성을 추가하세요.

<body data-bs-spy="scroll" data-bs-target="#navbar-example">
  ...
  <div id="navbar-example">
    <ul class="nav nav-tabs" role="tablist">
      ...
    </ul>
  </div>
  ...
</body>

JavaScript로 (Via JavaScript)

const scrollSpy = new bootstrap.ScrollSpy(document.body, {
  target: '#navbar-example'
})

옵션 (Options)

옵션은 data 속성이나 JavaScript로 전달할 수 있으므로, data-bs-animation="{value}"처럼 data-bs-에 옵션 이름을 붙일 수 있습니다. data 속성으로 옵션을 전달할 때는 옵션 이름의 대소문자 형태를 "camelCase"에서 "kebab-case"로 바꿔야 합니다. 예를 들어 data-bs-customClass="beautifier" 대신 data-bs-custom-class="beautifier"를 사용하세요.

Bootstrap 5.2.0부터 모든 컴포넌트는 JSON 문자열로 간단한 컴포넌트 설정을 담을 수 있는 실험적인 예약 data 속성 data-bs-config를 지원합니다. 요소에 data-bs-config='{"delay":0, "title":123}'과 data-bs-title="456" 속성이 있으면 최종 title 값은 456이 되고, 별도의 data 속성이 data-bs-config에 주어진 값을 덮어씁니다. 또한 기존 data 속성도 data-bs-delay='{"show":0,"hide":150}'처럼 JSON 값을 담을 수 있습니다.

최종 설정 객체는 data-bs-config, data-bs-, js object를 병합한 결과이며, 가장 마지막에 주어진 키-값이 나머지를 덮어씁니다.

이름 타입 기본값 설명
rootMargin string 0px 0px -25% 스크롤 위치를 계산할 때 사용하는 Intersection Observer rootMargin 유효 단위입니다.
smoothScroll boolean false 사용자가 ScrollSpy 관찰 대상 요소를 가리키는 링크를 클릭할 때 부드러운 스크롤을 활성화합니다.
target string, DOM element null Scrollspy 플러그인을 적용할 요소를 지정합니다.
threshold array [0.1, 0.5, 1] 스크롤 위치를 계산할 때 사용하는 IntersectionObserver threshold 유효 입력입니다.

폐기된 옵션 (Deprecated Options)

v5.1.3까지는 offset과 method 옵션을 사용했는데, 지금은 폐기되고 rootMargin으로 대체되었습니다. 하위 호환성을 유지하기 위해 주어진 offset을 rootMargin으로 계속 파싱하지만, 이 기능은 v6에서 제거될 예정입니다.

메서드 (Methods)

메서드 설명
dispose 요소의 scrollspy를 파괴합니다(DOM 요소에 저장된 데이터를 제거합니다).
getInstance DOM 요소와 연결된 scrollspy 인스턴스를 가져오는 정적 메서드입니다.
getOrCreateInstance DOM 요소와 연결된 scrollspy 인스턴스를 가져오거나, 초기화되지 않은 경우 새로 만드는 정적 메서드입니다.
refresh DOM에서 요소를 추가하거나 제거할 때 refresh 메서드를 호출해야 합니다.

refresh 메서드를 사용하는 예시:

const dataSpyList = document.querySelectorAll('[data-bs-spy="scroll"]')
dataSpyList.forEach(dataSpyEl => {
  bootstrap.ScrollSpy.getInstance(dataSpyEl).refresh()
})

이벤트 (Events)

이벤트 설명
activate.bs.scrollspy scrollspy가 앵커를 활성화할 때마다 스크롤 요소에서 발생하는 이벤트입니다.
const firstScrollSpyEl = document.querySelector('[data-bs-spy="scroll"]')
firstScrollSpyEl.addEventListener('activate.bs.scrollspy', () => {
  // do something...
})

더 알아보기 (Learn more)