Vas por la mitad de una página, se carga un anuncio o un puñado de posts nuevos por encima y el párrafo que estabas leyendo se te escapa de la pantalla. Desde Safari 27, todos los navegadores principales evitan ese salto por ti. El scroll anchoring (anclaje del scroll) y la propiedad overflow-anchor que lo controla son Baseline desde el 14 de septiembre de 2026.
Lo que me sorprendió es lo viejo que es: los datos de compatibilidad de MDN indican Chrome 56 y Firefox 66. Lo que faltaba era Safari, y con él el iPhone.
Qué hace el scroll anchoring
Lo clásico en un contenedor con scroll es mantener fijo el desplazamiento. Si aparece algo de 300 px por encima de lo que estás mirando, el desplazamiento no cambia, así que todo lo que ves baja 300 px. La introducción de la especificación nombra a los culpables habituales: un script que inserta el iframe de un anuncio o imágenes sin dimensiones que se cargan con una red lenta.
El scroll anchoring le da la vuelta. El navegador elige un nodo ancla dentro del contenedor, recuerda dónde está y, cuando un cambio de layout lo mueve, ajusta el desplazamiento exactamente esa misma cantidad. Tu posición de scroll cambia, pero lo que estás mirando se queda quieto.
Eso es lo que enseña la demo de arriba. Los dos paneles son el mismo feed, la tarjeta resaltada es "lo que estás leyendo" y la línea discontinua marca dónde estaba. Pulsa "+ 3 posts arriba" o carga el anuncio: a la izquierda la tarjeta cae y el contador suma los píxeles que se ha movido; a la derecha se queda en 0 px y lo que crece es scrollTop. "Feed automático" mete posts cada 1,4 segundos, que es justo el caso de un timeline con elementos nuevos, donde más se nota.
El código: viene activado
No hay nada que activar. El único CSS de la demo es la desactivación del panel izquierdo:
.feed {
overflow-y: auto; /* aquí el scroll anchoring se aplica por defecto */
}
.bad .feed {
overflow-anchor: none; /* el comportamiento de siempre: el contenido salta */
}
overflow-anchor acepta dos valores. auto, el valor inicial, hace que un elemento pueda ser ancla. none saca de la selección de ancla al elemento y a sus descendientes, y puesto en el propio contenedor desactiva el anclaje de ese contenedor. La propiedad no se hereda, pero la especificación aclara que no puedes volver a activar el anclaje dentro de un subárbol con none, con una excepción: un contenedor con scroll anidado recupera su propio anclaje salvo que también diga none.
También conviene saber cómo se elige el ancla. El algoritmo de la especificación recorre los hijos del contenedor buscando un nodo totalmente visible, y baja un nivel cuando un nodo solo es visible en parte, porque es menos probable que un nodo profundo cambie por dentro. Se saltan los elementos con display: none, position: fixed, position: sticky o una caja absolute posicionada respecto a algo de fuera del contenedor.
El plan B, si lo necesitas
En navegadores sin anclaje nativo, la demo lo emula con la misma idea en pocas líneas: apuntar la posición de la tarjeta que lees, insertar el contenido y compensar la diferencia.
if (!CSS.supports('overflow-anchor', 'auto')) {
const before = reader.offsetTop;
insertAbove(); // lo que sea que añade contenido arriba
feed.scrollTop += reader.offsetTop - before; // devuelve al lector a su sitio
}
offsetTop es relativo al feed porque tiene position: relative. Probé este fragmento en Chromium con el anclaje nativo desactivado y la tarjeta se movió 0 px. No lo ejecutes donde el anclaje es nativo: compensarías dos veces. En la demo, cuando el plan B está activo, el panel derecho lleva además overflow-anchor: none para que solo lo ajuste el script.
Trampas que encontré o comprobé
- Arriba del todo no pasa nada. La especificación dice que si el contenedor no se ha desplazado desde su origen, no se elige ancla. Lo probé: con
scrollTopa 0, insertar un elemento de 50 px arriba movió el contenido 52 px (el elemento más sus bordes). Es a propósito: si estás arriba del todo en un feed, seguramente quieres ver lo nuevo. - Solo protege al ancla. Mientras montaba la demo en una pantalla de 375 px, el anuncio se insertaba al principio entre la parte visible de arriba del panel y la tarjeta que lees. Siguiendo el algoritmo de la especificación, el ancla es la primera tarjeta totalmente visible desde arriba, que quedaba por encima del punto de inserción. Esa tarjeta no se movió, y la mía bajó 150 px igualmente. Es correcto según la especificación, y un buen recordatorio: el anclaje compensa los cambios por encima del nodo ancla, no todo lo que hay en pantalla.
- Disparadores de supresión. Si, dentro de la misma ventana de supresión (más o menos, la misma vuelta del event loop), algo cambia
top/left/right/bottom,margin,padding, una propiedad de ancho o alto,positionotransformen el camino entre el nodo ancla y el contenedor, el ajuste se cancela. - Lanza eventos de scroll. El ajuste cuenta como un scroll, así que los listeners de
scrolllo verán. La guía de MDN dice que si una página se porta mal con el anclaje, lo normal es que haya un listener de scroll que no espera ese desplazamiento extra, y que Firefox puede resaltar el ancla elegida con el flaglayout.css.scroll-anchoring.highlightenabout:config. - No sustituye a reservar espacio. Poner
widthyheighten las imágenes o unmin-heighten los huecos de anuncios sigue evitando el desplazamiento desde el origen, también arriba del todo, donde el anclaje no actúa.
Qué vigilar
- Safari 27 en la calle. Baseline "newly available" significa que lo soportan las últimas versiones, no que todo el mundo haya actualizado. Hasta que los iPhone antiguos desaparezcan, quien use Safari 26 o anterior seguirá viendo el salto.
- Feeds y chats. Añadir mensajes antiguos o posts nuevos por arriba era un sitio típico para arreglos a mano con
scrollTop. Ahora que todos los motores anclan, esos arreglos pueden compensar dos veces, así que es buen momento para revisar si siguen haciendo falta. - La especificación sigue siendo un borrador. La ventana de supresión, el trozo del event loop en el que se agrupan los ajustes, solo está definida en este borrador; la propia especificación indica que debería acabar en el estándar HTML.