Worklet de animação da Houdini's

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 theduraçãooptions. Since we have a duration of 2000ms, we will reach the middle keyframe when the timeline'scurrentTimeisstartTime + 3000 + 1000and the last keyframe atstartTime + 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!