CSS Infinite Slider folheando imagens Polaroid PlatoBlockchain Data Intelligence. Pesquisa vertical. Ai.

Controle deslizante infinito CSS passando por imagens Polaroid

No último artigo, fizemos um pequeno slider bem legal (ou “carrossel” se preferir) que gira em uma direção circular. Desta vez, vamos fazer um que folheie uma pilha de imagens Polaroid.

Legal certo? Não olhe para o código ainda porque há muito a desvendar. Junte-se a mim, sim?

Série de controles deslizantes CSS

A configuração básica

A maior parte do HTML e CSS para este controle deslizante é semelhante ao circular que fizemos da última vez. Na verdade, estamos usando exatamente a mesma marcação:

E este é o CSS básico que define nosso pai .gallery container como uma grade onde todas as imagens são empilhadas umas sobre as outras:

.gallery  {
  display: grid;
  width: 220px; /* controls the size */
}
.gallery > img {
  grid-area: 1 / 1;
  width: 100%;
  aspect-ratio: 1;
  object-fit: cover;
  border: 10px solid #f2f2f2;
  box-shadow: 0 0 4px #0007;
}

Nada complexo até agora. Mesmo para o estilo tipo Polaroid para as imagens, tudo o que estou usando é algum border e box-shadow. Você pode fazer melhor, então fique à vontade para brincar com esses estilos decorativos! Vamos colocar a maior parte do nosso foco na animação, que é a parte mais complicada.

Qual é o truque?

A lógica desse controle deslizante depende da ordem de empilhamento das imagens — então, sim, vamos brincar com z-index. Todas as imagens começam com o mesmo z-index valor (2) que fará logicamente a última imagem no topo da pilha.

Pegamos a última imagem e a deslizamos para a direita até revelar a próxima imagem da pilha. Então diminuímos a imagem z-index valor, então nós o deslizamos de volta para o baralho. E já que é z-index valor for menor que o restante das imagens, ela se tornará a última imagem da pilha.

Aqui está uma demonstração simplificada que mostra o truque. Passe o mouse sobre a imagem para ativar a animação:

Agora, imagine o mesmo truque aplicado a todas as imagens. Aqui está o padrão se estivermos usando o :nth-child() pseudo-seletor para diferenciar as imagens:

  • Deslizamos a última imagem (N). A próxima imagem é visível (N - 1).
  • Deslizamos a próxima imagem (N - 1). A próxima imagem é visível (N - 2)
  • Deslizamos a próxima imagem (N - 2). A próxima imagem é visível (N - 3)
  • (Continuamos o mesmo processo até chegar à primeira imagem)
  • Deslizamos a primeira imagem (1). A última imagem (N) está visível novamente.

Esse é o nosso controle deslizante infinito!

Dissecando a animação

Se você se lembra do artigo anterior, defini apenas uma animação e joguei com atrasos para controlar cada imagem. Estaremos fazendo a mesma coisa aqui. Vamos primeiro tentar visualizar a linha do tempo da nossa animação. Começaremos com três imagens e depois generalizaremos para qualquer número (N) de imagens.

Controle deslizante infinito CSS passando por imagens Polaroid

Nossa animação é dividida em três partes: “deslize para a direita”, “deslize para a esquerda” e “não se mova”. Podemos identificar facilmente o atraso entre cada imagem. Se considerarmos que a primeira imagem começa em 0s, e a duração é igual a 6s, então o segundo começará em -2s e o terceiro em -4s.

.gallery > img:nth-child(2) { animation-delay: -2s; } /* -1 * 6s / 3 */
.gallery > img:nth-child(3) { animation-delay: -4s; } /* -2 * 6s / 3 */

Também podemos ver que a parte “não se mova” ocupa dois terços de toda a animação (2*100%/3) enquanto as partes “desliza para a direita” e “desliza para a esquerda” ocupam um terço juntas — portanto, cada uma é igual a 100%/6 da animação total.

Podemos escrever nossos quadros-chave de animação assim:

@keyframes slide {
  0%     { transform: translateX(0%); }
  16.67% { transform: translateX(120%); }
  33.34% { transform: translateX(0%); }
  100%   { transform: translateX(0%); } 
}

Êxtase 120% é um valor arbitrário. Eu precisava de algo maior do que 100%. As imagens precisam deslizar para a direita do resto das imagens. Para fazer isso, ele precisa se mover pelo menos 100% de seu tamanho. por isso eu fui 120% - para ganhar algum espaço extra.

Agora precisamos considerar o z-index. Não se esqueça que precisamos atualizar a imagem z-index valor depois de ele desliza para a direita da pilha, e antes nós o deslizamos de volta para o fundo da pilha.

@keyframes slide {
  0%     { transform: translateX(0%);   z-index: 2; }
  16.66% { transform: translateX(120%); z-index: 2; }
  16.67% { transform: translateX(120%); z-index: 1; } /* we update the z-order here */
  33.34% { transform: translateX(0%);   z-index: 1; }
  100%   { transform: translateX(0% );  z-index: 1; }  
}

Em vez de definir um estado no 16.67% (100%/6) na linha do tempo, estamos definindo dois estados em pontos quase idênticos (16.66% e 16.67%) onde o z-index o valor diminui antes de deslizarmos a imagem de volta para o deck.

Aqui está o que acontece quando juntamos tudo isso:

Hmmm, a parte deslizante parece funcionar bem, mas a ordem de empilhamento está toda embaralhada! A animação começa bem, já que a imagem superior está se movendo para trás… mas as imagens subsequentes não seguem o exemplo. Se você notar, a segunda imagem na sequência retorna ao topo da pilha antes que a próxima imagem pisque em cima dela.

Precisamos acompanhar de perto o z-index mudanças. Inicialmente, todas as imagens são z-index: 2. Isso significa que a ordem de empilhamento deve ir…

Our eyes 👀 --> 3rd (2) | 2nd (2) | 1st (2)

Deslizamos a terceira imagem e atualizamos sua z-index para obter este pedido:

Our eyes 👀 --> 2nd (2) | 1st (2) | 3rd (1)

Fazemos o mesmo com o segundo:

Our eyes 👀 --> 1st (2) | 3rd (1) | 2nd (1)

…e o primeiro:

Our eyes 👀 --> 3rd (1) | 2nd (1) | 1st (1)

Fazemos isso e tudo parece estar bem. Mas, na realidade, não é! Quando a primeira imagem for movida para trás, a terceira imagem iniciará outra iteração, ou seja, retornará para z-index: 2:

Our eyes 👀 --> 3rd (2) | 2nd (1) | 1st (1)

Então, na realidade, nunca tivemos todas as imagens z-index: 2 de forma alguma! Quando as imagens não estão se movendo (ou seja, a parte “não se mova” da animação), o z-index is 1. Se deslizarmos a terceira imagem e atualizarmos sua z-index valor de 2 para 1, ele permanecerá no topo! Quando todas as imagens tiverem o mesmo z-index, a última na ordem de origem — nossa terceira imagem neste caso — está no topo da pilha. Deslizar a terceira imagem resulta no seguinte:

Our eyes 👀 --> 3rd (1) | 2nd (1) | 1st (1)

A terceira imagem ainda está no topo e, logo após, movemos a segunda imagem para o topo quando sua animação recomeça em z-index: 2:

Our eyes 👀 --> 2nd (2) | 3rd (1) | 1st (1)

Depois de deslizar, obtemos:

Our eyes 👀 --> 3rd (1) | 2nd (1) | 1st (1)

Em seguida, a primeira imagem saltará para o topo:

Our eyes 👀 --> 1st(2) | 3rd (1) | 2nd (1)

OK, estou perdido. Toda a lógica está errada então?

Eu sei, é confuso. Mas nossa lógica não está completamente errada. Só precisamos retificar um pouco a animação para que tudo funcione como queremos. O truque é redefinir corretamente o z-index.

Vamos pegar a situação em que a terceira imagem está no topo:

Our eyes 👀 -->  3rd (2) | 2nd (1) | 1st (1)

Vimos que deslizar a terceira imagem e mudar sua z-index mantém em cima. O que precisamos fazer é atualizar o z-index da segunda imagem. Portanto, antes de deslizarmos a terceira imagem para fora do baralho, atualizamos o z-index da segunda imagem para 2.

Em outras palavras, redefinimos o z-index da segunda imagem antes que a animação termine.

Diagramar as partes da animação com indicadores para onde o z-index é aumentado ou diminuído.
Controle deslizante infinito CSS passando por imagens Polaroid

O símbolo de mais verde representa o aumento z-index para 2, e o símbolo de menos vermelho corresponde a z-index: 1. A segunda imagem começa com z-index: 2, então nós o atualizamos para 1 quando desliza para longe do convés. Mas antes que a primeira imagem deslize para fora do baralho, mudamos o z-index da segunda imagem de volta para 2. Isso garantirá que ambas as imagens tenham o mesmo z-index, mas ainda assim, o terceiro permanecerá no topo porque aparece mais tarde no DOM. Mas após os slides da terceira imagem e sua z-index é atualizado, ele se move para o fundo.

Esses dois terços da animação, então vamos atualizar nossos quadros-chave de acordo:

@keyframes slide {
  0%     { transform: translateX(0%);   z-index: 2; }
  16.66% { transform: translateX(120%); z-index: 2; }
  16.67% { transform: translateX(120%); z-index: 1; } /* we update the z-order here */
  33.34% { transform: translateX(0%);   z-index: 1; }
  66.33% { transform: translateX(0%);   z-index: 1; }
  66.34% { transform: translateX(0%);   z-index: 2; } /* and also here */
  100%   { transform: translateX(0%);   z-index: 2; }  
}

Um pouco melhor, mas ainda não bastante lá. Há outra questão…

Oh não, isso nunca vai acabar!

Não se preocupe, não vamos alterar os quadros-chave novamente porque esse problema só acontece quando a última imagem está envolvida. Podemos fazer uma animação de quadro-chave “especial” especificamente para a última imagem para consertar as coisas.

Quando a primeira imagem está no topo, temos a seguinte situação:

Our eyes 👀 -->  1st (2) | 3rd (1) | 2nd (1)

Considerando o ajuste anterior que fizemos, a terceira imagem saltará para cima antes que a primeira imagem deslize. Isso só acontece nessa situação porque a próxima imagem que se move após a primeira imagem é a último image que tem uma ordem superior no DOM. O resto das imagens estão bem porque temos N, Em seguida N - 1, então vamos de 3 para 2 e 2 para 1… mas então vamos de 1 para N.

Para evitar isso, usaremos os seguintes quadros-chave para a última imagem:

@keyframes slide-last {
  0%     { transform: translateX(0%);   z-index: 2;}
  16.66% { transform: translateX(120%); z-index: 2; }
  16.67% { transform: translateX(120%); z-index: 1; } /* we update the z-order here */
  33.34% { transform: translateX(0%);   z-index: 1; }
  83.33% { transform: translateX(0%);   z-index: 1; }
  83.34% { transform: translateX(0%);   z-index: 2; } /* and also here */
  100%   { transform: translateX(0%);   z-index: 2; }
}

Nós redefinimos o z-index valor 5/6 através da animação (ao invés de dois terços) que é quando a primeira imagem sai da pilha. Portanto, não vemos nenhum salto!

TADA! Nosso controle deslizante infinito agora é perfeito! Aqui está o nosso código final em toda a sua glória:

.gallery > img {
  animation: slide 6s infinite;
}
.gallery > img:last-child {
  animation-name: slide-last;
}
.gallery > img:nth-child(2) { animation-delay: -2s; } 
.gallery > img:nth-child(3) { animation-delay: -4s; }

@keyframes slide {
  0% { transform: translateX(0%); z-index: 2; }
  16.66% { transform: translateX(120%); z-index: 2; }
  16.67% { transform: translateX(120%); z-index: 1; } 
  33.34% { transform: translateX(0%); z-index: 1; }
  66.33% { transform: translateX(0%); z-index: 1; }
  66.34% { transform: translateX(0%); z-index: 2; } 
  100% { transform: translateX(0%); z-index: 2; }
}
@keyframes slide-last {
  0% { transform: translateX(0%); z-index: 2; }
  16.66% { transform: translateX(120%); z-index: 2; }
  16.67% { transform: translateX(120%); z-index: 1; }
  33.34% { transform: translateX(0%); z-index: 1; }
  83.33% { transform: translateX(0%); z-index: 1; }
  83.34% { transform: translateX(0%); z-index: 2; } 
  100%  { transform: translateX(0%); z-index: 2; }
}

Suportando qualquer número de imagens

Agora que nossa animação funciona para três imagens, vamos fazê-la funcionar para qualquer número (N) de imagens. Mas antes podemos otimizar um pouco nosso trabalho dividindo a animação para evitar redundância:

.gallery > img {
  z-index: 2;
  animation: 
    slide 6s infinite,
    z-order 6s infinite steps(1);
}
.gallery > img:last-child {
  animation-name: slide, z-order-last;
}
.gallery > img:nth-child(2) { animation-delay: -2s; } 
.gallery > img:nth-child(3) { animation-delay: -4s; }

@keyframes slide {
  16.67% { transform: translateX(120%); }
  33.33% { transform: translateX(0%); }
}
@keyframes z-order {
  16.67%,
  33.33% { z-index: 1; }
  66.33% { z-index: 2; }
}
@keyframes z-order-last {
  16.67%,
  33.33% { z-index: 1; }
  83.33% { z-index: 2; }
}

Muito menos código agora! Fazemos uma animação para a parte deslizante e outra para a z-index atualizações. Observe que usamos steps(1) na z-index animação. Isso porque eu quero mudar abruptamente o z-index valor, ao contrário da animação deslizante onde queremos um movimento suave.

Agora que o código é mais fácil de ler e manter, temos uma visão melhor para descobrir como oferecer suporte a qualquer número de imagens. O que precisamos fazer é atualizar os atrasos da animação e as porcentagens dos quadros-chave. O atraso é fácil porque podemos usar exatamente o mesmo loop que fizemos no último artigo para suportar várias imagens no controle deslizante circular:

@for $i from 2 to ($n + 1) {
  .gallery > img:nth-child(#{$i}) {
    animation-delay: calc(#{(1 - $i)/$n}*6s);
  }
}

Isso significa que estamos mudando do CSS vanilla para o Sass. Em seguida, precisamos imaginar como a escala da linha do tempo com N imagens. Não vamos esquecer que a animação acontece em três fases:

Mostrando as três partes da animação em uma série de linhas com setas.
Controle deslizante infinito CSS passando por imagens Polaroid

Depois de “deslizar para a direita” e “deslizar para a esquerda”, a imagem deve ficar parada até que o restante das imagens percorra a sequência. Portanto, a parte "não se mova" precisa levar o mesmo tempo que (N - 1) como “desliza para a direita” e “desliza para a esquerda”. E dentro de uma iteração, N as imagens deslizarão. Assim, “deslizar para a direita” e “deslizar para a esquerda” levam 100%/N da linha de tempo total da animação. A imagem desliza para longe da pilha em (100%/N)/2 e desliza para trás em 100%/N .

Podemos mudar isso:

@keyframes slide {
  16.67% { transform: translateX(120%); }
  33.33% { transform: translateX(0%); }
}

…para isso:

@keyframes slide {
  #{50/$n}%  { transform: translateX(120%); }
  #{100/$n}% { transform: translateX(0%); }
}

Se substituirmos N de 3, Nós temos 16.67% e 33.33% quando houver 3 imagens na pilha. É a mesma lógica com a ordem de empilhamento onde teremos isso:

@keyframes z-order {
  #{50/$n}%,
  #{100/$n}% { z-index: 1; }
  66.33% { z-index: 2; }
}

Ainda precisamos atualizar o 66.33% ponto. Supõe-se que seja onde a imagem redefine seu z-index antes do final da animação. Ao mesmo tempo, a próxima imagem começa a deslizar. Como a parte deslizante leva 100%/N, a reinicialização deve acontecer em 100% - 100%/N:

@keyframes z-order {
  #{50/$n}%,
  #{100/$n}% { z-index: 1; }
  #{100 - 100/$n}% { z-index: 2; }
}

Mas para o nosso z-order-last animação funcione, isso deve acontecer um pouco mais tarde na sequência. Lembra da correção que fizemos na última imagem? Reiniciando o z-index value precisa acontecer quando a primeira imagem estiver fora da pilha e não quando ela começar a deslizar. Podemos usar o mesmo raciocínio aqui em nossos keyframes:

@keyframes z-order-last {
  #{50/$n}%,
  #{100/$n}% { z-index: 1; }
  #{100 - 50/$n}% { z-index: 2; }
}

Acabamos! Aqui está o que obtemos ao usar cinco imagens:

Podemos adicionar um toque de rotação para tornar as coisas um pouco mais sofisticadas:

Tudo o que fiz foi anexar rotate(var(--r)) ao transform propriedade. Dentro do circuito, --r é definido com um ângulo aleatório:

@for $i from 1 to ($n + 1) {
  .gallery > img:nth-child(#{$i}) {
    --r: #{(-20 + random(40))*1deg}; /* a random angle between -20deg and 20deg */
  }
}

A rotação cria pequenas falhas, pois às vezes podemos ver algumas das imagens pulando para o final da pilha, mas não é grande coisa.

Resumindo

Tudo isso z-index O trabalho era um grande ato de equilíbrio, certo? Se você não tinha certeza de como a ordem de empilhamento funcionava antes deste exercício, provavelmente tem uma ideia muito melhor agora! Se você achou algumas das explicações difíceis de seguir, eu recomendo que você faça outra leitura do artigo e mapeie as coisas com lápis e papel. Tente ilustrar cada passo da animação usando um número diferente de imagens para entender melhor o truque.

Da última vez, usamos alguns truques de geometria para criar um controle deslizante circular que gira de volta para a primeira imagem após uma sequência completa. Desta vez, realizamos um truque semelhante usando z-index. Em ambos os casos, não duplicamos nenhuma das imagens para simular uma animação contínua, nem recorremos ao JavaScript para ajudar nos cálculos.

Da próxima vez, faremos controles deslizantes 3D. Fique ligado!

Carimbo de hora:

Mais de Truques CSS