Turbine as animações do seu webapp
Texto longo, leia o resumo:o Worklet de animação permite escrever animações imperativas que são executadas. com o frame rate nativo do dispositivo para ter uma suavidade extra e sem instabilidadeTM, tornam as animações mais resilientes contra a instabilidade da linha de execução principal e são vinculáveis rolar em vez de tempo. O Worklet de animação está no Chrome Canary (por trás do "Recursos experimentais da plataforma Web" ) e estamos planejando um teste de origem para o Chrome 71. Você pode começar a usar um aprimoramento progressivo hoje.
Outra API Animation?
Na verdade, não, é uma extensão do que já temos, e por um bom motivo. Vamos começar do início. Se você quiser animar qualquer elemento DOM na Web Hoje, você tem duas opções e meia: Transições CSS para transições simples de A para B, animações CSS para animações potencialmente cíclicas e mais complexas baseadas em tempo e a API Web Animations (WAAPI) para animações quase arbitrariamente complexas. A matriz de suporte da WAAPI parece sombria, mas está subindo. Até lá, há um polyfill (em inglês).
O que todos esses métodos têm em comum é que são sem estado e com base no tempo. Mas alguns dos efeitos que os desenvolvedores estão experimentando não são orientados por tempo nem sem estado. Por exemplo, o infame botão de rolagem de paralaxe, como orientado por rolagem. Atualmente, é surpreendente implementar um botão de rolagem de paralaxe com alto desempenho na Web.
E os sem estado? Pense na barra de endereço do Chrome no Android, por exemplo. Ao rolar para baixo, ele sai da visualização. Mas o que quando você rola para cima, ele volta, mesmo que você esteja na metade do caminho nessa página. A animação não depende só da posição de rolagem, mas também a direção de rolagem anterior. É com estado.
Outro problema é o estilo das barras de rolagem. Eles são notoriamente não estilosos (ou não tem muito estilo. E se eu quiser um gato nyan como barra de rolagem? Seja qual for a técnica escolhida, criar uma barra de rolagem personalizada desempenho nem fácil.
A questão é que todas essas coisas são estranhas e difíceis de
a implementar com eficiência. A maioria deles depende de eventos e/ou
requestAnimationFrame
, que pode manter você a 60 fps, mesmo quando a tela está
de rodar a 90, 120 fps ou mais e usam uma fração dos seus
precioso do frame da linha de execução principal.
O Worklet de animações amplia os recursos da pilha de animações da Web para tornar esses tipos de efeitos mais fáceis. Antes de começar, vamos garantir que estamos atualizados sobre os fundamentos das animações.
Uma introdução sobre animações e linhas do tempo
A WAAPI e o Worklet de animação fazem uso extensivo de linhas do tempo para permitir que você orquestrar animações e efeitos da maneira que você quiser. Esta seção é um rápida atualização ou introdução às linhas do tempo e como elas funcionam com animações.
Cada documento tem document.timeline
. Ela começa em 0 quando o documento é
criado e conta os milissegundos desde que o documento começou a existir. Todas as
as animações de um documento funcionam em relação a esse cronograma.
Para ir mais além, vamos conferir este snippet da WAAPI
const animation = new Animation(
new KeyframeEffect(
document.querySelector('#a'),
[
{
transform: 'translateX(0)',
},
{
transform: 'translateX(500px)',
},
{
transform: 'translateY(500px)',
},
],
{
delay: 3000,
duration: 2000,
iterations: 3,
}
),
document.timeline
);
animation.play();
Quando chamamos animation.play()
, a animação usa o currentTime
da linha do tempo.
como horário de início. Nossa animação tem um atraso de 3.000 ms, o que significa que
a animação começará (ou ficará "ativa") quando a linha do tempo atingir "startTime"
- 3000
. After that time, the animation engine will animate the given element from the first keyframe (
translateX(0)), through all intermediate keyframes (
translateX(500px)) all the way to the last keyframe (
translateY(500px)) in exactly 2000ms, as prescribed by the
duraçãooptions. Since we have a duration of 2000ms, we will reach the middle keyframe when the timeline's
currentTimeis
startTime + 3000 + 1000and the last keyframe at
startTime + 3000 + 2000`. A questão é que A linha do tempo controla onde estamos na animação.
Assim que a animação chegar ao último frame-chave, ela voltará para o primeiro
frame-chave e iniciar a próxima iteração da animação. Esse processo repete uma
no total de três vezes desde que definimos iterations: 3
. Se quiséssemos que a animação
nunca parar, escreveríamos iterations: Number.POSITIVE_INFINITY
. Estes são os
resultado do código
acima.
A WAAPI é incrivelmente eficiente e tem muitos outros recursos nessa API, como easing, deslocamento inicial, ponderação de frame-chave e comportamento de preenchimento que fariam escopo deste artigo. Para saber mais, recomendo a leitura deste artigo sobre animações CSS em truques do CSS (em inglês).
Como escrever um worklet de animação
Agora que definimos o conceito de cronogramas, podemos começar a analisar Worklet de animação e como ele permite que você mexa nas linhas do tempo! A animação A API Worklet não é apenas baseada na WAAPI, mas, no sentido da Web extensível, um primitivo de nível mais baixo que explica como a WAAPI funciona. Em termos de sintaxe, eles são incrivelmente semelhantes:
Objeto de animação | WAAPI |
---|---|
new WorkletAnimation( 'passthrough', new KeyframeEffect( document.querySelector('#a'), [ { transform: 'translateX(0)' }, { transform: 'translateX(500px)' } ], { duration: 2000, iterations: Number.POSITIVE_INFINITY } ), document.timeline ).play(); |
new Animation( new KeyframeEffect( document.querySelector('#a'), [ { transform: 'translateX(0)' }, { transform: 'translateX(500px)' } ], { duration: 2000, iterations: Number.POSITIVE_INFINITY } ), document.timeline ).play(); |
A diferença está no primeiro parâmetro, que é o nome do worklet que conduz esta animação.
Detecção de recursos
O Chrome é o primeiro navegador a fornecer esse recurso, por isso, certifique-se de que seu
código não espera apenas que AnimationWorklet
esteja lá. Portanto, antes de carregar
worklet, devemos detectar se o navegador do usuário tem suporte para
AnimationWorklet
com uma verificação simples:
if ('animationWorklet' in CSS) {
// AnimationWorklet is supported!
}
Como carregar um worklet
Os Worklets são um novo conceito introduzido pela força-tarefa Houdini para tornar muitos dos as novas APIs mais fáceis de criar e escalonar. Vamos abordar os detalhes dos worklets e mais tarde, mas, para simplificar, você pode pensar neles como baratos e linhas de execução leves (como workers) por enquanto.
Precisamos ter certeza de que carregamos um worklet com o nome “passthrough”, antes de declarar a animação:
// index.html
await CSS.animationWorklet.addModule('passthrough-aw.js');
// ... WorkletAnimation initialization from above ...
// passthrough-aw.js
registerAnimator(
'passthrough',
class {
animate(currentTime, effect) {
effect.localTime = currentTime;
}
}
);
O que está acontecendo aqui? Estamos registrando uma turma como animador usando o
chamada registerAnimator()
do AnimationWorklet, com o nome "passthrough".
É o mesmo nome que usamos no construtor WorkletAnimation()
acima. Quando o
registro for concluído, a promessa retornada por addModule()
será resolvida e
podemos começar a criar animações usando esse worklet.
O método animate()
da instância será chamado para cada frame
navegador deseja renderizar, passando o currentTime
da linha do tempo da animação
bem como o efeito que está sendo processado no momento. Temos apenas um
KeyframeEffect
, e estamos usando currentTime
para definir a
localTime
. Por isso, o animador é chamado de "passagem". Com esse código para
o worklet, a WAAPI e o AnimationWorklet acima se comportam
como você pode ver
demonstração.
Tempo
O parâmetro currentTime
do método animate()
é o currentTime
do
linha do tempo que transmitimos para o construtor WorkletAnimation()
. Na última
exemplo, acabamos de passar esse tempo para o efeito. Mas, como isso é
no código JavaScript. Assim, podemos distorcer o tempo 💫
function remap(minIn, maxIn, minOut, maxOut, v) {
return ((v - minIn) / (maxIn - minIn)) * (maxOut - minOut) + minOut;
}
registerAnimator(
'sin',
class {
animate(currentTime, effect) {
effect.localTime = remap(
-1,
1,
0,
2000,
Math.sin((currentTime * 2 * Math.PI) / 2000)
);
}
}
);
Vamos usar o Math.sin()
do currentTime
e remapear esse valor para
o intervalo [0; 2000], que é o período para o qual nosso efeito é definido. Agora
a animação fica muito diferente, sem ter
mudou os frames-chave ou as opções da animação. O código do worklet pode ser
arbitrariamente complexo e permite definir programaticamente quais efeitos são
tocado em que ordem e até que ponto.
Opções em vez de opções
Talvez você queira reutilizar um worklet e alterar seus números. Por esse motivo, o O construtor WorkletAnimation permite passar um objeto de opções para o worklet:
registerAnimator(
'factor',
class {
constructor(options = {}) {
this.factor = options.factor || 1;
}
animate(currentTime, effect) {
effect.localTime = currentTime * this.factor;
}
}
);
new WorkletAnimation(
'factor',
new KeyframeEffect(
document.querySelector('#b'),
[
/* ... same keyframes as before ... */
],
{
duration: 2000,
iterations: Number.POSITIVE_INFINITY,
}
),
document.timeline,
{factor: 0.5}
).play();
Neste exemplo, se ambas as animações são conduzidas com o mesmo código, mas com opções diferentes.
Mostre o estado da sua região.
Como mencionei antes, um dos principais problemas que o worklet de animação visa resolver é
animações com estado. Os worklets de animação podem conter o estado. No entanto, um
um dos principais recursos dos worklets é que eles podem ser migrados para um outro
ou até mesmo destruídos para economizar recursos, o que também destruiria
estado. Para evitar a perda de estado, o worklet de animação oferece um hook que
é chamado antes de um worklet ser destruído e pode ser usado para retornar um estado
objeto. Esse objeto será passado para o construtor quando o worklet for
ser recriadas. Na criação inicial, esse parâmetro será undefined
.
registerAnimator(
'randomspin',
class {
constructor(options = {}, state = {}) {
this.direction = state.direction || (Math.random() > 0.5 ? 1 : -1);
}
animate(currentTime, effect) {
// Some math to make sure that `localTime` is always > 0.
effect.localTime = 2000 + this.direction * (currentTime % 2000);
}
destroy() {
return {
direction: this.direction,
};
}
}
);
Sempre que você atualiza esta demonstração, tem uma amostra 50/50
probabilidade de em que direção o quadrado girará. Se o navegador desmontasse
e migrá-lo para um thread diferente, haveria outro
Math.random()
na criação, o que pode causar uma mudança repentina de
direção Para garantir que isso não aconteça, retornamos as animações
direção escolhida aleatoriamente como state e usá-la no construtor, se fornecido.
Interação com o contínuo espaço-tempo: ScrollTimeline
Como mostrado na seção anterior, o AnimationWorklet permite
define programaticamente como o avanço da linha do tempo afeta os efeitos da
animação. Mas, até agora, nossa linha do tempo sempre foi document.timeline
, que
monitora o tempo.
O ScrollTimeline
abre novas possibilidades e permite que você gere animações
com rolagem em vez de tempo. Vamos reutilizar nossa primeira
"passthrough" para este
demonstração:
new WorkletAnimation(
'passthrough',
new KeyframeEffect(
document.querySelector('#a'),
[
{
transform: 'translateX(0)',
},
{
transform: 'translateX(500px)',
},
],
{
duration: 2000,
fill: 'both',
}
),
new ScrollTimeline({
scrollSource: document.querySelector('main'),
orientation: 'vertical', // "horizontal" or "vertical".
timeRange: 2000,
})
).play();
Em vez de transmitir document.timeline
, estamos criando um novo ScrollTimeline
.
Você deve ter adivinhado, ScrollTimeline
não usa tempo, mas o
Posição de rolagem do scrollSource
para definir o currentTime
no worklet. Sendo
rolaram até o topo (ou para a esquerda) significa currentTime = 0
, enquanto
sendo rolada até a parte inferior (ou para a direita), define currentTime
como
timeRange
Se você rolar a caixa
demo, você pode
controlar a posição da caixa vermelha.
Se você criar um ScrollTimeline
com um elemento que não role, o
currentTime
da linha do tempo será NaN
. Especialmente com o design responsivo em
Não se esqueça de que é necessário estar sempre preparado para NaN
como seu currentTime
. Muitas vezes,
sensato usar como padrão um valor de 0.
Vincular animações à posição de rolagem é algo que é buscado há muito tempo, mas nunca foi alcançado nesse nível de fidelidade (exceto alternativas com CSS3D). O Worklet de animação permite que esses efeitos sejam implementados de maneira direta e com alto desempenho. Por exemplo: um efeito de rolagem paralaxe como esse demo mostra que Agora são necessárias apenas algumas linhas para definir uma animação de rolagem.
Configurações avançadas
Worklets
Os worklets são contextos de JavaScript com um escopo isolado e uma API muito pequena. superfície A pequena superfície de API permite uma otimização mais agressiva do navegador, especialmente em dispositivos mais simples. Além disso, os worklets não estão vinculados a um loop de evento específico, mas pode ser movido entre linhas de execução conforme necessário. Isso é especialmente importante para o AnimationWorklet.
NSync do compositor
Você deve saber que certas propriedades CSS são animadas rapidamente, enquanto outras não. Algumas propriedades só precisam de algum trabalho na GPU para serem animadas, enquanto outras forçar o navegador a alterar o layout de todo o documento.
No Chrome (como em muitos outros navegadores), temos um processo chamado compositor, de quem é essa função (e estou simplificando muito aqui) organizar camadas e texturas e, em seguida, utilizar a GPU para atualizar a tela o mais regularmente possível, idealmente o mais rápido possível para atualizar a tela (normalmente 60 Hz). Dependendo propriedades CSS estão sendo animadas, o navegador pode precisar apenas da o compositor faz todo o trabalho, enquanto outras propriedades precisam executar o layout, que é uma operação que só a linha de execução principal pode fazer. Dependendo das propriedades que você planejam animar, seu worklet de animação será vinculado à linha de execução ou em uma linha de execução separada sincronizada com o compositor.
Toque no pulso
Geralmente, há apenas um processo de compositor que é potencialmente compartilhado entre várias guias, já que a GPU é um recurso concorrido. Se o compositor receber de alguma forma bloqueado, todo o navegador desacelera até parar e para de responder entrada do usuário. Isso precisa ser evitado a todo custo. O que acontece se o worklet não pode fornecer os dados que o compositor precisa a tempo para que o frame seja renderizado?
Se isso acontecer, o worklet pode ser "desligado" (de acordo com a especificação). Ele fica para trás o compositor, que tem permissão para reutilizar os dados do último frame para manter o frame rate alto. Visualmente, isso pode parecer instabilidade, mas a diferença é que o navegador ainda responde à entrada do usuário.
Conclusão
Existem muitos aspectos no AnimationWorklet e os benefícios que ele traz para a Web. Os benefícios óbvios são mais controle sobre as animações e novas maneiras de gerar animações para dar um novo nível de fidelidade visual à Web. Mas as APIs o design também permite tornar seu aplicativo mais resiliente a instabilidade e, ao mesmo tempo, e tenha acesso a todas as novas vantagens ao mesmo tempo.
O Worklet de animação está na versão Canary, e estamos buscando um teste de origem com Chrome 71. Estamos ansiosos para ver suas novas e incríveis experiências na Web sobre o que podemos melhorar. Também há um polyfill (link em inglês) oferece a mesma API, mas não fornece o isolamento do desempenho.
Lembre-se de que as transições CSS e as animações CSS ainda são válidas e pode ser muito mais simples para animações básicas. Mas se você precisar elegante, o AnimationWorklet pode ajudar!