ניווט באמצעות Scrollspy הוא דפוס נפוץ של ממשק משתמש שמדגיש באופן אוטומטי את קישור הניווט שתואם לקטע בדף שמוצג כרגע באזור התצוגה. כך המשתמשים יכולים לדעת איפה הם נמצאים במסמך ארוך כשהם גוללים בו.
הדפוס הזה נפוץ בשני תרחישים:
- אתרים עם דף אחד: אתרים שבהם הניווט הראשי באתר מקשר לחלקים שונים של דף אחד, ולא לכתובות URL נפרדות.
- תוכן ארוך: כשתוכן עניינים או סרגל צד לניווט עוקבים אחרי ההתקדמות של המשתמש במאמר, במדריך או במסמך.
איך מטמיעים
בעבר, כדי להשתמש בתבנית scrollspy היה צריך להשתמש ב-JavaScript וב-API של IntersectionObserver. ב-CSS מודרני יש דרך מובנית לעשות את זה באמצעות המאפיין scroll-target-group והמחלקה המדומה :target-current.
- מוודאים שקישורי הניווט משתמשים במזהי מקטעים: הניווט חייב לכלול קישורים לכותרות (
<a>) שמפנים לקטעים באותו דף באמצעות מזהי מקטעים (למשל,<a href="#section-1">) שתואמים ל-idשל קטעי התוכן. - הפעלת קיבוץ יעדי גלילה: מחילים את המאפיין
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במאגר ייעודי לקישורי ניווט: כך הדפדפן יודע אחרי אילו קישורים צריך לעקוב. - חשוב לוודא שליעדים יש מאפייני
idייחודיים שתואמים למאפייניhrefשל הקישורים. - מומלץ להשאיר מספיק מקום אנכי לקטעים:
scroll-target-groupמזהה בצורה הכי ברורה את היעד הגלוי כשהקטעים גדולים מספיק כך שרק אחד או שניים מהם גלויים בכל פעם. - חובה: כדי לשמור על נגישות, המצב החזותי צריך להיות משוכפל עם
aria-current. ההיבט החזותי מטופל על ידי:target-current, אבל הוא לא חשוף לקוראי מסך. - AVOID רק על צבע כדי לציין מצב פעיל: הוסיפו רמזים חזותיים אחרים כמו עובי הגופן, קו תחתון או נקודת אינדיקציה, או ודאו שיש מספיק ניגודיות בין הצבעים כדי שההבדל יהיה נגיש למשתמשים עם קושי בהבחנה בין צבעים שונים.
אסטרטגיות חלופיות
בדפדפנים שלא תומכים ב-scroll-target-group, צריך להשתמש באפשרות חלופית שמבוססת על ה-API של IntersectionObserver.
<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>