Crie efeitos de imagem responsivos com gradientes CSS e proporção

Publicados: 2022-03-10
Resumo rápido ↬ Um problema clássico em CSS é manter a proporção das imagens em componentes relacionados, como cartões. A propriedade aspect-ratio recém-suportada em combinação com object-fit fornece um remédio para essa dor de cabeça do passado! Vamos aprender a usar essas propriedades, além de criar um efeito de imagem gradiente responsivo para um toque extra.

Para nos prepararmos para nossos futuros efeitos de imagem, vamos configurar um componente de cartão que tem uma imagem grande na parte superior seguida por um título e uma descrição. O problema comum com essa configuração é que nem sempre podemos ter controle perfeito sobre o que é a imagem e, mais importante, sobre nosso layout, quais são suas dimensões . E embora isso possa ser resolvido cortando com antecedência, ainda podemos encontrar problemas devido a contêineres de tamanho responsivo. Uma consequência são as posições desiguais do conteúdo do cartão que realmente se destaca quando você apresenta uma fileira de cartões.

Outra solução anterior além do recorte pode ter sido trocar de uma img inline para uma div em branco que existia apenas para apresentar a imagem via background-image . Eu mesmo implementei essa solução muitas vezes no passado. Uma vantagem que isso tem é usar um truque mais antigo para proporção de aspecto que usa um elemento de altura zero e define um valor padding-bottom . Definir um valor de preenchimento como uma porcentagem resulta em um valor calculado final relativo à largura do elemento. Você também pode ter usado essa ideia para manter uma proporção de 16:9 para incorporações de vídeo, caso em que o valor de preenchimento é encontrado com a fórmula: 9/16 = 0.5625 * 100% = 56.26% . Mas vamos explorar duas propriedades CSS modernas que não envolvem matemática extra, nos dão mais flexibilidade e também permitem manter a semântica fornecida usando um img real em vez de um div vazio.

Primeiro, vamos definir a semântica do HTML, incluindo o uso de uma lista não ordenada como recipiente dos cartões:

 <ul class="card-wrapper"> <li class="card"> <img src="" alt=""> <h3>A Super Wonderful Headline</h3> <p>Lorem ipsum sit dolor amit</p> </li> <!-- additional cards --> </ul>

Em seguida, criaremos um conjunto mínimo de estilos de linha de base para o componente .card . Definiremos alguns estilos visuais básicos para o cartão em si, uma atualização rápida para o título h3 esperado e, em seguida, estilos essenciais para começar a estilizar a imagem do cartão.

 .card { background-color: #fff; border-radius: 0.5rem; box-shadow: 0.05rem 0.1rem 0.3rem -0.03rem rgba(0, 0, 0, 0.45); padding-bottom: 1rem; } .card > :last-child { margin-bottom: 0; } .card h3 { margin-top: 1rem; font-size: 1.25rem; } img { border-radius: 0.5rem 0.5rem 0 0; width: 100%; } img ~ * { margin-left: 1rem; margin-right: 1rem; }

A última regra usa o combinador irmão geral para adicionar uma margem horizontal a qualquer elemento que segue a img , pois queremos que a imagem em si fique alinhada com os lados do cartão.

E nosso progresso até agora nos leva à seguinte aparência de cartão:

Um cartão com os estilos básicos descritos anteriormente aplicados e incluindo uma imagem do Unsplash de uma sobremesa em um prato pequeno ao lado de uma bebida quente em uma caneca
Um cartão com os estilos básicos descritos anteriormente aplicados e incluindo uma imagem do Unsplash de uma sobremesa em um prato pequeno ao lado de uma bebida quente em uma caneca. (Visualização grande)

Por fim, criaremos os estilos .card-wrapper para um layout responsivo rápido usando grade CSS. Isso também removerá os estilos de lista padrão.

 .card-wrapper { list-style: none; padding: 0; margin: 0; display: grid; grid-template-columns: repeat(auto-fit, minmax(30ch, 1fr)); grid-gap: 1.5rem; }

Nota : Se esta técnica de grade não for familiar para você, revise a explicação no meu tutorial sobre soluções modernas para a grade de 12 colunas.

Com isso aplicado e com todos os cartões contendo uma imagem com um caminho de origem válido, nossos estilos .card-wrapper nos dão o seguinte layout:

Três cartões são mostrados em uma linha devido aos estilos de layout do invólucro de cartão aplicados. Cada cartão tem uma imagem única que tem diferentes proporções naturais, com o último cartão tendo uma imagem orientada verticalmente com mais de duas vezes a altura das outras imagens de cartão
Três cartões são mostrados em uma linha devido aos estilos de layout do invólucro de cartão aplicados. Cada cartão tem uma imagem única que tem diferentes proporções naturais, com o último cartão tendo uma imagem orientada verticalmente com mais de duas vezes a altura das outras imagens de cartão. (Visualização grande)

Conforme demonstrado na imagem de visualização, esses estilos de linha de base não são suficientes para conter adequadamente as imagens devido às suas dimensões naturais variadas. Precisamos de um método para restringir essas imagens de maneira uniforme e consistente.

Mais depois do salto! Continue lendo abaixo ↓

Ativar tamanhos de imagem uniformes com object-fit

Como observado anteriormente, você pode ter feito anteriormente uma atualização neste cenário para alterar as imagens a serem adicionadas por meio background-image e usar background-size: cover para lidar bem com o redimensionamento da imagem. Ou você pode ter tentado impor o corte antes do tempo (ainda uma meta válida, pois qualquer redução no tamanho da imagem melhorará o desempenho!).

Agora, temos disponível a propriedade object-fit que permite que uma tag img atue como o contêiner da imagem. E também vem com um valor de cover que resulta em um efeito semelhante à solução de imagem de fundo, mas com o bônus de manter a semântica de uma imagem em linha. Vamos aplicá-lo e ver como ele funciona.

Precisamos emparelhá-lo com uma dimensão de height para obter orientação extra sobre como queremos que o contêiner de imagem se comporte (lembre-se de que já adicionamos width: 100% ). E vamos usar a função max() para selecionar 10rem ou 30vh dependendo de qual for maior em um determinado contexto, o que evita que a altura da imagem diminua muito em viewports menores ou quando o usuário tiver definido um zoom grande.

 img { /* ...existing styles */ object-fit: cover; height: max(10rem, 30vh); }

Dica Bônus de Acessibilidade : Você deve sempre testar seus layouts com zoom de 200% e 400% na área de trabalho. Embora não haja atualmente uma consulta de mídia de zoom , funções como max() podem ajudar a resolver problemas de layout. Outro contexto em que esta técnica é útil é o espaçamento entre elementos.

Com esta atualização, definitivamente melhoramos as coisas, e o resultado visual é como se usássemos a técnica de imagem de fundo mais antiga:

As imagens de três cartões agora parecem ter uma altura uniforme e o conteúdo da imagem está centralizado na imagem como se fosse um contêiner
As imagens de três cartões agora parecem ter uma altura uniforme e o conteúdo da imagem é centralizado na imagem como se fosse um contêiner. (Visualização grande)

Dimensionamento de imagem consistente e responsivo com aspect-ratio

Ao usar object-fit por si só, uma desvantagem é que ainda precisamos definir algumas dicas de dimensão.

Uma propriedade futura (atualmente disponível nos navegadores Chromium) chamada aspect-ratio aumentará nossa capacidade de dimensionar imagens de forma consistente.

Usando esta propriedade, podemos definir uma proporção para redimensionar a imagem em vez de definir dimensões explícitas. Continuaremos a usá-lo em combinação com object-fit para garantir que essas dimensões afetem apenas a imagem como um contêiner, caso contrário, a imagem poderá parecer distorcida.

Aqui está nossa regra de imagem atualizada completa:

 img { border-radius: 0.5rem 0.5rem 0 0; width: 100%; object-fit: cover; aspect-ratio: 4/3; }

Vamos começar com uma proporção de imagem de 43 para nosso contexto de cartão, mas você pode escolher qualquer proporção. Por exemplo, 11 para um quadrado ou 169 para incorporações de vídeo padrão.

Aqui estão os cartões atualizados, embora provavelmente seja difícil notar a diferença visual nesta instância em particular, já que a proporção de aspecto coincide com a aparência que alcançamos definindo a height apenas para o ajuste do object-fit .

As imagens de três cartões têm dimensões idênticas de largura e altura, que são ligeiramente diferentes da solução anterior de ajuste de objeto
As imagens de três cartões têm dimensões idênticas de largura e altura, que são ligeiramente diferentes da solução anterior de ajuste de objeto. (Visualização grande)

Definir uma `proporção de aspecto` resulta na manutenção da proporção à medida que os elementos aumentam ou diminuem, enquanto ao definir apenas `ajuste ao objeto` e `altura` a proporção da imagem estará constantemente em fluxo à medida que as dimensões do contêiner mudam.

Adicionando efeitos responsivos com gradientes e funções CSS

OK, agora que sabemos como configurar imagens de tamanho consistente, vamos nos divertir com elas adicionando um efeito gradiente!

Nosso objetivo com esse efeito é fazer parecer que a imagem está desaparecendo no conteúdo do cartão. Você pode ficar tentado a envolver a imagem em seu próprio contêiner para adicionar o gradiente, mas graças ao trabalho que já fizemos no dimensionamento da imagem, podemos descobrir como fazer isso com segurança no .card principal.

O primeiro passo é definir um gradiente . Vamos usar uma propriedade personalizada CSS para adicionar as cores de gradiente para permitir a troca fácil do efeito de gradiente, começando com azul para rosa. A última cor do gradiente sempre será branca para manter a transição para o plano de fundo do conteúdo do cartão e criar a borda “emplumada”.

 .card { --card-gradient: #5E9AD9, #E271AD; background-image: linear-gradient( var(--card-gradient), white max(9.5rem, 27vh) ); /* ...existing styles */ }

Mas espere - isso é uma função CSS max() ? Em um gradiente? Sim, é possível, e é a mágica que torna esse gradiente eficaz de forma responsiva!

No entanto, se eu adicionasse uma captura de tela, ainda não veríamos o gradiente tendo qualquer efeito na imagem. Para isso, precisamos trazer a propriedade mix-blend-mode e, neste cenário, usaremos o valor de overlay :

 img { /* ...existing styles */ mix-blend-mode: overlay; }

A propriedade mix-blend-mode é semelhante à aplicação dos estilos de mesclagem de camadas disponíveis em softwares de manipulação de fotos como o Photoshop. E o valor de overlay terá o efeito de permitir que os tons médios da imagem se misturem com o gradiente por trás dela, levando ao seguinte resultado:

Cada imagem do cartão tem um efeito de mesclagem de gradiente que começa com um azul claro na parte superior, que se mistura com um rosa avermelhado e termina com um branco antes do restante do conteúdo do texto do cartão
Cada imagem do cartão tem um efeito de mesclagem de gradiente que começa com um azul claro na parte superior, que se mistura com um rosa avermelhado e termina com um branco antes do restante do conteúdo do texto do cartão. (Visualização grande)

Agora, neste ponto, estamos contando apenas com o valor aspect-ratio para redimensionar a imagem. E se redimensionarmos o contêiner e fizermos com que o layout do cartão reflua, a alteração da altura da imagem causará inconsistências no local onde o gradiente desbota para branco.

Portanto, adicionaremos também uma propriedade max-height que também usa a função max() e contém valores ligeiramente maiores que os do gradiente. O comportamento resultante é que o gradiente (quase sempre) se alinha corretamente com a parte inferior da imagem.

 img { /* ...existing styles */ max-height: max(10rem, 30vh); }

É importante notar que adicionar um `max-height` altera o comportamento do `aspect-ratio`. Em vez de sempre usar a proporção exata, ela será usada apenas quando houver espaço suficiente, dada a nova restrição extra da `max-height`.

No entanto, aspect-ratio ainda continuará a garantir que as imagens sejam redimensionadas de forma consistente, como foi o benefício em relação apenas object-fit . Tente comentar a aspect-ratio na demonstração final do CodePen para ver a diferença que está fazendo nos tamanhos de contêiner.

Como nosso objetivo original era permitir dimensões de imagem consistentemente responsivas , ainda atingimos o objetivo. Para seu próprio caso de uso, talvez seja necessário mexer nos valores de proporção e altura para obter o efeito desejado.

Alternativo: mix-blend-mode e adicionando um filtro

Usar overlay como o valor mix-blend-mode foi a melhor escolha para o efeito fade-to-white que estávamos procurando, mas vamos tentar uma opção alternativa para um efeito mais dramático.

Vamos atualizar nossa solução para adicionar uma propriedade customizada CSS para o valor mix-blend-mode e também atualizar os valores de cor para o gradiente:

 .card { --card-gradient: tomato, orange; --card-blend-mode: multiply; } img { /* ...existing styles */ mix-blend-mode: var(--card-blend-mode); }

O valor de multiply tem um efeito de escurecimento nos tons médios, mas mantém o branco e o preto como estão, resultando na seguinte aparência:

Cada imagem do cartão tem um forte tom laranja do novo gradiente que vai de um vermelho-alaranjado a um laranja puro. As áreas brancas ainda são brancas e as áreas pretas ainda são pretas
Cada imagem do cartão tem um forte tom laranja do novo gradiente que vai de um vermelho-alaranjado a um laranja puro. As áreas brancas ainda são brancas e as áreas pretas ainda são pretas. (Visualização grande)

Embora tenhamos perdido o fade e agora tenhamos uma borda dura na parte inferior da imagem, a parte branca do nosso gradiente ainda é importante para garantir que o gradiente termine antes do conteúdo do cartão.

Uma modificação adicional que podemos adicionar é o uso de filter e, em particular, usar a função grayscale() para remover as cores da imagem e, portanto, fazer com que o gradiente seja a única fonte de coloração da imagem.

 img { /* ...existing styles */ filter: grayscale(100); }

O uso do valor de grayscale(100) resulta na remoção completa das cores naturais da imagem e na transformação em preto e branco. Aqui está a atualização para comparação com a captura de tela anterior de seu efeito ao usar nosso gradiente laranja com multiply :

Agora, cada imagem do cartão ainda tem o gradiente laranja, mas todas as outras cores são removidas e substituídas por tons de cinza
Agora, cada imagem do cartão ainda tem o gradiente laranja, mas todas as outras cores são removidas e substituídas por tons de cinza. (Visualização grande)

Use aspect-ratio como um aprimoramento progressivo

Como mencionado anteriormente, atualmente aspect-ratio é suportada apenas na versão mais recente dos navegadores Chromium (Chrome e Edge). No entanto, todos os navegadores suportam object-fit e isso, juntamente com nossas restrições de height , resulta em um resultado menos ideal, mas ainda aceitável, visto aqui para o Safari:

A altura da imagem do cartão é limitada, mas cada cartão tem uma altura percebida ligeiramente diferente
A altura da imagem do cartão é limitada, mas cada cartão tem uma altura percebida ligeiramente diferente. (Visualização grande)

Sem o funcionamento da aspect-ratio , o resultado aqui é que, em última análise, a altura da imagem é limitada, mas as dimensões naturais de cada imagem ainda levam a alguma variação entre as alturas da imagem do cartão. Você pode querer mudar para adicionar um max-height ou fazer uso da função max() novamente para ajudar a tornar um max-height mais responsivo em vários tamanhos de cartão.

Estendendo os efeitos de gradiente

Como definimos as paradas de cor do gradiente como uma propriedade personalizada do CSS, temos acesso pronto para alterá-las em diferentes contextos. Por exemplo, podemos alterar o gradiente para apresentar mais fortemente uma das cores se o cartão estiver em foco ou tiver um de seus filhos em foco.

Primeiro, atualizaremos cada cartão h3 para conter um link, como:

 <h3><a href="">A Super Wonderful Headline</a></h3>

Em seguida, podemos usar um de nossos seletores mais recentes disponíveis — :focus-within — para alterar o gradiente do cartão quando o link estiver em foco. Para uma cobertura extra de possíveis interações, combinaremos isso com :hover . E reutilizaremos nossa ideia max() para atribuir uma única cor para assumir a cobertura da parte da imagem do cartão. A desvantagem desse efeito em particular é que as paradas de gradiente e as mudanças de cor não são animaveis de maneira confiável - mas serão em breve graças ao CSS Houdini.

Para atualizar a cor e adicionar a nova parada de cor, precisamos apenas reatribuir o valor de --card-gradient dentro desta nova regra:

 .card:focus-within, .card:hover { --card-gradient: #24a9d5 max(8.5rem, 20vh); }

Nossos valores max() são menores que o original em uso para white para manter a borda difusa. Se usássemos os mesmos valores, ele encontraria o white e criaria uma separação claramente reta.

Ao criar esta demonstração, originalmente tentei um efeito que usava transform com scale para um efeito de zoom. Mas descobri que devido à aplicação mix-blend-mode , o navegador não redesenhava consistentemente a imagem, o que causava uma tremulação desagradável. Sempre haverá desvantagens em solicitar que o navegador execute efeitos e animações somente CSS e, embora seja muito legal o que podemos fazer, é sempre melhor verificar o impacto de seus efeitos no desempenho .

Divirta-se experimentando!

O CSS moderno nos deu algumas ferramentas incríveis para atualizar nossos kits de ferramentas de web design, sendo a aspect-ratio a mais recente adição. Então vá em frente e experimente com object-fit , aspect-ratio e adicione funções como max() em seus gradientes para alguns efeitos responsivos divertidos! Apenas certifique-se de verificar as coisas em vários navegadores (por enquanto!) e em várias janelas de visualização e tamanhos de contêiner.

Aqui está o CodePen, incluindo os recursos e efeitos que analisamos hoje:

Veja a Caneta [Responsive Image Effects with CSS Gradients and aspect-ratio](https://codepen.io/smashingmag/pen/WNoERXo) de Stephanie Eckles.

Veja os Efeitos de imagem responsivos da caneta com gradientes CSS e proporção de aspecto de Stephanie Eckles.

Procurando mais? Certifique-se de verificar nosso Guia CSS aqui em Smashing →