Skip to content
This page has been auto-translated and may contain errors.View in English

Posicionamiento e índice z

docs.scrimba.com

Quieres una pequeña insignia "Oferta" en la esquina superior derecha de una foto de producto. Quieres un encabezado que se mantenga en su lugar mientras la página se desplaza debajo de él. Quieres un diálogo que flota sobre todo lo demás en la página. El espaciado CSS ordinario no puede hacer nada de esto, porque solo organiza elementos uno tras otro. El posicionamiento es el conjunto de herramientas que permite que un elemento salga de esa disposición ordinaria y se sitúe exactamente donde lo señales, incluso encima de otra cosa.

La propiedad position

Cada elemento comienza con posicionamiento estático, que es una forma simple de decir "siéntate en tu lugar normal en el flujo de la página". Ese es el comportamiento predeterminado, y la mayoría de las veces es lo que quieres. El posicionamiento comienza a importar cuando quieres que un elemento se aleje de ese lugar normal.

El primer paso es position: relative. Desplaza un elemento desde donde estaría normalmente, usando cuatro propiedades de desplazamiento: top, right, bottom y left. Lo importante es que el elemento mantiene su espacio original. Nada más se mueve para llenar el vacío.

css
.badge {
  position: relative;
  top: 10px;                 /* desplazado 10px hacia abajo desde su lugar normal */
  left: 20px;                /* desplazado 20px hacia la derecha desde su lugar normal */
}

Esos desplazamientos top y left no hacen nada en absoluto en un elemento static. Solo comienzan a funcionar una vez que le das al elemento un position diferente de static.

Todo en la página comienza con position: static, su lugar en el flujo normal de arriba hacia abajo que conociste en display y flujo del documento. Los elementos estáticos ignoran completamente las propiedades de desplazamiento, por lo que establecer top o left en ellos no hace nada. Esto atrapa a la gente: añaden top: 20px, no ven cambio, y asumen que el valor es incorrecto cuando el problema real es un position faltante.

La opción suave es posicionamiento relativo. Desplaza un elemento desde su lugar normal por los desplazamientos que das, mientras dejas su espacio original reservado en el flujo. Los vecinos no se mueven para cerrar el vacío, por lo que un elemento posicionado relativamente puede terminar solapándose con lo que está junto a él.

css
.tooltip {
  position: relative;
  top: -4px;                 /* elevado 4px hacia arriba desde donde estaría */
  left: 8px;                 /* desplazado 8px hacia la derecha */
  /* el espacio reservado se mantiene en su lugar; solo se mueve la pintura */
}

Los desplazamientos como top y left solo se aplican una vez que un elemento está posicionado. Por sí solo, relative es una pequeña herramienta de desplazamiento. Su trabajo más importante es establecer un punto de referencia para los hijos posicionados absolutamente, que es la próxima sección.

La propiedad position tiene cinco valores, y todo el modelo es más fácil de entender si los divides por una pregunta: ¿el elemento sigue ocupando espacio en el flujo normal? static (el valor inicial) y relative lo hacen; absolute y fixed no; sticky es un híbrido que alterna entre los dos comportamientos mientras desplazas.

relative es la salida más suave de static. El elemento se distribuye en el flujo normal exactamente como si fuera estático, su espacio se reserva en esa ubicación original, y luego se desplaza visualmente desde esa posición por top / right / bottom / left. Como el espacio reservado nunca se mueve, desplazar un elemento relativo solo cambia dónde pinta, no dónde piensa el diseño que está, por lo que puede solaparse con contenido adyacente sin empujar nada.

css
.list-item {
  position: relative;
  top: 6px;                  /* pintado 6px más bajo; su ranura de flujo se mantiene en su lugar */
}

Dos puntos de precisión. Primero, los desplazamientos se resuelven contra la posición normal del elemento mismo, no contra ningún ancestro: top: 10px significa "10px debajo de donde estarías de otra manera", no "10px desde la parte superior del contenedor". Segundo, si estableces desplazamientos opuestos (top y bottom, o left y right) en un elemento relativo, el lado de inicio gana en un modo de escritura de izquierda a derecha, de arriba a abajo, por lo que top anula bottom e left anula right. La razón mucho más común para llegar a relative no es el desplazamiento en sí; es establecer un bloque contenedor para descendientes absolute, que la próxima sección amplía.

JunoLa propiedad position Todo comienza como static, lo que significa que se sienta en su lugar normal. Cambia a position: relative y puedes desplazarlo con top, right, bottom y left, mientras su espacio original permanece reservado. Esas propiedades de desplazamiento no hacen nada hasta que el elemento está posicionado, así que si top parece ignorado, verifica que hayas establecido un position primero.
JunoLa propiedad positionstatic es el predeterminado e ignora los desplazamientos por completo, por lo que top: 20px en un elemento simple no hace nada. relative desplaza el elemento desde su lugar normal mientras mantiene su espacio reservado, por lo que los vecinos nunca se desplazan para llenar el vacío. El desplazamiento es útil, pero la razón real por la que llegas a relative es para anclar hijos posicionados absolutamente, que viene a continuación.
JunoLa propiedad position Ordena los cinco valores por si mantienen su espacio en flujo: static y relative lo hacen, absolute y fixed no, sticky alterna entre los dos. relative resuelve los desplazamientos contra la posición normal del elemento, no su contenedor, y los desplazamientos opuestos dejan que el lado de inicio gane. Usarás relative mucho más como anclaje para hijos absolutos que como herramienta de desplazamiento.

Posicionamiento absoluto

position: absolute es un movimiento más grande. Extrae el elemento completamente del flujo normal, por lo que ya no ocupa espacio, y los elementos alrededor se cierran como si nunca hubiera estado allí. Luego lo colocas usando los mismos desplazamientos top, right, bottom y left.

La pregunta es: ¿desplazamiento desde qué? Un elemento absoluto se posiciona contra su ancestro posicionado más cercano, es decir, el padre más cercano que tiene un position propio (cualquier cosa que no sea static). Por eso el patrón de insignia en una tarjeta siempre viene en pareja: haces la tarjeta relative para que se convierta en el punto de referencia, luego haces la insignia absolute para que pueda sentarse en la esquina de la tarjeta.

css
.card {
  position: relative;        /* se convierte en el punto de referencia */
}

.card .badge {
  position: absolute;
  top: 8px;                  /* 8px desde la parte superior de la tarjeta */
  right: 8px;                /* 8px desde la derecha de la tarjeta */
}

Sin position: relative en la tarjeta, la insignia buscaría más arriba en la página un ancestro posicionado y terminaría en algún lugar donde no lo querías.

El posicionamiento absoluto elimina un elemento del flujo normal por completo. No ocupa espacio, sus antiguos vecinos se cierran como si se hubiera ido, y luego lo colocas con las propiedades de desplazamiento. Los desplazamientos se miden contra el bloque contenedor del elemento: el borde de relleno de su ancestro más cercano que está posicionado.

Esa regla del "ancestro posicionado más cercano" es el corazón del patrón que usarás constantemente. Establece el padre en position: relative (que por sí solo no cambia nada visible), y se convierte en el marco de referencia para cualquier hijo absoluto dentro de él.

css
.dropdown {
  position: relative;        /* el marco de referencia, sin cambio visual */
}

.dropdown .menu {
  position: absolute;
  top: 100%;                 /* directamente debajo del activador */
  left: 0;                   /* alineado a su borde izquierdo */
  width: 200px;
}

Si ningún ancestro está posicionado, el elemento absoluto cae al bloque contenedor inicial, aproximadamente el viewport, y típicamente termina en la esquina superior izquierda de la página. Un elemento absoluto colocado incorrectamente casi siempre es un position: relative faltante en el padre que querías anclar.

Un elemento posicionado absolutamente se extrae del flujo normal: no genera una caja en el flujo, no contribuye a la altura de su padre, y sus hermanos se distribuyen como si no existiera. Luego se posiciona en relación a su bloque contenedor, y obtener exactamente las reglas del bloque contenedor es lo que separa "funciona" de "funciona y sé por qué".

Para un elemento absolute, el bloque contenedor es la caja de relleno de su ancestro más cercano cuyo position no es static. Si varios ancestros califican, el más cercano gana. Si ninguno califica, el bloque contenedor es el bloque contenedor inicial, un rectángulo del tamaño del viewport anclado al documento, por eso un elemento absoluto sin ancla se desplaza a la esquina superior izquierda de la página.

css
.field {
  position: relative;        /* establece el bloque contenedor */
  padding: 12px;
}

.field .clear-button {
  position: absolute;
  top: 50%;
  right: 8px;
  transform: translateY(-50%);   /* centro vertical contra .field */
  /* los desplazamientos se miden desde la caja de relleno de .field, no su caja de contenido */
}

Una regla que sorprende a la gente e importa para la imagen avanzada del bloque contenedor: un transform, filter o will-change en un ancestro también establece un bloque contenedor para descendientes absolutos, incluso sin un position establecido. Así que un hijo absoluto puede de repente reparentarse a sus desplazamientos en un abuelo transformado que no pensaste que fuera un anclaje. Cuando un elemento absoluto se posiciona contra el elemento "incorrecto", verifica un transform perdido en el árbol, no solo un position faltante.

JunoPosicionamiento absolutoposition: absolute extrae un elemento del flujo, por lo que no ocupa espacio y los vecinos se cierran detrás de él. Luego se posiciona contra su padre más cercano que tiene un position establecido. Por eso el truco de insignia en tarjeta es una pareja: relative en la tarjeta, absolute en la insignia, así que la insignia aterrizará en la esquina de la tarjeta y no en algún lugar aleatorio.
JunoPosicionamiento absoluto Un elemento absoluto deja el flujo completamente y se posiciona contra su bloque contenedor, el ancestro más cercano que está posicionado. El patrón cotidiano es position: relative en el padre, que no cambia nada visualmente pero se convierte en el marco de referencia, luego absolute en el hijo. Cuando un elemento absoluto aterrizas en la esquina superior izquierda de la página, significa que ningún ancestro fue posicionado y cayó al viewport.
JunoPosicionamiento absoluto El bloque contenedor para un elemento absoluto es la caja de relleno de su ancestro no-static más cercano, o el viewport si ninguno existe. La trampa es que transform, filter y will-change también crean un bloque contenedor sin position a la vista, por lo que un hijo puede anclarse a un abuelo transformado que olvidaste. Cuando los desplazamientos aterrizan contra el elemento incorrecto, examina el árbol en busca de un transform extraviado, no solo un position faltante.

Fijo y pegajoso

position: fixed fija un elemento a la pantalla misma. Sale del flujo como lo hace absolute, pero en lugar de anclarse a un padre, se ancla al viewport, la ventana visible. Una vez fijo, permanece en el mismo lugar en la pantalla incluso mientras desplazas la página. Así es como una barra de navegación o un botón "volver al inicio" se mantiene visible todo el tiempo.

css
.site-header {
  position: fixed;
  top: 0;                    /* fijado a la parte superior de la ventana */
  left: 0;
  right: 0;                  /* estirado a todo el ancho */
}

position: sticky es el término medio amigable. Un elemento pegajoso se desplaza junto con la página normalmente, hasta que alcanza un umbral que estableces, y luego se queda en su lugar. Un encabezado de sección que se desplaza hacia arriba con su contenido y luego se mantiene en la parte superior de la pantalla es el ejemplo clásico.

css
.section-title {
  position: sticky;
  top: 0;                    /* se pega una vez que alcanza el borde superior */
}

El posicionamiento fijo funciona como absoluto, excepto que el bloque contenedor es siempre el viewport en lugar de un ancestro. Un elemento fijo se extrae del flujo y luego se fija a un lugar fijo en la pantalla, por lo que no se mueve cuando la página se desplaza. Eso lo convierte en la herramienta para el cromo persistente: una barra superior, un banner de cookies, un botón de acción flotante.

css
.site-header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 64px;
  /* el contenido debajo necesita padding-top: 64px para que no esté oculto */
}

Ese comentario señala el problema principal. Porque un encabezado fijo está fuera del flujo, se superpone al contenido debajo, así que típicamente añades relleno o margen coincidente al cuerpo de la página para empujar el contenido hacia un lado.

El posicionamiento pegajoso es un híbrido: el elemento se comporta como relative mientras está a la vista, luego cambia al comportamiento tipo fixed una vez que alcanza el umbral que estableciste con top, bottom, left o right. Un elemento pegajoso sin uno de esos desplazamientos de umbral nunca se pega, porque no tiene línea a la que pegarse.

css
.section-title {
  position: sticky;
  top: 16px;                 /* se mantiene 16px desde la parte superior una vez que llega allí */
}

fixed es absolute con una diferencia que importa: su bloque contenedor es el viewport, por lo que se fija a la pantalla y no se desplaza con el documento. Eso lo hace adecuado para la interfaz de usuario persistente, con dos advertencias. Se superpone al contenido en flujo (reserva espacio con relleno en el cuerpo), y la misma regla de transform-establece-un-bloque-contenedor de absolute se aplica aquí también, así que un transform ancestro atrapará un elemento fixed dentro de ese ancestro y se desplazará con él. Un elemento "fijo" que misteriosamente se desplaza casi siempre está bajo un ancestro transformado.

El posicionamiento pegajoso es el más condicional de los cinco. Un elemento pegajoso se distribuye en el flujo normal, mantiene su espacio, y se queda allí hasta que su ancestro de desplazamiento más cercano lo desplaza al umbral que declaraste; desde ese punto se mantiene en el umbral hasta que su bloque contenedor se desplaza más allá, en cuyo punto se libera. Dos requisitos son los que la gente pierde:

css
.toc {
  position: sticky;
  top: 24px;                 /* el umbral; sin un desplazamiento nunca se pega */
  /* se pega solo mientras el padre de .toc todavía está en pantalla */
}

Primero, pegajoso necesita un desplazamiento de umbral (top, bottom, left o right); sin ninguno, no hay línea para engancharse y se comporta como un elemento relativo simple. Segundo, solo se puede pegar dentro de los límites de su padre, así que si el padre es corto, el elemento se desadhiere tan pronto como el padre se desplaza fuera de la vista. Y un fallo silencioso frecuente: un ancestro con overflow: hidden, scroll o auto se convierte en el contenedor de desplazamiento del elemento pegajoso, así que un elemento pegajoso puede hacer silenciosamente nada porque está confinado a un contenedor que no es el que esperabas que se desplazara.

JunoFijo y pegajosoposition: fixed fija un elemento a la pantalla, por lo que permanece en su lugar mientras desplazas, que es cómo un encabezado o un botón de volver al inicio se mantiene. position: sticky es el término medio: se desplaza con la página, luego se pega una vez que alcanza un umbral como top: 0. Fijo también se superpone a lo que hay debajo, así que deja algo de espacio para el contenido que cubre.
JunoFijo y pegajosofixed es absoluto anclado al viewport, por lo que se queda en la pantalla mientras la página se desplaza, al costo de solaparse con contenido en flujo a menos que lo rellenes. sticky se comporta como relative hasta que alcanza su desplazamiento de umbral, luego se mantiene en su lugar. Un elemento pegajoso sin top o bottom establecido nunca se pega, porque no hay línea para que se enganche.
JunoFijo y pegajosofixed se fija al viewport, pero un transform ancestro lo atrapa dentro de ese ancestro y se desplazará, así que un elemento "fijo" que se desplaza usualmente tiene un padre transformado. sticky necesita dos cosas que la gente olvida: un desplazamiento de umbral y un padre lo suficientemente alto, ya que se libera el momento en que su padre se desplaza hacia afuera. Y vigila un ancestro con overflow establecido, que se convierte en el contenedor de desplazamiento y puede silenciosamente detener el funcionamiento de pegajoso.

Apilamiento con z-index

Cuando los elementos se superponen, algo tiene que decidir cuál se sienta en la parte superior. Eso es lo que controla z-index. Un z-index más alto se dibuja delante de uno más bajo, así que un desplegable con z-index: 10 cubre una tarjeta con z-index: 1.

Hay una regla que atrapa a todos: z-index solo funciona en elementos posicionados. Si un elemento todavía es static, su z-index se ignora. Así que casi siempre estableces un position y un z-index juntos.

css
.modal {
  position: fixed;
  z-index: 100;              /* se sienta encima del contenido normal de la página */
}

.modal-overlay {
  position: fixed;
  z-index: 99;               /* el fondo oscuro, un paso detrás del modal */
}

Rara vez necesitas números enormes aquí. Una escala pequeña y ordenada como 1, 10, 100 es más fácil de razonar que una página llena de 9999s compitiendo.

Cuando dos elementos se superponen, z-index establece su orden de adelante a atrás: números más altos se pintan encima de los más bajos. La captura es que z-index solo afecta elementos posicionados. Dale a un elemento static un z-index y nada sucede, así que z-index y position casi siempre viajan juntos.

css
.modal {
  position: fixed;
  inset: 0;
  z-index: 100;
}

.tooltip {
  position: absolute;
  z-index: 50;               /* encima de tarjetas, debajo del modal */
}

El hábito que vale la pena formar temprano es una escala pequeña y deliberada en lugar de números escalados. Cuando te encuentras escribiendo z-index: 9999 para vencer algo, la verdadera solución es típicamente un plan para tus valores de apilamiento, no un número más grande. El próximo nivel explica por qué a veces un número más grande pierde de todas maneras.

z-index ordena elementos posicionados superpuestos a lo largo del eje z, más alto adelante, e se ignora en elementos static. La parte que convierte un error de cinco minutos en una hora es el contexto de apilamiento: un grupo independiente dentro del cual los valores z-index se comparan, y que se apila como una unidad única contra sus hermanos.

Un contexto de apilamiento se forma siempre que un elemento está posicionado con un z-index distinto de auto, pero también desde opacity por debajo de 1, cualquier transform, filter, will-change, isolation: isolate, y un puñado de otros. Crucialmente, z-index solo se compara dentro del mismo contexto de apilamiento. Una vez que un elemento se sienta dentro de un contexto, su z-index no puede levantarlo por encima de nada fuera de ese contexto, no importa qué tan grande sea el número.

css
.card {
  opacity: 0.98;             /* esto solo crea un contexto de apilamiento */
}

.card .badge {
  position: absolute;
  z-index: 9999;             /* atrapado dentro del contexto de .card */
}

.sibling-panel {
  position: relative;
  z-index: 2;                /* aún se pinta encima de .badge */
}

Aquí .badge en 9999 pierde contra .sibling-panel en 2, porque .badge está confinado al contexto que la opacity de .card creó, y ese contexto completo se clasifica por debajo de .sibling-panel. Cuando un z-index alto se niega a ganar, la respuesta casi nunca es un número más alto; es un ancestro que abrió silenciosamente un contexto de apilamiento (a menudo una opacity o un transform), y la solución es elevar o aislar ese ancestro en su lugar.

Un punto de cierre sobre criterio: el posicionamiento y z-index son para superposiciones, insignias y cromo fijado, las cosas que necesitan escapar del flujo o sentarse en la parte superior. Son la herramienta incorrecta para organizar la estructura principal de una página. Para filas, columnas y cuadrículas que deberían responder a su contenido y al viewport, recurre a flexbox o el flujo impulsado por el modelo de caja en el modelo de caja, y mantén el posicionamiento para la capa que flota encima.

JunoApilamiento con z-index Cuando los elementos se superponen, z-index decide cuál está al frente: un número más alto gana. Solo funciona en elementos posicionados, así que un z-index en un elemento static simple se ignora, y estableces position y z-index juntos. Mantén los números pequeños y ordenados, como 1, 10, 100, en lugar de un montón de 9999s.
JunoApilamiento con z-indexz-index establece el orden de adelante a atrás para elementos posicionados, y no hace nada en los static, así que viaja junto con position. Recurre a una escala deliberada y pequeña en lugar de números escalados; si estás escribiendo 9999 para ganar, necesitas un plan, no un valor más grande. Y a veces un valor más grande pierde de todas maneras, que la inmersión profunda en contextos de apilamiento explica.
JunoApilamiento con z-indexz-index solo se compara dentro de un contexto de apilamiento, y cosas como opacity por debajo de 1, transform y filter silenciosamente crean uno. Por eso un hijo en z-index: 9999 puede aún perder contra un elemento externo en 2: está atrapado en el contexto de su ancestro. Cuando un número alto no ganará, busca el ancestro que abrió un contexto y eleva ese, y mantén el posicionamiento para superposiciones en lugar de diseño de página.