wandres.dev
UNIDADES · px, em, rem, ch, lh, vi, dvh

Unidades de viewport y la barra del navegador

Por qué 100vh es más alto que la pantalla en el móvil, qué son el viewport pequeño, grande y dinámico, y cómo elegir entre svh, lvh y dvh sin provocar reflujos.

⏱ 18 min

100vh fue durante una década la unidad más rota de CSS, y no por un bug: por una decisión deliberada de los fabricantes de navegadores móviles que era razonable y a la vez producía secciones a pantalla completa con el último renglón escondido detrás de la barra de direcciones. Las unidades svh, lvh y dvh no son azúcar sintáctico; son la formalización de un problema que hasta 2022 solo se podía resolver con JavaScript.

🎯 Al terminar esta lección sabrás
  • Explicar contra qué caja se resuelven las unidades de viewport.
  • Describir el conflicto entre barra retráctil y reflujo que originó el problema.
  • Distinguir el viewport pequeño, el grande y el dinámico.
  • Elegir entre svh, lvh y dvh según el elemento.

Contra qué se resuelven

1vw es el 1% del ancho del bloque contenedor inicial, y 1vh el 1% de su alto. El bloque contenedor inicial es la caja del viewport, no la del documento ni la del elemento raíz. vmin y vmax toman la menor y la mayor de las dos dimensiones.

Dos consecuencias que dan problemas todos los días.

100vw incluye la barra de scroll clásica. En un escritorio con barras que ocupan sitio, el ancho del viewport es mayor que el ancho disponible para el contenido, así que un elemento de width: 100vw desborda horizontalmente unos 15 píxeles y aparece una barra horizontal indeseada. La solución no es una unidad, es no usar 100vw para “todo el ancho”: un elemento de bloque ya ocupa el ancho disponible con width: auto, y cuando de verdad necesitas romper un contenedor, scrollbar-gutter: stable en la raíz elimina la discrepancia porque reserva el hueco siempre.

No dependen del contenedor. Un componente dimensionado en vw mide lo mismo dentro de una barra lateral de 280 píxeles que a pantalla completa. Para dimensionar respecto al contenedor están las unidades de contenedor, que son otra familia con otra semántica.

El problema real de la barra del móvil

En un navegador móvil, la barra de direcciones se retrae al hacer scroll hacia abajo y vuelve al hacer scroll hacia arriba. Eso significa que el área visible cambia de altura mientras el usuario navega, sin que haya ningún cambio de orientación ni de ventana.

Aquí los fabricantes tenían que elegir entre dos males.

Si vh siguiera el área visible en cada momento, cada retracción de la barra dispararía un recálculo de estilo y layout de la página entera, en mitad de un gesto de scroll, en el dispositivo más lento del catálogo. El resultado es un scroll que da tirones y contenido que salta bajo el dedo mientras lo lees.

Si vh se congela en un valor, el layout es estable pero deja de corresponder a lo que se ve.

Eligieron congelarlo, y lo congelaron en la altura con la barra retraída, es decir, en la mayor de las dos. Por eso 100vh es más alto que el área visible cuando la barra está desplegada, que es justo el estado inicial al abrir una página: la sección a pantalla completa tiene su parte de abajo tapada, el botón queda fuera, y el usuario ve un corte que sugiere que hay más contenido.

Los tres viewports

CSS Values 4 formaliza la situación definiendo tres viewports y dando unidades a cada uno.

viewport definición unidades
pequeño con toda la interfaz retráctil desplegada svw svh svi svb svmin svmax
grande con toda la interfaz retráctil retraída lvw lvh lvi lvb lvmin lvmax
dinámico el estado actual, cambia en vivo dvw dvh dvi dvb dvmin dvmax

Las tres familias llegaron juntas: Safari 15.4, Firefox 101 y Chrome 108. Son seguras desde hace años.

Las claves para no equivocarse:

  • El viewport pequeño es el más bajo. Nada dimensionado en svh queda nunca tapado.
  • El viewport grande es el más alto, y en la práctica 100vh es igual a 100lvh en los cuatro motores. Las unidades sin prefijo son las de siempre, con el comportamiento de siempre.
  • El viewport dinámico es exacto en todo momento y a cambio cambia de valor durante el scroll, provocando el recálculo que los fabricantes querían evitar.

Elegir entre las tres

La decisión depende de qué le pase al elemento cuando el valor cambie.

svh para todo lo que deba caber entero. Una sección de portada, un panel que no debe recortarse, un contenedor cuyo contenido llega hasta abajo. Con svh el elemento cabe con la barra desplegada, y cuando la barra se retrae simplemente sobra un poco de espacio, que es un fallo invisible.

.portada {
  min-block-size: 100svh;
  display: grid;
  place-content: center;
}

Fíjate en min-block-size en lugar de block-size: si el contenido crece —por una traducción larga, por un tamaño de fuente mayor— la sección crece con él en lugar de desbordar. Fijar la altura exacta de algo que contiene texto es un error independiente de la unidad que uses.

dvh para lo que está anclado a un borde y es pequeño. Una hoja inferior, un teclado emergente, un panel de navegación deslizante. Estos elementos deben seguir el área visible exactamente y son lo bastante pequeños como para que su recálculo no arrastre el layout de la página.

lvh casi nunca. Su caso legítimo es un fondo decorativo que debe cubrir la altura máxima para que no aparezca una franja vacía cuando la barra se retrae.

Nunca dvh en un contenedor de scroll largo. Es el error que produce el peor síntoma de todos: al hacer scroll, la barra se retrae, el contenedor crece, la posición de scroll cambia, y en algunos motores eso vuelve a mover la barra. El contenido pega tirones y en el peor caso oscila.

⚠️
El teclado virtual es un cuarto estado

En un móvil, al abrir el teclado el navegador puede reducir el viewport o dejarlo igual y superponer el teclado. Lo decide el atributo interactive-widget del meta de viewport, cuyo valor por defecto es resizes-visual: el viewport de layout no cambia y por tanto dvh tampoco. Si tu formulario necesita que el área visible refleje el teclado, la opción es interactive-widget=resizes-content en el meta, y entonces sí afecta a dvh. Sin eso, ninguna unidad de CSS te va a dar la altura sin teclado.

Las tres unidades son la especificación admitiendo que no hay respuesta correcta

Lo más honesto de esta parte de CSS Values es que no elige. Podrían haber decidido que vh pasara a significar el viewport dinámico y arreglar de golpe todos los sitios rotos, a cambio de romper el scroll en los millones de páginas que dependían del comportamiento congelado. Podrían haber elegido el pequeño y dejar franjas vacías por todas partes. En lugar de eso reconocieron que el conflicto entre exactitud y estabilidad no se resuelve, se decide caso por caso, y le dieron al autor las tres respuestas para que elija la suya en cada elemento. Ese patrón aparece cada vez que una especificación madura: overflow-anchor, content-visibility, will-change y contain son exactamente lo mismo —controles para decidir un compromiso que el navegador venía decidiendo por ti con una heurística. La consecuencia práctica es que a partir de cierto nivel dejas de preguntarte cuál es la unidad correcta y empiezas a preguntarte cuál es el fallo que prefieres: un poco de espacio de más que nadie nota, un corte que se ve, o un scroll que tiembla. Esa pregunta tiene respuesta; “cuál es la buena” no la tiene.