Conteúdo deste artigo
- Movimento como comunicação: definindo sua intenção
- Opção A: O evento psicodélico
- Opção B: O Retiro Espiritual
- Dividir animações de texto
- Mascaramento e recorte
- Animações baseadas em rolagem
- Transformações 3D
- Cursores personalizados
- Bônus: posicionamento da âncora
- Conclusão
Adoro o fato de que o CSS está finalmente recuperando o controle sobre as interações visuais, assumindo o controle do estilo, da animação e da acessibilidade exatamente como deveria. Hoje, os recursos nativos do navegador nos permitem afastar o trabalho pesado do thread principal do JavaScript e aproximá-lo da GPU. Ao permitir que o mecanismo do navegador otimize o desempenho nos bastidores, economizamos energia e poder de processamento enquanto construímos um código que é robusto, acessível e independente de bibliotecas externas que podem ser obsoletas amanhã.
Temos 3D, técnicas de layout modernas, caminhos de clipe, transformações, propriedades personalizadas, animações orientadas por rolagem, transições de visualização, @property — e podemos animar quase tudo, até mesmo para altura automática !
E, claro, tem o SVG, que não é novidade, mas nos permite construir sites inteiros através de ilustrações e animações. Veja o exemplo abaixo: é responsivo, leve, acessível e alimentado principalmente por CSS Grid + SVG.
Podemos até construir um videogame inteiro incluindo a UI usando apenas SVG:
O que se segue não é um guia completo para CSS moderno, mas uma seleção opinativa de técnicas que utilizo quando quero que um site pareça vivo e seja lembrado. Existem muitas maneiras de criar experiências memoráveis. Às vezes é tão simples quanto um formulário que é preenchido sem problemas. Mas aqui estou interessado na extremidade expressiva do espectro.
Movimento como comunicação: definindo sua intenção
Antes de mergulharmos no lado técnico, quero esclarecer uma coisa: não devemos mudar as coisas só porque podemos.
Tudo se comunica e nossas animações não são exceção. Devemos reservar um tempo para projetar movimentos que apoiem a mensagem que queremos transmitir, a fim de manter nossas intenções bem definidas, sem exagerar.
Aqui está uma metodologia que utilizo ao planejar o design e a animação de um site.
Imagine que estamos trabalhando em um projeto para um evento de natureza focado em cogumelos . A linguagem do design muda completamente dependendo da “vibe”: vender uma “Rave de Cogumelos Psicodélicos” é muito diferente de um “Retiro Espiritual de Cogumelos” focado na medicina ancestral.
Cada decisão de design comunica. Gosto de criar o que chamo de listas de palavras-chave para definir minha intenção e escopo. Por exemplo, posso dividir as coisas em diferentes opções:
Opção A: O evento psicodélico
- Imagens: Colorido, saturado, alto contraste, ilustrações, distorções
- Movimento: Rápido, frenético, imprevisível, metamorfose, rítmico, loops sincronizados, hipnótico
- Sentimento: Divertido, caótico, enérgico, estimulante, surpreendente
- Tipografia: Funk, “rock psicológico”
- Referências de estilo: Pop Art, Op Art dos anos 60/70, panfletos rave
- Ações: Dançando
- Extras: Emojis, filmes (por exemplo, Medo e Delírio em Las Vegas )
Opção B: O Retiro Espiritual
- Imagens: Tons terrosos, tons neutros, dessaturados, muitas fotografias, natureza, espaços em branco
- Movimento: Lento, fluido, orgânico, respiratório, paralaxe sutil, rolagem suave.
- Sentimento: Calmo, sereno, introspectivo, contemplativo, seguro
- Tipografia: Serif elegante, sem serifa minimalista, espaçamento amplo, legível
- Referências de estilo: Design escandinavo, japonês Wabi-sabi , estética de bem-estar/spa, livros botânicos
- Ações: Respiração
- Extras: Sons de cura, filme (por exemplo, Comer, Rezar, Amar )
Este é o tipo de exercício que faço para orientar minhas decisões de design e animação. As listas me ajudarão a selecionar todas as propriedades CSS que pretendo usar e como usá-las. Inclusive compartilho com o cliente e, juntos, escolhemos um rumo.
Digamos que optemos pela Opção A e vejamos alguns exemplos do que considero ingredientes essenciais para criar experiências de usuário memoráveis.
Dividir animações de texto
Essas animações se tornaram populares graças ao plugin GSAP SplitText . Ele divide o texto por caractere (ou palavras, ou linhas, se preferir) para que possamos criar efeitos de texto interessantes, como animações escalonadas.
<h1 class="reveal-text">
<span style="--i:0">H</span>
<span style="--i:1">O</span>
<span style="--i:2">L</span>
<span style="--i:3">A</span>
</h1>
Esta abordagem agrupa cada letra em “Hola” em um intervalo. A partir daí, cada extensão é estilizada em linha com uma propriedade personalizada que indexa as extensões em ordem. O que será muito mais fácil quando a função sibling-index() ganhar amplo suporte ao navegador.
Mas, por enquanto, cada valor de propriedade customizada atua como um multiplicador que aumenta um animation-delay, escalonando cada intervalo. Neste caso, desaparecemos cada personagem à medida que ele sobe.
.reveal-text span {
animation: slideUp 0.6s ease-out forwards;
animation-delay: calc(var(--i) * 0.1s);
display: inline-block;
opacity: 0;
transform: translateY(3rem);
}
@keyframes slideUp {
to {
opacity: 1;
transform: translateY(0);
}
}
Acessibilidade é a parte complicada aqui. O instinto é ocultar todos os trechos individuais da tecnologia assistiva com aria-hidden="true" e adicionar uma versão visualmente oculta da palavra completa para leitores de tela:
<h1>
<span class="sr-only">HOLA</span>
<span aria-hidden="true" class="reveal-text">
<span style="--i:0">H</span>
<span style="--i:1">O</span>
<span style="--i:2">L</span>
<span style="--i:3">A</span>
</span>
</h1>
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
Mas esteja avisado: esse padrão não garante uma boa experiência em todos os leitores de tela. Adrian Roselli testou o SplitText do GSAP em oito combinações de leitor de tela e navegador e descobriu que ele só funcionou corretamente em duas delas. Se você enviar esta técnica, teste-a com tecnologia assistiva real.
Se esse risco parecer muito alto, há uma alternativa muito inteligente de Preethi que vale a pena conhecer e que usa a propriedade letter-spacing . Ele aceita valores negativos que colapsam os caracteres uns sobre os outros, ocultando-os sem tocar no DOM. Anime-o de volta para 0 e você obterá um efeito de revelação semelhante sem sobrecarga de acessibilidade.
O que seria ótimo é um pseudo-seletor como ::nth-letter para direcionar glifos individuais diretamente do CSS da maneira ::first-letter seleciona o primeiro caractere. Mas, infelizmente, não existe ::nth-letter… pelo menos ainda.
Lembre-se de respeitar as preferências de movimento do usuário em cada animação:
@media (prefers-reduced-motion: reduce) {
.reveal-text span {
animation: none; /* or a softer animation */
}
}
E aqui vamos nós:
Pode não ser muito dimensionado quando temos muito texto e animações diferentes que queremos aplicar. Para o evento psicodélico, eu queria tentar dividir o texto com SMIL , mas era detalhado. Este é o código para animar duas letras sozinhas:
<svg role="img" aria-label="TODOS LOS HONGOS" viewBox="0 0 1366 938.96">
<title>TODOS LOS HONGOS</title>
<g aria-hidden="true">
<text transform="rotate(-9.87 2181.107 -1635.1)" opacity="0">T
<animate attributeName="dy" values="100; -20; 0" keyTimes="0; 0.8; 1" dur="0.4s" begin="0s" fill="freeze"/>
<animate attributeName="opacity" from="0" to="1" dur="0.01s" begin="0s" fill="freeze"/>
</text>
<text transform="rotate(-8.92 2372.854 -2084.755)" opacity="0">O
<animate attributeName="dy" values="100; -20; 0" keyTimes="0; 0.8; 1" dur="0.4s" begin="0.1s" fill="freeze"/>
<animate attributeName="opacity" from="0" to="1" dur="0.01s" begin="0.1s" fill="freeze"/>
</text>
<!-- rest of letters... -->
</g>
</svg>
Adicione role="img" e um <title> ao <svg> e coloque as letras individuais em <g aria-hidden="true">. Isso dá aos leitores de tela um rótulo limpo para ler. Funciona bem em algumas combinações e mal em outras, então se o texto for crítico, não o anime.
Aqui está o código completo. É mais fácil escrever quando você tem uma IA para fazer isso por você:
Para textos mais longos, uma biblioteca como GSAP oferece mais controle, mas os mesmos riscos de acessibilidade que discutimos anteriormente se aplicam, e os resultados em leitores de tela são inconsistentes:
<h1>
<span class="splitfirst">Todos los hongos son</span>
<span class="splitlast">mágicos</span>
</h1>
const splitFirst = SplitText.create('.splitfirst', {
type: "chars",
});
const splitLast = SplitText.create('.splitlast', {
type: "chars, lines",
mask: "lines"
});
const tween = gsap.timeline()
.from(splitFirst.chars, {
xPercent: 100,
stagger: 0.1,
opacity: 0,
duration: 1,
})
.from(splitLast.chars, {
yPercent: 100,
stagger: 0.1,
opacity: 0,
duration: 1,
});
Esta seria uma boa abordagem para a Opção B se tivéssemos seguido esse caminho. Veja como as coisas ficam “serenas” à medida que o texto desaparece.
Mascaramento e recorte
O clip-path e mask propriedades nos permitem ocultar partes de um elemento, mas funcionam com princípios fundamentalmente diferentes. Recorte é uma decisão binária: os pixels ficam totalmente visíveis ou desaparecem completamente, tornando-o a escolha certa para formas geométricas limpas, como polígonos, círculos ou caminhos SVG, onde o navegador também pode otimizar a renderização com mais eficiência. Mascaramento , por outro lado, usa valores de luminância ou canal alfa: branco revela, preto esconde e tudo mais produz transparência parcial. Isso o torna a ferramenta para bordas suaves, desbotamentos gradientes e texturas irregulares. Tenha em mente que se você tiver uma forma de vetor muito complexa, pode ser mais eficiente usar um mask do que um vetor clip-path. Sarah Drasner escreveu um ótimo artigo sobre quando faz sentido usar um em vez do outro.
Nosso projeto é um caso de uso muito claro para clip-path. Temos uma forma de círculo que começa com clip-path: circle(0%), o que torna o elemento invisível (o círculo de recorte tem raio zero). Ao longo da animação, ele se expande para circle(100%), que revela totalmente o elemento à medida que o círculo cresce para fora de seu centro. Enquanto isso, fazemos fade in com a ajuda de opacity.
#rainbow, #floor, #mushroom, #flores {
opacity: 0;
animation: maskAnim 2s ease-in forwards;
}
@keyframes maskAnim {
0%, 1% {
clip-path: circle(0%);
opacity: 1;
}
100% {
clip-path: circle(100%);
opacity: 1;
}
}
Nota: O quadro-chave 1% está lá para garantir que o navegador inicie o clip-path interpolação de circle(0%) em vez de qualquer valor que o elemento já possa ter. Sem ele, alguns navegadores saltarão inesperadamente logo no início. Uma alternativa mais limpa é usar animation-fill-mode: both porque ele bloqueia o elemento em seu from estado antes do início da animação.
A partir daí, aplicamos a mesma animação aos diferentes grupos SVG em nossa ilustração:
<g id="rainbow">...</g>
<g id="floor">...</g>
<g id="mushroom">...</g>
<g id="flowers">...</g>
Quão psicodélico é isso?!
Animações baseadas em rolagem
As animações controladas por rolagem são ótimas porque podemos conectar o progresso de uma animação à rolagem do usuário, em vez de uma linha do tempo típica que é executada e interrompida.
Podemos usá-lo para movimentos sutis e um tanto “alucinantes”, como um leve efeito de paralaxe. Nesse caso, podemos fazer com que as coisas que parecem mais próximas do usuário se movam mais rápido do que as que estão mais distantes.
Este é o CSS completo:
#estrellas, #arcoiris, .text-line, #fecha, #arco, #flores, #dir, #piso, #barras {
animation: moveUp both;
animation-timeline: view();
}
@keyframes moveUp {
from { transform: translateY(var(--offset)); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
#estrellas { --offset: 10vh; }
#arcoiris { --offset: 20vh; }
#fecha { --offset: 45vh; }
#arco { --offset: 50vh; }
#dir { --offset: 50vh; }
#flores { --offset: 65vh; }
#piso { --offset: 85vh; }
#barras { --offset: 90vh; }
O animation-timeline: view() diz que as coisas devem iniciar a animação assim que um elemento entra na porta de rolagem quando o usuário rola para dentro dela e é totalmente concluída quando rola para fora da vista. Para fazer as coisas se moverem em velocidades diferentes, nós as colocamos em deslocamentos diferentes usando uma propriedade personalizada indexada --offset como fizemos anteriormente para dividir texto.
Transformações 3D
Este é mais complicado e precisamos ficar de olho no desempenho. Uma ferramenta como Layoutit pode ajudar a transportar o elevador porque possui um voxels e gerador de terreno construído inteiramente com CSS 3D. Ele pode ir ainda mais longe quando é complementado com VoxCSS , um mecanismo voxel completo que renderiza cubóides 3D usando apenas camadas CSS Grid e transforma sem a complexidade do Canvas ou WebGL.
Vamos juntar algumas combinações de rolagem e efeitos 3D. É o tipo de coisa que apóia as ideias “hipnóticas” e “dançantes” na lista de palavras-chave da Opção A. Confira isso:
Aqui, configurei uma cena com profundidade usando a propriedade perspective e, em seguida, envolva todos os elementos filhos dentro da cena em um espaço 3D com transform-style: preserve-3d. Dessa forma, todos os elementos da imagem filha giram e transladam ao longo do eixo de profundidade (ou eixo z).
Vamos conectar isso a uma animação baseada em rolagem que usa transform: rotateY:
.scene {
perspective: 1200px;
}
.img-wrapper {
transform-style: preserve-3d;
animation: rotateImg linear;
animation-timeline: scroll();
> img {
transform: rotateY(270deg) translate3d(0, 50px, var(--distance));
}
> img:nth-child(2) {
transform: rotateY(180deg) translate3d(0, 50px, var(--distance));
}
}
/* etc. */
@keyframes rotateImg {
to { transform: rotateY(360deg); }
}
Cursores personalizados
cursor pode ser uma das propriedades CSS menos utilizadas. Existem muitos tipos de cursor que podemos usar, embora existam definitivamente opiniões sobre até onde ir com isso .
E podemos usá-lo para brincar com as imagens, exibindo diferentes cursores em diferentes contêineres quando o usuário passa o mouse sobre eles. Eu pessoalmente usaria uma imagem SVG e PNG para suporte de transparência, embora a propriedade suporte qualquer imagem rasterizada.
É importante notar que os tamanhos dos cursores variam de acordo com o navegador: o Firefox limita os cursores personalizados a 32×32px, enquanto o Chrome suporta até 128×128px. A maioria dos navegadores se recusa a exibir – ou reduzirá a escala – cursores maiores que 32×32px em telas de alto DPI (retina). Manter o cursor em 32×32px é a escolha mais segura para garantir consistência.
Por exemplo:
.box1 {
cursor: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB0AAAAZCAMAAAD63NUrAAAACVBMVEX///8AAAD///9+749PAAAAAXRSTlMAQObYZgAAAFZJREFUeNqdzksKwDAIAFHH+x+6lIYOVPOhs5OHJnES/5UkYKEkU7xjijSIm50iFh4fAXgYDd/yumVVRSwsqq/nRA3xVK0oo06d5U6DpQZ7PV7lMxH7LkaQAbYFwryzAAAAAElFTkSuQmCC),auto;
}
Podemos até definir vários substitutos para garantir o mais amplo nível de suporte do navegador:
body {
cursor: url('path-to-image.png'), url('path-to-image-2.svg'), url('path-to-image-3.jpeg'), auto;
}
Embora isso seja legal e tudo mais, temos que manter a acessibilidade em mente para algo que altera o comportamento padrão da web como este. Cursores personalizados podem ser divertidos de aplicar a elementos muito específicos, em vez de serem generalizados.
Bônus: posicionamento da âncora
Mais uma coisa antes de encerrarmos. Tenho brincado com CSS Anchor Positioning , inspirado em uma demonstração de Kevin Powell . Podemos usá-lo para anexar um único pseudoelemento a um item atualmente pairado, em vez de anexar um pseudoelemento para cada item. Em outras palavras, criamos um único elemento e o ancoramos em um elemento pairado, como cartões de destaque:
Isso abre possibilidades interessantes, como poder fazer a transição do estado de foco entre os cartões. Neste caso, estou usando a função linear() para obter aquele salto natural com a ajuda de Easing Wizard .
Conclusão
As barreiras técnicas para criar experiências memoráveis na web praticamente desapareceram. Espero que tudo o que abordamos aqui lhe dê uma ideia de até onde podemos ir com recursos CSS modernos que eliminam completamente a necessidade de JavaScript adicional. Temos mais possibilidades do que nunca, tudo sem a necessidade de despesas técnicas complexas como no passado.
Então, em vez de perguntar, isso é possível? , a questão mais importante é: esse movimento conta uma história melhor? Se sim, envie. Use essas ferramentas não porque você pode, mas porque elas ajudam você a contar uma história melhor, que também seja acessível e de alto desempenho.
E, claro, tudo aqui são apenas algumas maneiras de fazer isso. Mas que tipo de experiências memoráveis você usou em seu trabalho? Ou o que você viu em outros sites?
Criando experiências web memoráveis: um kit de ferramentas CSS moderno originalmente escrito à mão e publicado com amor em CSS-Tricks . Você realmente deveria receber o boletim informativo também.
