스크롤스파이 탐색은 현재 표시 영역에 있는 페이지 섹션에 해당하는 탐색 링크를 자동으로 강조 표시하는 일반적인 UI 패턴입니다. 이렇게 하면 사용자가 긴 문서를 스크롤할 때 현재 위치를 명확하게 파악할 수 있습니다.
이 패턴은 다음 두 가지 시나리오에서 일반적입니다.
- 단일 페이지 웹사이트: 기본 사이트 탐색이 별도의 URL이 아닌 단일 페이지의 여러 섹션으로 연결되는 경우입니다.
- 긴 형식 콘텐츠: 목차 또는 탐색 사이드바에서 기사, 가이드 또는 문서의 사용자 진행 상황을 추적하는 경우
구현 방법
기존에는 스크롤스파이 패턴에 JavaScript와 IntersectionObserver API가 필요했습니다. 최신 CSS는 scroll-target-group 속성과 :target-current 가상 클래스를 사용하여 이를 달성하는 네이티브 방법을 제공합니다.
- 탐색 링크에 프래그먼트 식별자 사용: 탐색은 콘텐츠 섹션의
id와 일치하는 프래그먼트 식별자(예:<a href="#section-1">)를 사용하여 동일한 페이지의 섹션을 가리키는 앵커 링크(<a>)로 구성되어야 합니다. - 스크롤 타겟 그룹화 사용 설정: 탐색 링크의 컨테이너에
scroll-target-group: auto속성을 적용합니다. 이렇게 하면 브라우저가 이러한 링크를 각 타겟을 추적하는 마커로 처리합니다. - 활성 링크 스타일 지정: 탐색 링크에
:target-current가상 클래스를 사용하여 스크롤 컨테이너에 타겟 섹션이 표시될 때 스타일을 적용합니다. - 시맨틱 상태: 스크롤이 중지될 때 활성 링크의
aria-current="true"를 업데이트하는 스크립트를 사용하여 상태가 보조 기술에 올바르게 전달되도록 합니다.
예시 코드
이 예에서는 사용자가 섹션을 스크롤할 때 고정된 탐색 메뉴에서 활성 링크를 강조 표시합니다.
/* MANDATORY: The container of markers must have scroll-target-group: auto */
nav {
/* Enables automatic tracking of fragment links */
scroll-target-group: auto;
}
/* MANDATORY: Use :target-current to style the link whose target is in view. */
nav a:target-current {
color: var(--accent, #007bff);
border-bottom-color: var(--accent, #007bff);
}
<nav>
<a href="#intro">Introduction</a>
<a href="#features">Features</a>
<a href="#pricing">Pricing</a>
</nav>
<main>
<!-- MANDATORY: Each section must have a unique ID matching a link fragment in the <nav>. -->
<section id="intro">
<h2>Introduction</h2>
<!-- content -->
</section>
<section id="features">
<h2>Features</h2>
<!-- content -->
</section>
<section id="pricing">
<h2>Pricing</h2>
<!-- content -->
</section>
</main>
<script type="module">
if (CSS.supports('scroll-target-group: auto')) {
const syncAriaCurrent = () => {
const currentLink = document.querySelector('nav a:target-current');
document.querySelectorAll('nav a').forEach(link => {
link.setAttribute('aria-current', link === currentLink ? 'true' : 'false');
});
};
// Update the aria on initial page load. Ensure the DOM content is loaded, either by using `<script type="module">` or an event listener for `DOMContentLoaded`.
syncAriaCurrent();
// Update the aria after scroll ends
document.addEventListener('scrollend', syncAriaCurrent);
}
</script>
권장사항
- 탐색 링크 전용 컨테이너에
scroll-target-group: auto를 사용합니다. 이렇게 하면 브라우저가 추적해야 하는 링크를 알 수 있습니다. - 권장사항: 타겟에 링크의
href속성과 일치하는 고유한id속성이 있는지 확인합니다. - 권장사항: 섹션에 충분한 세로 공간을 제공합니다.
scroll-target-group는 섹션이 한 번에 하나 또는 두 개만 표시될 정도로 충분히 큰 경우 표시되는 타겟을 가장 명확하게 식별합니다. - 필수: 접근성을 위해 시각적 상태를
aria-current로 미러링해야 합니다.:target-current는 시각적 측면을 처리하지만 스크린 리더에는 노출되지 않습니다. - 활성 상태를 색상에만 의존하지 AVOID. 글꼴 두께, 밑줄, 표시기 점과 같은 다른 시각적 단서를 포함하거나 색상 간에 충분한 대비를 두어 색각 이상이 있는 사용자가 차이를 인식할 수 있도록 하세요.
대체 전략
scroll-target-group를 지원하지 않는 브라우저의 경우 IntersectionObserver API를 기반으로 하는 대체 기능을 사용해야 합니다.
<script>
// Feature detection for scroll-target-group
const supportsScrollTargetGroup = CSS.supports('scroll-target-group: auto');
if (!supportsScrollTargetGroup) {
const navLinks = document.querySelectorAll('nav a');
const sections = document.querySelectorAll('main section');
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const id = entry.target.getAttribute('id');
navLinks.forEach(link => {
const isActive = link.getAttribute('href') === `#${id}`;
// Use the escaped colon class for consistent styling
link.classList.toggle('\:target-current', isActive);
link.setAttribute('aria-current', isActive);
});
}
});
}, { rootMargin: '-50% 0px -50% 0px', threshold: 0 });
sections.forEach(section => observer.observe(section));
}
</script>
<style>
/* Fallback style if :target-current is not supported.
Use :is() to combine the native pseudo-class with a fallback class for older browsers. DO NOT use `nav a:target-current, nav a.\:target-current, as the unrecognized pseudoselector invalidates the entire rule in older browsers. */
nav a:is(:target-current, .\:target-current) {
color: var(--accent, #007bff);
border-bottom-color: var(--accent, #007bff);
font-weight: bold;
}
</style>