Criar uma grade de imagens é fácil, graças ao CSS Grid. Mas fazer a grade fazer coisas extravagantes depois de as imagens foram colocadas pode ser difícil de retirar.
Digamos que você queira adicionar algum efeito de foco sofisticado às imagens onde elas crescem e ampliar além das linhas e colunas onde elas ficam? Nós podemos fazer isso!
Legal certo? Se você verificar o código, não encontrará nenhum JavaScript, seletores complexos ou mesmo números mágicos. E este é apenas um exemplo entre muitos que iremos explorar!
Construindo a grade
O código HTML para criar a grade é tão simples quanto uma lista de imagens dentro de um container. Não precisamos mais do que isso.
<div class="gallery">
<img>
<img>
<img>
<!-- etc. -->
</div>
Para o CSS, primeiro começamos definindo a grade usando o seguinte:
.gallery {
--s: 150px; /* controls the size */
--g: 10px; /* controls the gap */
display: grid;
gap: var(--g);
width: calc(3*var(--s) + 2*var(--g)); /* 3 times the size plus 2 times the gap */
aspect-ratio: 1;
grid-template-columns: repeat(3, auto);
}
Resumindo, temos duas variáveis, uma que controla o tamanho das imagens e outra que define o tamanho do gap entre as imagens. aspect-ratio
ajuda a manter as coisas em proporção.
Você pode estar se perguntando por que estamos definindo apenas três colunas, mas nenhuma linha. Não, eu não esqueci as linhas - nós simplesmente não precisamos defini-las explicitamente. CSS Grid é capaz de colocar itens automaticamente em linhas e colunas implícitas, o que significa que obtemos quantas linhas forem necessárias para qualquer número de imagens que lançarmos nele. Podemos definir explicitamente as linhas, mas precisamos adicionar grid-auto-flow: column
para garantir que o navegador crie as colunas necessárias para nós.
Aqui está um exemplo para ilustrar ambos os casos. A diferença é que um flui em um row
direção um o outro em um column
direção.
Dê uma olhada na este outro artigo que escrevi para saber mais sobre as grades implícitas e o algoritmo de posicionamento automático.
Agora que temos nossa grade, é hora de estilizar as imagens:
.gallery > img {
width: 0;
height: 0;
min-height: 100%;
min-width: 100%;
object-fit: cover;
}
O efeito hover que estamos fazendo depende desse CSS. Provavelmente parece estranho para você que estamos fazendo imagens que não têm largura ou altura, mas têm largura e altura mínimas de 100%. Mas você verá que é um truque bem legal para o que estamos tentando alcançar.
O que estou fazendo aqui é dizer ao navegador que as imagens precisam ter 0
largura e altura, mas também precisam ter uma altura mínima igual a 100%
… mas 100%
sobre o que? Ao usar porcentagens, o valor é em relação a outra coisa. Neste caso, nossa imagem é colocada dentro de um célula de grade e precisamos saber esse tamanho para saber o que é 100%
é relativo a.
O navegador irá primeiro ignorar min-height: 100%
para calcular o tamanho das células da grade, mas usará o height: 0
em seu cálculo. Isso significa que nossas imagens não contribuirão para o tamanho das células da grade... porque tecnicamente elas não têm tamanho físico. Isso resultará em três colunas e linhas iguais baseadas no tamanho da grade (que definimos no .gallery
largura e aspect-ratio
). A altura de cada célula da grade nada mais é do que a variável --s
definimos (o mesmo para a largura).
Agora que temos as dimensões das células da nossa grade, o navegador irá usá-la com min-height: 100%
(E min-width: 100%
) que forçará as imagens a preencherem completamente o espaço de cada célula da grade. A coisa toda pode parecer um pouco confusa, mas a ideia principal é garantir que a grade defina o tamanho das imagens e não o contrário. Não quero que a imagem defina o tamanho da grade e você entenderá o porquê após adicionar o efeito hover.
Criando o efeito de foco
O que precisamos fazer é aumentar a escala das imagens ao passar o mouse. Podemos fazer isso ajustando a imagem width
e height
on :hover
:
.gallery {
--f: 1.5; /* controls the scale factor */
}
.gallery img:hover{
width: calc(var(--s) * var(--f));
height: calc(var(--s) * var(--f));
}
Eu adicionei uma nova variável personalizada, --f
, à mistura como um fator de escala para controlar o tamanho ao passar o mouse. Observe como estou multiplicando a variável size, --s
, por ele para calcular o novo tamanho da imagem.
Mas você disse que o tamanho da imagem precisa ser 0. O que está acontecendo? Eu estou perdido…
O que eu disse ainda é verdade, mas estou abrindo uma exceção para a imagem pairada. Estou dizendo ao navegador que apenas uma imagem terá um tamanho diferente de zero - portanto, contribuirá para a dimensão da grade - enquanto todas as outras permanecerão iguais a 0
.
O lado esquerdo mostra a grade em seu estado natural sem nenhuma imagem em foco, que é o que o lado direito está mostrando. Todas as células da grade do lado esquerdo são iguais em tamanho, pois todas as imagens não têm dimensões físicas.
No lado direito, a segunda imagem na primeira linha passa o mouse, o que lhe dá dimensões que afetam o tamanho da célula da grade. O navegador tornará essa célula de grade específica maior ao passar o mouse, o que contribui para o tamanho geral. E como o tamanho de toda a grade está definido (porque definimos um valor fixo width
na .gallery
), as outras células da grade responderão logicamente tornando-se menores para manter a .gallery
tamanho total do 's no tato.
Esse é o nosso efeito de zoom em ação! Ao aumentar o tamanho de apenas uma imagem, afetamos toda a configuração da grade, e dissemos antes que a grade define o tamanho das imagens para que cada imagem se estique dentro de sua célula da grade para preencher todo o espaço.
Para isso, adicionamos um toque de transition
E use object-fit
para evitar a distorção da imagem e a ilusão é perfeita!
Eu sei que a lógica por trás do truque não é fácil de entender. Não se preocupe se você não entender completamente. O mais importante é entender a estrutura do código usado e como modificá-lo para obter mais variações. É o que faremos a seguir!
Adicionando mais imagens
Criamos uma grade 3×3 para explicar o truque principal, mas você provavelmente adivinhou que não precisaríamos parar por aí. Podemos tornar variáveis o número de colunas e linhas e adicionar quantas imagens quisermos.
.gallery {
--n: 3; /* number of rows*/
--m: 4; /* number of columns */
--s: 150px; /* control the size */
--g: 10px; /* control the gap */
--f: 1.5; /* control the scale factor */
display: grid;
gap: var(--g);
width: calc(var(--m)*var(--s) + (var(--m) - 1)*var(--g));
height: calc(var(--n)*var(--s) + (var(--n) - 1)*var(--g));
grid-template-columns: repeat(var(--m),auto);
}
Temos duas novas variáveis para o número de linhas e colunas. Em seguida, simplesmente definimos a largura e a altura de nossa grade usando-os. O mesmo para grid-template-columns
que usa o --m
variável. E, assim como antes, não precisamos definir explicitamente as linhas, pois o recurso de posicionamento automático do CSS Grid fará o trabalho para nós, não importa quantos elementos de imagem estamos usando.
Por que não valores diferentes para a largura e a altura? Nós podemos fazer isso:
.gallery {
--n: 3; /* number of rows*/
--m: 4; /* number of columns */
--h: 120px; /* control the height */
--w: 150px; /* control the width */
--g: 10px; /* control the gap */
--f: 1.5; /* control the scale factor */
display: grid;
gap: var(--g);
width: calc(var(--m)*var(--w) + (var(--m) - 1)*var(--g));
height: calc(var(--n)*var(--h) + (var(--n) - 1)*var(--g));
grid-template-columns: repeat(var(--m),auto);
}
.gallery img:hover{
width: calc(var(--w)*var(--f));
height: calc(var(--h)*var(--f));
}
Nós substituímos --s
com duas variáveis, uma para a largura, --w
, e outro para a altura, --h
. Então ajustamos todo o resto de acordo.
Então, começamos com uma grade com tamanho e número de elementos fixos, mas depois criamos um novo conjunto de variáveis para obter qualquer configuração que desejarmos. Tudo o que temos a fazer é adicionar quantas imagens quisermos e ajustar as variáveis CSS de acordo. As combinações são ilimitadas!
Uma galeria de imagens em tela cheia
Que tal uma versão em tela cheia? Sim, isso também é possível. Tudo o que precisamos é saber quais valores precisamos atribuir às nossas variáveis. Se nós quisermos N
linhas de imagens e queremos que nossa grade seja em tela cheia, primeiro precisamos resolver para uma altura de 100vh
:
var(--n) * var(--h) + (var(--n) - 1) * var(--g) = 100vh
Mesma lógica para a largura, mas usando vw
em vez de vh
:
var(--m) * var(--w) + (var(--m) - 1) * var(--g) = 100vw
Fazemos as contas para obter:
--w: (100vw - (var(--m) - 1) * var(--g)) / var(--m)
--h: (100vh - (var(--n) - 1) * var(--g)) / var(--n)
Feito!
É exatamente o mesmo HTML, mas com algumas variáveis atualizadas que alteram o tamanho e o comportamento da grade.
Observe que omiti a fórmula que definimos anteriormente no .gallery
's width
e height
e os substituiu por 100vw
e 100vh
, respectivamente. A fórmula nos dará o mesmo resultado, mas como sabemos o valor que queremos, podemos descartar toda essa complexidade adicional.
Também podemos simplificar a --h
e --w
removendo a lacuna da equação em favor disso:
--h: calc(100vh / var(--n)); /* Viewport height divided by number of rows */
--w: calc(100vw / var(--m)); /* Viewport width divided by number of columns */
Isso fará com que a imagem pairada cresça um pouco mais do que no exemplo anterior, mas não é grande coisa, pois podemos controlar a escala com o --f
variável que estamos usando como multiplicador.
E como as variáveis são usadas em um só lugar, ainda podemos simplificar o código removendo-as completamente:
É importante observar que essa otimização se aplica apenas ao exemplo de tela inteira e não aos exemplos que abordamos. Este exemplo é um caso particular em que podemos tornar o código mais leve removendo parte do trabalho de cálculo complexo que precisávamos nos outros exemplos.
Na verdade, temos tudo o que precisamos para criar o padrão popular de painéis expansíveis:
Vamos cavar ainda mais fundo
Você notou que nosso fator de escala pode ser menor que 1
? Podemos definir o tamanho da imagem pairada para ser menor do que --h
or --w
mas a imagem fica maior ao passar o mouse.
O tamanho inicial da célula da grade é igual a --w
e --h
, então por que valores menores tornam a célula da grade maior? A célula não deveria ficar menor, ou pelo menos manter seu tamanho inicial? E qual é o tamanho final da célula da grade?
Precisamos nos aprofundar em como o algoritmo CSS Grid calcula o tamanho das células da grade. E isso envolve entender o padrão do CSS Grid esticar alinhamento.
Aqui está um exemplo para entender a lógica.
No lado esquerdo da demo, defini duas colunas com auto
largura. Obtemos o resultado intuitivo: duas colunas iguais (e duas células de grade iguais). Mas a grade que montei no lado direito da demo, onde estou atualizando o alinhamento usando place-content: start
, parece não ter nada.
O DevTools ajuda a nos mostrar o que realmente está acontecendo em ambos os casos:
Na segunda grade, temos duas colunas, mas suas larguras são iguais a zero, então obtemos duas células de grade que são recolhidas no canto superior esquerdo do contêiner da grade. Isto é não um bug, mas o resultado lógico do alinhamento da grade. Quando dimensionamos uma coluna (ou linha) com auto
, significa que seu conteúdo determina seu tamanho — mas temos um vazio div
sem conteúdo para abrir espaço.
Mas desde stretch
é o alinhamento padrão e temos espaço suficiente dentro de nossa grade, o navegador irá esticar ambas as células da grade igualmente para cobrir toda essa área. É assim que a grade à esquerda termina com duas colunas iguais.
De a especificação:
Observe que alguns valores de
justify-content
ealign-content
pode fazer com que as faixas fiquem espaçadas (space-around
,space-between
,space-evenly
) ou para ser redimensionado (stretch
).
Observe o “a ser redimensionado” que é a chave aqui. No último exemplo, usei place-content
que é a abreviatura para justify-content
e align-content
E isso está enterrado em algum lugar o algoritmo de dimensionamento de grade especificações:
Esta etapa expande as faixas que têm um auto função de dimensionamento máximo da faixa dividindo qualquer positivo restante, definido espaço livre igualmente entre eles. Se o espaço livre for indeterminado, Mas o recipiente de grade tem uma definição min-largura/altura, use esse tamanho para calcular o espaço livre para esta etapa.
“Igualmente” explica por que acabamos com células de grade iguais, mas se aplica ao “espaço livre”, que é muito importante.
Vamos pegar o exemplo anterior e adicionar conteúdo a um dos div
s:
Adicionamos um quadrado 50px
imagem. Aqui está uma ilustração de como cada grade em nosso exemplo responde a essa imagem:
No primeiro caso, podemos ver que a primeira célula (em vermelho) é maior que a segunda (em azul). No segundo caso, o tamanho da primeira célula muda para se ajustar ao tamanho físico da imagem enquanto a segunda célula permanece sem dimensões. O espaço livre é dividido igualmente, mas a primeira célula tem mais conteúdo dentro, o que a torna maior.
Esta é a matemática para descobrir nosso espaço livre:
(grid width) - (gap) - (image width) = (free space)
200px - 5px - 50px = 145px
Dividido por dois - o número de colunas - obtemos uma largura de 72.5px
para cada coluna. Mas adicionamos o tamanho da imagem, 50px
, para a primeira coluna que nos deixa com uma coluna em 122.5px
e o segundo igual a 72.5px
.
A mesma lógica se aplica à nossa grade de imagens. Todas as imagens têm um tamanho igual a 0
(sem conteúdo) enquanto a imagem em foco contribui para o tamanho - mesmo que seja apenas 1px
– tornando sua célula de grade maior que as outras. Por esta razão, o fator de escala pode ser qualquer valor maior que 0
mesmo decimais entre 0
e 1
.
Para obter a largura final das células da grade, fazemos o mesmo cálculo para obter o seguinte:
(container width) - (sum of all gaps) - (hovered image width) = (free space)
A largura do contêiner é definida por:
var(--m)*var(--w) + (var(--m) - 1)*var(--g)
… e todas as lacunas são iguais a:
(var(--m) - 1)*var(--g)
… e para a imagem pairada temos:
var(--w)*var(--f)
Podemos calcular tudo isso com nossas variáveis:
var(--m)*var(--w) - var(--w)*var(--f) = var(--w)*(var(--m) - var(--f))
O número de colunas é definido por --m
, então dividimos esse espaço livre igualmente para obter:
var(--w)*(var(--m) - var(--f))/var(--m)
…o que nos dá o tamanho das imagens não suspensas. Para imagens pairadas, temos isso:
var(--w)*(var(--m) - var(--f))/var(--m) + var(--w)*var(--f)
var(--w)*((var(--m) - var(--f))/var(--m) + var(--f))
Se quisermos controlar o tamanho final da imagem em foco, consideramos a fórmula acima para obter o tamanho exato que desejamos. Se, por exemplo, queremos que a imagem seja duas vezes maior:
(var(--m) - var(--f))/var(--m) + var(--f) = 2
Então, o valor do nosso multiplicador de escala, --f
, deve ser igual a:
var(--m)/(var(--m) - 1)
Para três colunas teremos 3/2 = 1.5
e esse é o fator de escala que usei na primeira demonstração deste artigo porque queria tornar a imagem duas vezes maior ao passar o mouse!
A mesma lógica se aplica ao cálculo da altura e, caso queiramos controlar os dois de forma independente, precisaremos considerar dois fatores de escala para garantir que tenhamos uma largura e uma altura específicas ao passar o mouse.
.gallery {
/* same as before */
--fw: 1.5; /* controls the scale factor for the width */
--fh: 1.2; /* controls the scale factor for the height */
/* same as before */
}
.gallery img:hover{
width: calc(var(--w)*var(--fw));
height: calc(var(--h)*var(--fh));
}
Agora, você conhece todos os segredos para criar qualquer tipo de grade de imagem com um efeito de foco legal e, ao mesmo tempo, controlar o tamanho desejado usando a matemática que acabamos de abordar.
Resumindo
Na nossa último artigo, criamos uma grade de aparência complexa com algumas linhas de CSS que usam a grade implícita e os recursos de posicionamento automático do CSS Grid. Neste artigo, contamos com alguns truques de dimensionamento de CSS Grid para criar uma grade sofisticada de imagens que ampliam o foco e fazem com que a grade se ajuste de acordo. Tudo isso com um código simplificado e fácil de ajustar usando variáveis CSS!
No próximo artigo, vamos brincar com formas! Combinaremos a grade CSS com a máscara e o caminho do clipe para obter uma grade de imagens sofisticada.