Dentro del polyfill de la consulta del contenedor

Gerald Monaco
Gerald Monaco

Las consultas de contenedores son una nueva funci�n de CSS que te permite escribir l�gicas de dise�o orientadas a los componentes de un elemento superior (por ejemplo, su ancho o alto) para definir el estilo de sus elementos secundarios. Recientemente, se lanz� una gran actualizaci�n del polyfill, que coincidi� con la compatibilidad en navegadores.

En esta publicaci�n, podr�s ver c�mo funciona el polyfill, los desaf�os que supera y las pr�cticas recomendadas para usarlo a fin de brindar una experiencia del usuario excelente a tus visitantes.

Detr�s de escena

Transpilaci�n

Cuando el analizador de CSS de un navegador encuentra una regla at desconocida, como la regla @container nueva, la descartar� como si nunca hubiera existido. Por lo tanto, lo primero y m�s importante que debe hacer el polyfill es transpilar una consulta de @container a algo que no se descarte.

El primer paso en la transpilaci�n es convertir la regla @container de nivel superior en una consulta @media. Esto garantiza, principalmente, que el contenido permanezca agrupado. Por ejemplo, cuando se usan las APIs de CSSOM y se visualiza la fuente de CSS.

Antes
@container (width > 300px) {
  /* content */
}
Despu�s
@media all {
  /* content */
}

Antes de las consultas de contenedores, el CSS no ten�a forma de que un autor habilitara o inhabilitara de forma arbitraria grupos de reglas. Para aplicar polyfills en este comportamiento, tambi�n se deben transformar las reglas dentro de una consulta de contenedor. A cada @container se le da su propio ID �nico (por ejemplo, 123), que se usa para transformar cada selector de modo que solo se aplique cuando el elemento tenga un atributo cq-XYZ que incluya este ID. El polyfill establecer� este atributo durante el tiempo de ejecuci�n.

Antes
@container (width > 300px) {
  .card {
    /* ... */
  }
}
Despu�s
@media all {
  .card:where([cq-XYZ~="123"]) {
    /* ... */
  }
}

Ten en cuenta el uso de la seudoclase :where(...). Normalmente, la inclusi�n de un selector de atributos adicional aumentar� la especificidad del selector. Con la seudoclase, se puede aplicar la condici�n adicional mientras se conserva la especificidad original. Para entender por qu� esto es tan importante, considera el siguiente ejemplo:

@container (width > 300px) {
  .card {
    color: blue;
  }
}

.card {
  color: red;
}

Dado este CSS, un elemento con la clase .card siempre debe tener color: red, ya que la regla posterior siempre anular� a la anterior con el mismo selector y la misma especificidad. Por lo tanto, transpilar la primera regla e incluir un selector de atributos adicional sin :where(...) aumentar�a la especificidad y har�a que color: blue se aplicara de forma err�nea.

Sin embargo, la seudoclase :where(...) es bastante nueva. Para los navegadores que no lo admiten, el polyfill proporciona una soluci�n alternativa segura y sencilla: puedes aumentar intencionalmente la especificidad de tus reglas agregando manualmente un selector :not(.container-query-polyfill) ficticio a tus reglas @container:

Antes
@container (width > 300px) {
  .card {
    color: blue;
  }
}

.card {
  color: red;
}
Despu�s
@container (width > 300px) {
  .card:not(.container-query-polyfill) {
    color: blue;
  }
}

.card {
  color: red;
}

Esto tiene varios beneficios:

  • El selector del CSS de origen cambi�, por lo que la diferencia de especificidad es visible de forma expl�cita. Esto tambi�n act�a como documentaci�n para que sepas qu� se ve afectado cuando ya no necesitas admitir la soluci�n alternativa o el polyfill.
  • La especificidad de las reglas siempre ser� la misma, ya que el polyfill no lo cambia.

Durante la transpilaci�n, el polyfill reemplazar� este elemento ficticio con el selector de atributos de la misma especificidad. Para evitar sorpresas, el polyfill usa ambos selectores: el selector de fuente original se usa para determinar si el elemento debe recibir el atributo de polyfill y el selector transpilado se usa para definir el estilo.

Pseudoelementos

Podr�as hacerte esta pregunta: si el polyfill establece alg�n atributo cq-XYZ en un elemento para incluir el ID �nico de contenedor 123, �c�mo se pueden admitir los pseudoelementos, que no pueden tener atributos configurados?

Los seudoelementos siempre est�n vinculados a un elemento real en el DOM, llamado elemento de origen. Durante la transpilaci�n, el selector condicional se aplica a este elemento real en su lugar:

Antes
@container (width > 300px) {
  #foo::before {
    /* ... */
  }
}
Despu�s
@media all {
  #foo:where([cq-XYZ~="123"])::before {
    /* ... */
  }
}

En lugar de transformarse en #foo::before:where([cq-XYZ~="123"]) (que no ser�a v�lido), el selector condicional se mueve al final del elemento de origen, #foo.

Sin embargo, eso no es todo lo que necesitas. Un contenedor no puede modificar nada que no est� dentro de �l (y un contenedor no puede estar dentro de s� mismo), pero ten en cuenta que eso es lo que suceder�a exactamente si #foo fuera el mismo elemento del contenedor que se consulta. Se cambiar�a el atributo #foo[cq-XYZ] por error y se aplicar�a cualquier regla #foo.

Para corregir esto, el polyfill en realidad usa dos atributos: uno que solo puede aplicarse a un elemento con un elemento superior y otro que un elemento puede aplicarse a s� mismo. El �ltimo atributo se usa para los selectores orientados a pseudoelementos.

Antes
@container (width > 300px) {
  #foo,
  #foo::before {
    /* ... */
  }
}
Despu�s
@media all {
  #foo:where([cq-XYZ-A~="123"]),
  #foo:where([cq-XYZ-B~="123"])::before {
    /* ... */
  }
}

Dado que un contenedor nunca se aplicar� el primer atributo (cq-XYZ-A) a s� mismo, el primer selector solo coincidir� si un contenedor superior diferente cumpli� con las condiciones del contenedor y lo aplic�.

Unidades relativas del contenedor

Las consultas de contenedor tambi�n vienen con algunas unidades nuevas que puedes usar en tu CSS, como cqw y cqh para el 1% del ancho y la altura (respectivamente) del contenedor superior adecuado m�s cercano. Para admitirlas, la unidad se transforma en una expresi�n calc(...) con las propiedades personalizadas de CSS. El polyfill establecer� los valores de estas propiedades mediante estilos intercalados en el elemento del contenedor.

Antes
.card {
  width: 10cqw;
  height: 10cqh;
}
Despu�s
.card {
  width: calc(10 * --cq-XYZ-cqw);
  height: calc(10 * --cq-XYZ-cqh);
}

Tambi�n hay unidades l�gicas, como cqi y cqb para el tama�o intercalado y el tama�o del bloque (respectivamente). Estos son un poco m�s complicados, porque los ejes de intercalado y de bloque est�n determinados por el writing-mode del elemento que usa la unidad, no por el elemento que se consulta. Para ello, el polyfill aplica un dise�o intercalado a cualquier elemento cuyo writing-mode difiera de su elemento superior.

/* Element with a horizontal writing mode */
--cq-XYZ-cqi: var(--cq-XYZ-cqw);
--cq-XYZ-cqb: var(--cq-XYZ-cqh);

/* Element with a vertical writing mode */
--cq-XYZ-cqi: var(--cq-XYZ-cqh);
--cq-XYZ-cqb: var(--cq-XYZ-cqw);

Ahora, las unidades se pueden transformar en la propiedad personalizada de CSS correspondiente, igual que antes.

Propiedades

Las consultas de contenedores tambi�n agregan algunas propiedades de CSS nuevas, como container-type y container-name. Dado que las APIs como getComputedStyle(...) no se pueden usar con propiedades desconocidas o no v�lidas, estas tambi�n se transforman en propiedades personalizadas de CSS despu�s de su an�lisis. Si una propiedad no se puede analizar (por ejemplo, porque contiene un valor no v�lido o desconocido), simplemente se deja sola para que la controle el navegador.

Antes
.card {
  container-name: card-container;
  container-type: inline-size;
}
Despu�s
.card {
  --cq-XYZ-container-name: card-container;
  --cq-XYZ-container-type: inline-size;
}

Estas propiedades se transforman cada vez que se descubren, lo que permite que el polyfill se comporte a la perfecci�n con otras funciones de CSS, como @supports. Esta funcionalidad es la base de las pr�cticas recomendadas para usar polyfill, que se describen a continuaci�n.

Antes
@supports (container-type: inline-size) {
  /* ... */
}
Despu�s
@supports (--cq-XYZ-container-type: inline-size) {
  /* ... */
}

De forma predeterminada, las propiedades personalizadas de CSS se heredan, lo que significa que cualquier elemento secundario de .card tomar� el valor de --cq-XYZ-container-name y --cq-XYZ-container-type. Definitivamente, no se comportan as� las propiedades nativas. Para resolver esto, el polyfill insertar� la siguiente regla antes de los estilos de los usuarios y garantizar� que cada elemento reciba los valores iniciales, a menos que otra regla lo anule intencionalmente.

* {
  --cq-XYZ-container-name: none;
  --cq-XYZ-container-type: normal;
}

Pr�cticas recomendadas

Si bien se espera que, pronto, la mayor�a de los visitantes utilicen navegadores con compatibilidad integrada para consultas en contenedores, sigue siendo importante ofrecer una buena experiencia al resto de los visitantes.

Durante la carga inicial, deben suceder varias cosas para que el polyfill pueda dise�ar la p�gina:

  • Se debe cargar e inicializar el polyfill.
  • Las hojas de estilo se deben analizar y transpilar. Debido a que no existe ninguna API para acceder a la fuente sin procesar de una hoja de estilo externa, puede que sea necesario volver a recuperarla de manera asincr�nica, aunque lo ideal ser�a hacerlo solo desde la cach� del navegador.

Si el polyfill no resuelve estas inquietudes, es posible que tus M�tricas web esenciales vuelvan a generarse.

Para que puedas brindar a los visitantes una experiencia agradable con mayor facilidad, el polyfill se dise�� para priorizar el Retraso de primera entrada (FID) y el Cambio de dise�o acumulado (CLS), posiblemente a expensas del Procesamiento de imagen con contenido m�s grande (LCP). En concreto, el polyfill no garantiza que las consultas de tu contenedor se evaluar�n antes de la primera pintura. Esto significa que, para brindar la mejor experiencia del usuario, debes asegurarte de que todo el contenido cuyo tama�o o posici�n se vea afectado por las consultas de contenedor est� oculto hasta que el polyfill haya cargado y transpilado tu CSS. Una forma de lograrlo es con una regla @supports:

@supports not (container-type: inline-size) {
  #content {
    visibility: hidden;
  }
}

Se recomienda combinar esto con una animaci�n de carga de CSS pura, absolutamente posicionada sobre tu contenido (oculto), para indicarle al visitante que algo est� sucediendo. Puede encontrar una demostraci�n completa de este enfoque aqu�.

Este enfoque se recomienda por varios motivos:

  • Un cargador de CSS puro minimiza la sobrecarga para los usuarios con navegadores m�s nuevos, al tiempo que proporciona comentarios ligeros a aquellos que usan navegadores m�s antiguos y redes m�s lentas.
  • Si combinas el posicionamiento absoluto del cargador con visibility: hidden, evitas el cambio de dise�o.
  • Despu�s de que se cargue el polyfill, la condici�n @supports dejar� de pasar y se revelar� tu contenido.
  • En navegadores con compatibilidad integrada para consultas de contenedores, la condici�n nunca pasar�, por lo que la p�gina se mostrar� en la primera pintura como se espera.

Conclusión

Si te interesa usar consultas de contenedores en navegadores más antiguos, prueba el polyfill. No dudes en informar un problema si tienes algún inconveniente.

Estamos ansiosos por ver y experimentar las cosas increíbles que construirás con ella.

Agradecimientos

Hero image de Dan Cristian Pădureprivilege en Unsplash.