Consultas de contêiner CSS: casos de uso e estratégias de migração
Publicados: 2022-03-10Quando escrevemos consultas de mídia para um elemento de interface do usuário, sempre descrevemos como esse elemento é estilizado dependendo das dimensões da tela. Essa abordagem funciona bem quando a capacidade de resposta da consulta de mídia do elemento de destino deve depender apenas do tamanho da janela de visualização. Vamos dar uma olhada no seguinte exemplo de layout de página responsivo.
No entanto, o Web Design responsivo (RWD) não se limita a um layout de página — os componentes individuais da interface do usuário geralmente têm consultas de mídia que podem alterar seu estilo dependendo das dimensões da janela de visualização.
Você já deve ter notado um problema com a declaração anterior — o layout do componente de interface do usuário individual geralmente não depende exclusivamente das dimensões da janela de visualização. Enquanto o layout da página é um elemento intimamente ligado às dimensões da janela de visualização e é um dos elementos mais importantes em HTML, os componentes de interface do usuário podem ser usados em diferentes contextos e contêineres. Se você pensar bem, a janela de visualização é apenas um contêiner, e os componentes da interface do usuário podem ser aninhados em outros contêineres com estilos que afetam as dimensões e o layout do componente.
Embora o mesmo componente do cartão do produto seja usado nas seções superior e inferior, os estilos de componentes não dependem apenas das dimensões da janela de visualização, mas também dependem do contexto e das propriedades CSS do contêiner (como a grade no exemplo) onde ele é colocado.
É claro que podemos estruturar nosso CSS para oferecer suporte às variações de estilo para diferentes contextos e contêineres para resolver o problema de layout manualmente. Na pior das hipóteses, essa variação seria adicionada com substituição de estilo, o que levaria a duplicação de código e problemas de especificidade.
.product-card { /* Default card style */ } .product-card--narrow { /* Style variation for narrow viewport and containers */ } @media screen and (min-width: 569px) { .product-card--wide { /* Style variation for wider viewport and containers */ } }
No entanto, isso é mais uma solução alternativa para as limitações das consultas de mídia do que uma solução adequada. Ao escrever consultas de mídia para elementos de interface do usuário, estamos tentando encontrar um valor de viewport “mágico” para um ponto de interrupção quando o elemento de destino tiver dimensões mínimas em que o layout não seja interrompido. Em resumo, estamos vinculando um valor de dimensão de janela de visualização “mágico” ao valor de dimensões de elemento . Esse valor geralmente é diferente da dimensão da janela de visualização e está sujeito a erros quando as dimensões internas do contêiner ou o layout são alterados.
O exemplo a seguir mostra exatamente esse problema — mesmo que um elemento de cartão de produto responsivo tenha sido implementado e pareça bom em um caso de uso padrão, parece quebrado se for movido para um contêiner diferente com propriedades CSS que afetam as dimensões do elemento. Cada caso de uso adicional requer a adição de código CSS adicional, o que pode levar a código duplicado, excesso de código e código difícil de manter.
Esse é um dos problemas que as consultas de contêiner tentam corrigir. As consultas de contêiner estendem a funcionalidade de consultas de mídia existente com consultas que dependem das dimensões do elemento de destino. Existem três principais benefícios de usar essa abordagem:
- Os estilos de consulta de contêiner são aplicados dependendo das dimensões do próprio elemento de destino. Os componentes da interface do usuário poderão se adaptar a qualquer contexto ou contêiner.
- Os desenvolvedores não precisarão procurar um valor de dimensão de janela de visualização “número mágico” que vincule uma consulta de mídia de janela de visualização a uma dimensão de destino do componente de interface do usuário em um contêiner ou contexto específico.
- Não há necessidade de adicionar classes CSS ou consultas de mídia adicionais para diferentes contextos e casos de uso.
“O site responsivo ideal é um sistema de componentes flexíveis e modulares que podem ser reaproveitados para servir em vários contextos.”
— “Consultas sobre contêineres: mais uma vez até a violação”, Mat Marquis
Antes de nos aprofundarmos nas consultas de contêiner, precisamos verificar o suporte do navegador e ver como podemos habilitar o recurso experimental em nosso navegador.
Suporte ao navegador
As consultas de contêiner são um recurso experimental, disponível atualmente na versão Chrome Canary no momento da redação deste artigo. Se você quiser acompanhar e executar os exemplos do CodePen neste artigo, precisará habilitar as consultas de contêiner na URL de configurações a seguir.
chrome://flags/#enable-container-queries
Caso você esteja usando um navegador que não suporta consultas de contêiner, uma imagem mostrando o exemplo de trabalho pretendido será fornecida junto com a demonstração do CodePen.
Trabalhando com consultas de contêiner
As consultas de contêiner não são tão diretas quanto as consultas de mídia comuns. Teremos que adicionar uma linha extra de código CSS ao nosso elemento de interface do usuário para que as consultas de contêiner funcionem, mas há uma razão para isso e abordaremos isso a seguir.
Propriedade de contenção
CSS contain
propriedade foi adicionado à maioria dos navegadores modernos e tem um suporte decente de 75% do navegador no momento da redação deste artigo. A propriedade contain
é usada principalmente para otimização de desempenho, indicando ao navegador quais partes (subárvores) da página podem ser tratadas como independentes e não afetarão as alterações em outros elementos em uma árvore. Dessa forma, se ocorrer uma alteração em um único elemento, o navegador renderizará novamente apenas essa parte (subárvore) em vez de toda a página. Com os valores da propriedade container, podemos especificar quais tipos de contain
queremos usar — layout
, size
ou paint
.
Existem muitos artigos excelentes sobre a propriedade contain
que descrevem as opções disponíveis e os casos de uso com muito mais detalhes, então vou me concentrar apenas nas propriedades relacionadas a consultas de contêiner.
O que a propriedade contentment CSS usada para otimização tem a ver com consultas de contêiner? Para que as consultas de contêiner funcionem, o navegador precisa saber se ocorre uma alteração no layout filho do elemento que ele deve renderizar novamente apenas esse componente. O navegador saberá aplicar o código na consulta do contêiner ao componente correspondente quando o componente for renderizado ou a dimensão do componente for alterada.
Usaremos os valores layout
style
para a contain
, mas também precisaremos de um valor adicional que sinalize o navegador sobre o eixo em que a alteração ocorrerá.
-
inline-size
Contenção no eixo inline. Espera-se que esse valor tenha significativamente mais casos de uso, portanto, ele está sendo implementado primeiro. -
block-size
Contenção no eixo do bloco. Ainda está em desenvolvimento e não está disponível no momento.
Uma pequena desvantagem da propriedade contain
é que nosso elemento layout precisa ser filho de um elemento contain
, o que significa que estamos adicionando um nível de aninhamento adicional.
<section> <article class="card"> <div class="card__wrapper"> <!-- Card content --> </div> </article> </section>
.card { contain: layout inline-size style; } .card__wrapper { display: grid; grid-gap: 1.5em; grid-template-rows: auto auto; /* ... */ }
Observe como não estamos adicionando esse valor a uma section
semelhante a um pai mais distante e mantendo o contêiner o mais próximo possível do elemento afetado.
“Desempenho é a arte de evitar o trabalho e tornar qualquer trabalho o mais eficiente possível. Em muitos casos, trata-se de trabalhar com o navegador, não contra ele.”
— “Desempenho de renderização”, Paul Lewis
É por isso que devemos sinalizar corretamente ao navegador sobre a mudança. Envolver um elemento pai distante com uma propriedade contain
pode ser contraproducente e afetar negativamente o desempenho da página. Nos piores cenários de uso contain
da propriedade container, o layout pode até quebrar e o navegador não irá renderizá-lo corretamente.
Consulta de contêiner
Depois que a propriedade contain
for adicionada ao wrapper do elemento do cartão, podemos escrever uma consulta de contêiner. Adicionamos uma propriedade container a um elemento com classe card
, então agora podemos contain
qualquer um de seus elementos filho em uma consulta de contêiner.
Assim como nas consultas de mídia comuns, precisamos definir uma consulta usando as propriedades min-width
ou max-width
e aninhar todos os seletores dentro do bloco. No entanto, usaremos a palavra-chave @container
em vez de @media
para definir uma consulta de contêiner.
@container (min-width: 568px) { .card__wrapper { align-items: center; grid-gap: 1.5em; grid-template-rows: auto; grid-template-columns: 150px auto; } .card__image { min-width: auto; height: auto; } }
Ambos os elementos card__wrapper
e card__image
são filhos do elemento card
que tem a propriedade contain
definida. Quando substituímos as consultas de mídia regulares por consultas de contêiner, removemos as classes CSS adicionais para contêineres estreitos e executamos o exemplo do CodePen em um navegador que oferece suporte a consultas de contêiner, obtemos o seguinte resultado.
Observe que as consultas de contêiner atualmente não aparecem nas ferramentas de desenvolvedor do Chrome , o que dificulta um pouco a depuração de consultas de contêiner. Espera-se que o suporte de depuração adequado seja adicionado ao navegador no futuro.
Você pode ver como as consultas de contêiner nos permitem criar componentes de interface do usuário mais robustos e reutilizáveis que podem se adaptar a praticamente qualquer contêiner e layout. No entanto, o suporte adequado do navegador para consultas de contêiner ainda está longe no recurso. Vamos tentar e ver se podemos implementar consultas de contêiner usando aprimoramento progressivo.
Aprimoramento progressivo e polyfills
Vamos ver se podemos adicionar um substituto para a variação de classe CSS e consultas de mídia. Podemos usar consultas de recursos CSS com a regra @supports
para detectar recursos de navegador disponíveis. No entanto, não podemos verificar outras consultas, portanto, precisamos adicionar uma verificação para um valor contain: layout inline-size style
. Teremos que assumir que os navegadores que suportam a propriedade inline-size
também suportam consultas de contêiner.
/* Check if the inline-size value is supported */ @supports (contain: inline-size) { .card { contain: layout inline-size style; } } /* If the inline-size value is not supported, use media query fallback */ @supports not (contain: inline-size) { @media (min-width: 568px) { /* ... */ } } /* Browser ignores @container if it's not supported */ @container (min-width: 568px) { /* Container query styles */ }
No entanto, essa abordagem pode levar a estilos duplicados, pois os mesmos estilos estão sendo aplicados pela consulta de contêiner e pela consulta de mídia. Se você decidir implementar consultas de contêiner com aprimoramento progressivo, convém usar um pré-processador CSS como SASS ou um pós-processador como PostCSS para evitar a duplicação de blocos de código e usar mixins CSS ou outra abordagem.
Como essa especificação de consulta de contêiner ainda está em fase experimental, é importante ter em mente que a especificação ou implementação está sujeita a alterações em versões futuras.
Como alternativa, você pode usar polyfills para fornecer um fallback confiável. Há dois polyfills JavaScript que gostaria de destacar, que atualmente parecem ser mantidos ativamente e fornecem os recursos de consulta de contêiner necessários:
-
cqfill
por Jonathan Neal
Polyfill JavaScript para CSS e PostCSS -
react-container-query
por Chris Garcia
Gancho e componente personalizados para React
Migrando de consultas de mídia para consultas de contêiner
Se você decidir implementar consultas de contêiner em um projeto existente que usa consultas de mídia, será necessário refatorar o código HTML e CSS. Descobri que essa é a maneira mais rápida e direta de adicionar consultas de contêiner, ao mesmo tempo em que fornece um fallback confiável para consultas de mídia. Vamos dar uma olhada no exemplo de cartão anterior.
<section> <div class="card__wrapper card__wrapper--wide"> <!-- Wide card content --> </div> </section> /* ... */ <aside> <div class="card__wrapper"> <!-- Narrow card content --> </div> </aside>
.card__wrapper { display: grid; grid-gap: 1.5em; grid-template-rows: auto auto; /* ... */ } .card__image { /* ... */ } @media screen and (min-width: 568px) { .card__wrapper--wide { align-items: center; grid-gap: 1.5em; grid-template-rows: auto; grid-template-columns: 150px auto; } .card__image { /* ... */ } }
Primeiro, envolva o elemento HTML raiz que tem uma consulta de mídia aplicada a ele com um elemento que tenha a propriedade contain
.
<section> <article class="card"> <div class="card__wrapper"> <!-- Card content --> </div> </article> </section>
@supports (contain: inline-size) { .card { contain: layout inline-size style; } }
Em seguida, envolva uma consulta de mídia em uma consulta de recurso e adicione uma consulta de contêiner.
@supports not (contain: inline-size) { @media (min-width: 568px) { .card__wrapper--wide { /* ... */ } .card__image { /* ... */ } } } @container (min-width: 568px) { .card__wrapper { /* Same code as .card__wrapper--wide in media query */ } .card__image { /* Same code as .card__image in media query */ } }
Embora esse método resulte em algum excesso de código e código duplicado, usando SASS ou PostCSS você pode evitar a duplicação do código de desenvolvimento, para que o código-fonte CSS permaneça sustentável.
Depois que as consultas de contêiner receberem suporte adequado ao navegador, convém considerar a remoção de blocos de código @supports not (contain: inline-size)
e continuar oferecendo suporte exclusivamente a consultas de contêiner.
Stephanie Eckles publicou recentemente um ótimo artigo sobre consultas de contêiner cobrindo várias estratégias de migração. Recomendo dar uma olhada para mais informações sobre o assunto.
Cenários de casos de uso
Como vimos nos exemplos anteriores, as consultas de contêiner são melhor usadas para componentes altamente reutilizáveis com um layout que depende do espaço de contêiner disponível e que pode ser usado em vários contextos e adicionados a diferentes contêineres na página.
Outros exemplos incluem (os exemplos exigem um navegador que suporte consultas de contêiner):
- Componentes modulares como cartões, elementos de formulário, banners, etc.
- Layouts adaptáveis
- Paginação com diferentes funcionalidades para mobile e desktop
- Experiências divertidas com redimensionamento CSS
Conclusão
Depois que a especificação for implementada e amplamente suportada nos navegadores, as consultas de contêiner podem se tornar um recurso revolucionário. Ele permitirá que os desenvolvedores escrevam consultas no nível do componente, aproximando as consultas dos componentes relacionados, em vez de usar as consultas de mídia de viewport distantes e pouco relacionadas. Isso resultará em componentes mais robustos, reutilizáveis e de fácil manutenção que poderão se adaptar a vários casos de uso, layouts e contêineres.
Do jeito que está, as consultas de contêiner ainda estão em uma fase inicial e experimental e a implementação está sujeita a mudanças. Se você quiser começar a usar consultas de contêiner em seus projetos hoje, precisará adicioná-las usando aprimoramento progressivo com detecção de recursos ou usar um polyfill JavaScript. Ambos os casos resultarão em alguma sobrecarga no código, portanto, se você decidir usar consultas de contêiner nesta fase inicial, planeje a refatoração do código assim que o recurso se tornar amplamente suportado.
Referências
- “Consultas de contêiner: um guia de início rápido” por David A. Herron
- “Diga olá às consultas de contêiner CSS”, Ahmad Shadeed
- “Contenção CSS no Chrome 52”, Paul Lewis
- “Ajudando os navegadores a otimizar com a propriedade CSS Contein”, Rachel Andrew