Una navegación con scrollspy es un patrón de IU común que destaca automáticamente el vínculo de navegación correspondiente a la sección de la página que se encuentra actualmente en el viewport. Esto les proporciona a los usuarios una idea clara de dónde se encuentran en un documento largo mientras se desplazan.
Este patrón es común en dos situaciones:
- Sitios web de una sola página: Son aquellos en los que los vínculos de navegación principales del sitio dirigen a diferentes secciones de una sola página, en lugar de a URLs independientes.
- Contenido de formato largo: Es cuando un índice o una navegación en la barra lateral hace un seguimiento del progreso del usuario en un artículo, una guía o una documentación.
Cómo implementarlo
Tradicionalmente, el patrón de scrollspy requería JavaScript y la API de IntersectionObserver. El CSS moderno proporciona una forma nativa de lograrlo con la propiedad scroll-target-group y la pseudoclase :target-current.
- Asegúrate de que los vínculos de navegación usen identificadores de fragmentos: Tu navegación debe consistir en vínculos de anclaje (
<a>) que dirijan a secciones de la misma página con identificadores de fragmentos (p.ej.,<a href="#section-1">) que coincidan con elidde tus secciones de contenido. - Habilita la agrupación de objetivos de desplazamiento: Aplica la propiedad
scroll-target-group: autoal contenedor de tus vínculos de navegación. Esto le indica al navegador que trate estos vínculos como marcadores que hacen un seguimiento de sus respectivos objetivos. - Estiliza el vínculo activo: Usa la seudoclase
:target-currenten tus vínculos de navegación para aplicar estilos cuando su sección de destino esté visible en el contenedor de desplazamiento. - Estado semántico: Usa una secuencia de comandos para actualizar
aria-current="true"en el vínculo activo cuando se detenga el desplazamiento para garantizar que el estado se comunique correctamente a las tecnologías de accesibilidad.
Ejemplo de código
En este ejemplo, se destaca el vínculo activo en un menú de navegación fijo a medida que el usuario se desplaza por las secciones.
/* 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>
Prácticas recomendadas
- SÍ uses
scroll-target-group: autoen un contenedor exclusivo para vínculos de navegación: Esto garantiza que el navegador sepa de qué vínculos se debe hacer un seguimiento. - SÍ asegúrate de que los destinos tengan atributos
idúnicos que coincidan con los atributoshrefde los vínculos. - SÍ proporciones suficiente espacio vertical para las secciones:
scroll-target-groupidentifica con mayor claridad el objetivo visible cuando las secciones son lo suficientemente grandes como para que solo se vean una o dos a la vez. - OBLIGATORIO: Para la accesibilidad, el estado visual debe reflejarse con
aria-current. Si bien:target-currentcontrola el aspecto visual, este no se expone a los lectores de pantalla. - AVOID depender únicamente del color para el estado activo: Incluye otros indicadores visuales, como el grosor de la fuente, un subrayado o un punto indicador, o asegúrate de que haya suficiente contraste entre los colores para que la diferencia sea accesible para los usuarios con daltonismo.
Estrategias de resguardo
En el caso de los navegadores que no admiten scroll-target-group, debes usar un resguardo basado en la API de 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>