O efeito de paralaxe na rolagem é uma técnica visual em que diferentes camadas de conteúdo se movem em velocidades variadas conforme o usuário rola a página para baixo. Isso cria uma ilusão de profundidade, com os elementos em primeiro plano parecendo se mover mais rápido do que os elementos em segundo plano, resultando em uma experiência de navegação envolvente e imersiva. Para conseguir esse efeito, use as animações de rolagem do CSS, que permitem vincular animações à posição de rolagem de um contêiner.
Como fazer a implementação
Veja como criar um efeito de paralaxe básico:
Crie um elemento wrapper: esse elemento simplesmente agrupa todas as camadas do efeito paralaxe. Ele não é o elemento rolável, então o transbordamento precisa ser cortado. Além disso, defina um
heightque corresponda à altura de uma das camadas do efeito de paralaxe.<div class="wrapper"> … </div>.wrapper { overflow: clip; height: 100vh; /* Height of one of the layers of the parallax */ }Declare as camadas: dentro do wrapper, adicione as camadas individuais que vão se mover em velocidades diferentes.
<div class="wrapper"> <div class="layer">LAYER 0</div> <div class="layer">LAYER 1</div> <div class="layer">LAYER 2</div> … </div>Adicione uma animação de translação: defina uma animação CSS que mude a propriedade
transformdas camadas. Para um efeito de paralaxe, geralmente você usatranslateYpara mover as camadas verticalmente.@keyframes parallax { from { transform: translateY(700px); } }Configure o
view-timeline: para vincular a animação à posição de rolagem, crie umview-timelineno elemento wrapper e aplique-o às camadas..wrapper { view-timeline: --wrapper; } .layer { animation: parallax linear both; animation-timeline: --wrapper; }Intercale as animações: para que as camadas se movam em velocidades diferentes, use uma destas duas abordagens principais: intercalar nos frames-chave ou intercalar o
animation-range.As duas abordagens podem usar valores fixados no código ou a implementação
sibling-index()/sibling-count(). Os valores fixados no código são mais fáceis e também úteis quando há apenas uma quantidade limitada de camadas. A implementação desibling-index()/sibling-count()é útil quando você tem muitas camadas.Escalonamento nos frames-chave:
Usando valores fixados no código, é possível definir uma propriedade personalizada para cada camada para controlar manualmente o deslocamento de paralaxe.
.layer:nth-child(1) { --offset: 100px; } .layer:nth-child(2) { --offset: 200px; } .layer:nth-child(3) { --offset: 300px; } @keyframes parallax { from { transform: translateY(var(--offset)); } }Usando
sibling-index(), permita que a funçãosibling-index()retorne o índice de um elemento filho entre os irmãos para calcular automaticamente o efeito escalonado.@keyframes parallax { from { transform: translateY(calc(100px * sibling-index())); } }Como escalonar o
animation-range:Usando valores fixados no código, é possível definir explicitamente os limites do
animation-rangeem cada camada individualmente..layer:nth-child(1) { animation-range: entry 25% exit 50%; } .layer:nth-child(2) { animation-range: entry 25% exit 75%; } .layer:nth-child(3) { animation-range: entry 25% exit 100%; }Usando
sibling-index()esibling-count(), é possível calcular o intervalo matematicamente com base no número total de camadas (sibling-count())..layer { animation-range: entry 25% exit calc(100% / sibling-count() * sibling-index()); }
Exemplo de código
@keyframes parallax {
from {
transform: translateY(calc(100px * sibling-index()));
}
}
.wrapper {
view-timeline: --wrapper;
}
.layer {
animation: parallax linear both;
animation-timeline: --wrapper;
}
@media (prefers-reduced-motion: reduce) {
.layer {
animation: none;
}
}
Como alternativa, use a propriedade animation-range para conseguir um efeito semelhante:
@keyframes parallax {
from {
transform: translateY(700px);
}
}
.wrapper {
view-timeline: --wrapper;
}
.layer {
animation: parallax linear both;
animation-timeline: --wrapper;
animation-range: entry 25% exit calc(100% / sibling-count() * sibling-index());
}
@media (prefers-reduced-motion: reduce) {
.layer {
animation: none;
}
}
Práticas recomendadas
Ao usar animações acionadas por rolagem, é importante seguir algumas práticas recomendadas para garantir uma experiência tranquila e acessível:
- FAÇA a detecção de recursos: nem todos os navegadores oferecem suporte a animações acionadas por rolagem. Use
@supports ((animation-timeline: view()) and (animation-range: entry))para verificar a compatibilidade e fornecer um substituto para navegadores que não oferecem suporte.- A verificação
(animation-range: entry)PRECISA ser incluída aqui para filtrar navegadores com suporte parcial. - NÃO use o pacote
scroll-timeline-polyfillpara a estratégia de substituição, porque ele não tem todos os recursos e tem muitos problemas conhecidos. - Se a animação for considerada apenas decorativa, opte pelo aprimoramento progressivo e NÃO forneça um substituto.
- A verificação
- RESPEITE as preferências do usuário: algumas pessoas preferem menos movimento na Web. Use a consulta de mídia
prefers-reduced-motionpara desativar ou reduzir as animações para esses usuários. - FAÇA o possível para animar apenas propriedades CSS de alto desempenho: para animações mais suaves, anime propriedades que podem ser processadas pela linha de execução do compositor do navegador, como
transformeopacity. Animar outras propriedades, comowidthouheight, pode causar problemas de desempenho. - USE a ordem de declaração correta: ao usar a propriedade abreviada
animation, declareanimation-timelineeanimation-rangedepois dela para evitar que a abreviação redefina a linha do tempo.
Para definir o animation-range:
- FAÇA com que todas as camadas tenham o mesmo deslocamento inicial, por exemplo,
entry 25%. - RECOMENDAMOS dar a todas as camadas um deslocamento final diferente que use
sibling-count()esibling-index()para distribuir os deslocamentos, por exemplo,exit calc(100% / sibling-count() * sibling-index()).
Suporte ao navegador e estratégias de substituição
Portanto, uma estratégia de fallback geralmente é necessária.
Para navegadores que não oferecem suporte a animações acionadas por rolagem, use um substituto para recriar os efeitos visuais. Os substitutos geralmente são criados com um listener de rolagem (para efeitos de ScrollTimeline) ou com a API IntersectionObserver (para efeitos de ViewTimeline).
Em navegadores com suporte integrado para animações de rolagem, use SEMPRE a implementação nativa de CSS, porque ela tem melhor desempenho.
Nem todos os efeitos podem ser recriados usando a abordagem de substitutos.
Para esse caso de uso específico, o script a seguir aplica o substituto para navegadores que não oferecem suporte a animações de rolagem. Ele usa um IntersectionObserver para rastrear a visibilidade do elemento .wrapper e atualiza a propriedade transform das camadas com base na posição de rolagem.
// Fallback for browsers that don't support scroll-driven animations
if (!CSS.supports('(animation-timeline: view()) and (animation-range: entry)')) {
const wrapper = document.querySelector('.wrapper');
const layers = document.querySelectorAll('.layer');
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
window.addEventListener('scroll', onScroll);
} else {
window.removeEventListener('scroll', onScroll);
}
});
}, { threshold: 0 });
observer.observe(wrapper);
function onScroll() {
const scrollY = window.scrollY;
const wrapperRect = wrapper.getBoundingClientRect();
const wrapperTop = wrapperRect.top + scrollY;
const wrapperHeight = wrapperRect.height;
const windowHeight = window.innerHeight;
if (scrollY >= wrapperTop - windowHeight && scrollY <= wrapperTop + wrapperHeight) {
const scrollPercent = (scrollY - (wrapperTop - windowHeight)) / (wrapperHeight + windowHeight);
layers.forEach((layer, index) => {
// This matches the effect as defined in the CSS example above.
// Customize this further if needed.
const initialTranslateY = 100 * index;
const translateY = initialTranslateY * (1 - scrollPercent);
layer.style.transform = `translateY(${translateY}px)`;
});
}
}
// Trigger onScroll once to set initial positions
onScroll();
}