Paralaxe com performance

Robert Flack
Robert Flack

Não se engane: o paralaxe chegou para ficar. Quando usado criteriosamente, pode adicionar profundidade e sutileza a um aplicativo da web. No entanto, o problema é que a implementação fazer paralaxe com bom desempenho pode ser desafiador. Neste artigo, vamos falar sobre discutir uma solução com bom desempenho e que funcione entre navegadores.

Ilustração de paralaxe.

Texto longo, leia o resumo

  • Não use eventos de rolagem ou background-position para criar animações de paralaxe.
  • Use as transformações CSS 3D para criar um efeito de paralaxe mais preciso.
  • Para o Mobile Safari, use position: sticky para garantir que o efeito paralaxe sejam propagadas.

Se você quiser a solução drop-in, acesse o repositório do GitHub para exemplos de elementos da interface e faça o seguinte: JavaScript auxiliar de paralaxe! Veja uma demonstração ao vivo do botão de rolagem de paralaxe na repositório do GitHub.

Paralaxantes que apresentam problemas

Para começar, daremos uma olhada em duas formas comuns de obter uma paralaxe efeito e, em especial, por que elas são inadequadas para nossos objetivos.

Ruim: uso de eventos de rolagem

O principal requisito do paralaxe é que ele seja acoplado à rolagem. para cada mudança na posição de rolagem da página, o valor do elemento de paralaxe deve ser atualizada. Embora isso pareça simples, um mecanismo importante de dos navegadores mais recentes é a capacidade de trabalhar de maneira assíncrona. Isso se aplica, em nossa para eventos de rolagem. Na maioria dos navegadores, os eventos de rolagem são exibidos como “melhor esforço” e não há garantia de exibição em todos os frames da animação de rolagem.

Essa informação importante nos diz por que precisamos evitar uma Solução baseada em JavaScript que move elementos de acordo com eventos de rolagem: O JavaScript não garante que o paralaxe esteja alinhado com a a posição de rolagem da página. Em versões mais antigas do Mobile Safari, os eventos de rolagem eram entregue no final da rolagem, o que tornava impossível fazer uma Efeito de rolagem baseado em JavaScript. As versões mais recentes oferecem eventos de rolagem durante a animação, mas, assim como no Chrome, com base no "melhor esforço", base. Se o A linha de execução principal está ocupada com qualquer outro trabalho, os eventos de rolagem não serão entregues. imediatamente, o que significa que o efeito paralaxe será perdido.

Ruim: atualizando background-position

Outra situação que gostaríamos de evitar é pintar em todos os frames. Muitas soluções tentar mudar background-position para fornecer a aparência de paralaxe, que faz com que o navegador repinte as partes afetadas da página ao rolar, e que pode ser caro o suficiente para atrapalhar significativamente a animação.

Se quisermos cumprir a promessa do movimento de paralaxe, queremos algo que pode ser aplicada como uma propriedade acelerada (o que hoje significa manter transformações e opacidade) e que não depende de eventos de rolagem.

CSS em 3D

Tanto Scott Kellum quanto Keith Clark têm fez um trabalho significativo na área de uso de CSS 3D para alcançar o movimento de paralaxe, e a técnica usada é, na prática, esta:

  • Configure um elemento contêiner para rolar com a overflow-y: scroll (e provavelmente overflow-x: hidden).
  • Para esse mesmo elemento, aplique um valor perspective e um perspective-origin Defina como top left ou 0 0.
  • Aos filhos desse elemento, aplique uma tradução em Z e escalone-os de volta para oferecer movimento de paralaxe sem afetar o tamanho na tela.

O CSS para essa abordagem tem a seguinte aparência:

.container {
  width: 100%;
  height: 100%;
  overflow-x: hidden;
  overflow-y: scroll;
  perspective: 1px;
  perspective-origin: 0 0;
}

.parallax-child {
  transform-origin: 0 0;
  transform: translateZ(-2px) scale(3);
}

Ele pressupõe um snippet de HTML como este:

<div class="container">
    <div class="parallax-child"></div>
</div>

Ajustando a escala da perspectiva

Enviar o elemento filho de volta fará com que ele fique menor, proporcionalmente ao valor de perspectiva. Você pode calcular quanto precisará ser escalonado verticalmente esta equação: (perspective - distância) / perspectiva. Como provavelmente quer que o elemento paralaxe seja paralaxe, mas apareça no tamanho que o criamos, ele precisaria ser escalonado verticalmente, em vez de permanecer como está.

No caso do código acima, a perspectiva é 1px, e a A distância Z de parallax-child é -2 px. Isso significa que o elemento precisará ser escalonado verticalmente em 3x, que é o valor conectado ao código: scale(3)

Para qualquer conteúdo que não tenha um valor de translateZ aplicado, você pode substitua um valor de zero. Isso significa que a escala é (perspective - 0) / perspectiva, que resulta em um valor de 1, o que significa que ele foi dimensionado nem para cima nem para baixo. Isso é muito útil.

Como essa abordagem funciona

É importante esclarecer por que isso funciona, já que vamos usá-lo conhecimento logo. A rolagem é efetivamente uma transformação e, por isso, ela pode ser accelerated; envolve principalmente a troca de camadas com a GPU. Em um de rolagem típica, que é aquela sem noção de perspectiva, a rolagem acontece de forma individual ao comparar o elemento de rolagem e seus filhos. Se você rolar um elemento para baixo até 300px, os filhos dele serão transformados para cima pelo mesmo valor: 300px.

No entanto, aplicar um valor de perspectiva ao elemento de rolagem atrapalha com este processo. ela altera as matrizes que sustentam a transformação de rolagem. Agora, uma rolagem de 300px só pode mover os filhos em 150px, dependendo do Os valores perspective e translateZ que você escolheu. Se um elemento tiver um translateZ com o valor 0, a rolagem será de 1:1 (como costumava ser), mas um filho empurrado em Z para longe da origem da perspectiva será rolado em um taxa! Resultado: movimento de paralaxe. E, muito importante, isso é tratado como parte da máquina de rolagem interna do navegador, o que significa que não há não é necessário ouvir eventos scroll nem alterar background-position.

Uma mosca na pomada: Mobile Safari

Há ressalvas para cada efeito, e uma importante para as transformações é sobre a preservação de efeitos 3D em elementos infantis. Se houver elementos no hierarquia entre o elemento com uma perspectiva e seus filhos de paralaxe, a perspectiva 3D é "achatada", o que significa que o efeito é perdido.

<div class="container">
    <div class="parallax-container">
    <div class="parallax-child"></div>
    </div>
</div>

No HTML acima, o .parallax-container é novo e vai funcionar de maneira eficaz. o valor perspective se nivela, e o efeito paralaxe é perdido. A solução, na maioria dos casos é bem simples: você adiciona transform-style: preserve-3d ao elemento, fazendo com que ele se propague efeitos 3D (como nossa perspectiva valor) que foram aplicados mais acima na árvore.

.parallax-container {
  transform-style: preserve-3d;
}

No caso do Mobile Safari, no entanto, as coisas são um pouco mais complicadas. A aplicação de overflow-y: scroll ao elemento de contêiner funciona tecnicamente, mas em o custo de deslizar rapidamente o elemento de rolagem. A solução é adicionar -webkit-overflow-scrolling: touch, mas também nivela o perspective e não haverá paralaxe.

Do ponto de vista do aprimoramento progressivo, isso provavelmente não é um problema. Se não pudermos paralaxe em todas as situações, nosso aplicativo ainda funcionará, mas seria bom encontrar uma solução.

position: sticky ao resgate!

Na verdade, há alguma ajuda na forma de position: sticky, que existe para permitir que os elementos "fiquem" na parte superior da janela de visualização ou em um elemento pai durante a rolagem. A especificação, como a maioria deles, é bastante pesada, mas contém um uma pequena pérola útil em:

Isso pode não parecer muito à primeira vista, mas é um ponto importante Essa frase é sobre como, exatamente, a adesão de um elemento é calculado: "o deslocamento é calculado com referência ao ancestral mais próximo com uma caixa de rolagem". Em outras palavras, a distância para mover o elemento fixo (para que ele apareça anexado a outro elemento ou à janela de visualização) é calculada antes da aplicação de qualquer outra transformação, não depois. Isso significa que que, como o exemplo de rolagem anterior, se o deslocamento fosse calculado em 300 px, há uma nova oportunidade de usar perspectivas (ou qualquer outra transformação) para manipular o valor de deslocamento de 300 px antes de aplicá-lo a qualquer os elementos.

Ao aplicar position: -webkit-sticky ao elemento de paralaxe, podemos efetivamente “reverter” o efeito de achatamento de -webkit-overflow-scrolling: touch. Isso garante que o elemento de paralaxe faça referência ao objeto ancestral com uma caixa de rolagem, que nesse caso é .container. Depois, Assim como antes, o .parallax-container aplica um valor perspective, que altera o deslocamento de rolagem calculado e cria um efeito paralaxe.

<div class="container">
    <div class="parallax-container">
    <div class="parallax-child"></div>
    </div>
</div>
.container {
  overflow-y: scroll;
  -webkit-overflow-scrolling: touch;
}

.parallax-container {
  perspective: 1px;
}

.parallax-child {
  position: -webkit-sticky;
  top: 0px;
  transform: translate(-2px) scale(3);
}

Isso restaura o efeito paralaxe para o Mobile Safari, que é uma excelente notícia. rodada!

Ressalvas de posicionamento fixas

No entanto, uma diferença aqui: position: sticky altera o mecânica de paralaxe. O posicionamento fixo tenta fixar o elemento no contêiner de rolagem, enquanto uma versão não fixa não. Isso significa que o efeito paralaxe com fixo acaba sendo o inverso do sem:

  • Com position: sticky, quanto mais próximo o elemento estiver de z=0, menos ele se move.
  • Sem position: sticky, quanto mais próximo o elemento estiver de z=0, mais eles se movem.

Se tudo isso parece um pouco abstrato, veja esta demonstração de Robert Flack, que demonstra como os elementos se comportam de maneira diferente com e sem elementos fixos de posicionamento. Para ver a diferença, você precisa do Chrome Canary (que é a versão 56) no momento em que este artigo foi escrito) ou Safari.

Captura de tela da perspectiva do paralaxe

Uma demonstração de Robert Flack mostrando como position: sticky afeta a rolagem de paralaxe.

Vários bugs e soluções alternativas

No entanto, como em qualquer caso, ainda há protuberâncias e protuberâncias que precisam ser suavizado:

  • O suporte fixo é inconsistente. O suporte ainda está sendo implementado nos O Chrome, o Edge não têm suporte total, e o Firefox apresenta bugs de pintura quando o "sticky" é combinado com transformações de perspectiva (link em inglês). Dessa forma, casos, vale a pena adicionar um pequeno código para adicionar apenas position: sticky (os versão com prefixo -webkit-) quando necessário (para o Mobile Safari) .
  • O efeito não "simplesmente funciona" no Edge. O Edge tenta processar a rolagem no nível do SO, o que geralmente é bom, mas nesse caso, impede que desde a detecção das mudanças de perspectiva durante a rolagem. Para corrigir isso, você pode adicionar Um elemento de posição fixa, já que parece trocar o Edge para um método de rolagem que não é do SO, e garante que ela considere as mudanças de perspectiva.
  • "O conteúdo da página ficou enorme!" Muitos navegadores são responsáveis pela escala ao decidir o tamanho do conteúdo da página, mas, infelizmente, o Chrome e o Safari não consideram a perspectiva. Então, se houver uma escala de 3x aplicada a um elemento, você poderá ver barras de rolagem e similares, mesmo que o elemento esteja a 1x depois do perspective foi aplicado. Para contornar esse problema, dimensionar elementos do canto inferior direito (com transform-origin: bottom right), o que funciona porque fará com que elementos muito grandes cresçam na "região negativa" (normalmente no canto superior esquerdo) da área rolável; rolável As regiões nunca permitem que você acesse ou role até o conteúdo da região negativa.

Conclusão

O paralaxe é um efeito divertido quando usado com cuidado. Como você pode ver, é possível para implementá-lo de uma forma que seja eficiente, com acoplamento de rolagem e entre navegadores. Como isso requer um pouco de distorção matemática e uma pequena quantidade para obter o efeito desejado, criamos uma pequena biblioteca auxiliar e um exemplo, que podem ser encontrados no nosso repositório de exemplos de elementos da interface do GitHub (link em inglês).

Curta o jogo e conte para nós como foi o processo.