Controle de reprodução de animações da Web no Chrome 39

No início deste ano, o Chrome 36 lançou o método element.animate como parte da especificação Web Animations (link em inglês) mais ampla. Isso permite animações nativas eficientes e escritas de maneira imperativa, oferecendo aos desenvolvedores a opção de criar animações e transições com a abordagem mais adequada para eles.

Para relembrar rapidamente, veja como animar uma nuvem pela tela, com um callback quando terminar:

var player = cloud.animate([
    {transform: 'translateX(' + start + 'px)'},
    {transform: 'translateX(' + end + 'px)'}
], 5000);
player.onfinish = function() {
    console.info('Cloud moved across the screen!');
    startRaining(cloud);
};

Isso por si só é incrivelmente fácil e vale a pena considerar como parte de sua caixa de ferramentas ao criar animações ou transições de forma imperativa. No entanto, no Chrome 39, recursos de controle de mídia foram adicionados ao objeto AnimationPlayer retornado por element.animate. Antes, depois que uma animação era criada, você só podia chamar cancel() ou ouvir o evento de finalização.

Essas adições de reprodução abrem as possibilidades do que as animações da Web podem fazer, transformando animações em uma ferramenta de uso geral, em vez de ser prescritivo sobre transições, ou seja, animações predefinidas ou "fixas".

Pausar, voltar ou mudar a velocidade do vídeo

Vamos começar atualizando o exemplo acima para pausar a animação quando alguém clica na nuvem:

cloud.addEventListener('mousedown', function() {
    player.pause();
});

Também é possível modificar a propriedade playbackRate:

function changeWindSpeed() {
    player.playbackRate *= (Math.random() * 2.0);
}

Também é possível chamar o método reverse(), que normalmente é equivalente a inverter a playbackRate atual (multiplicar por -1). No entanto, há alguns casos especiais:

  • Se a mudança causada pelo método reverse() fizer com que a animação em execução termine efetivamente, a currentTime também será invertida.Por exemplo, se uma animação nova for invertida, toda a animação será reproduzida de trás para frente.

  • Se o player estiver pausado, a animação começará a ser reproduzida.

Como refinar o player

Um AnimationPlayer agora permite que o currentTime seja modificado enquanto uma animação está em execução. Normalmente, esse valor aumenta com o tempo (ou diminuirá, se o playbackRate for negativo). Isso pode permitir que a posição de uma animação seja controlada externamente, talvez por meio da interação do usuário. Isso é comumente chamado de refinamento.

Por exemplo, se sua página HTML representasse o céu, e você gostaria de um gesto de arrastar para alterar a posição de uma nuvem que está sendo reproduzida no momento, é possível adicionar alguns manipuladores ao documento:

var startEvent, startEventTime;
document.addEventListener('touchstart', function(event) {
    startEvent = event;
    startEventTime = players.currentTime;
    player.pause();
});
document.addEventListener('touchmove', function(event) {
    if (!startEvent) return;
    var delta = startEvent.touches[0].screenX -
        event.changedTouches[0].screenX;
    player.currentTime = startEventTime + delta;
});

Quando você arrasta o documento, o currentTime é alterado para refletir a distância do evento original. Você também pode retomar a exibição da animação quando o gesto terminar:

document.addEventListener('touchend', function(event) {
    startEvent = null;
    player.play();
});

Isso pode até ser combinado com o comportamento de inversão, dependendo de onde o mouse foi levantado da página (demonstração combinada).

Em vez de buscar uma AnimationPlayer em resposta a uma interação do usuário, o currentTime também pode ser usado para mostrar o progresso ou o status, por exemplo, para mostrar o status de um download.

O utilitário aqui é que um AnimationPlayer permite que um valor seja definido e que a implementação nativa subjacente cuide da visualização do progresso. No caso de download, a duração de uma animação pode ser definida como o tamanho total do download, e o currentTime pode ser definido como o tamanho do download no momento (demonstração).

Transições e gestos da interface

Há muito tempo, as plataformas móveis são o reino de gestos comuns: arrastar, deslizar, deslizar e afins. Esses gestos tendem a ter um tema comum: um componente de interface arrastável, como o “puxar para atualizar” de uma visualização em lista ou uma barra lateral sendo trabalhada do lado esquerdo da tela.

Com animações da Web, é muito fácil replicar um efeito semelhante aqui na Web, no computador ou no dispositivo móvel. Por exemplo, quando um gesto que controla currentTime é concluído:

var steps = [ /* animation steps */ ];
var duration = 1000;
var player = target.animate(steps, duration);
player.pause();
configureStartMoveListeners(player);

var setpoints = [0, 500, 1000];
document.addEventListener('touchend', function(event) {
    var srcTime = player.currentTime;
    var dstTime = findNearest(setpoints, srcTime);
    var driftDuration = dstTime - srcTime;

    if (!driftDuration) {
    runCallback(dstTime);
    return;
    }

    var driftPlayer = target.animate(steps, {
    duration: duration,
    iterationStart: Math.min(srcTime, dstTime) / duration,
    iterations: Math.abs(driftDuration) / duration,
    playbackRate: Math.sign(driftDuration)
    });
    driftPlayer.onfinish = function() { runCallback(dstTime); };
    player.currentTime = dstTime;
});

Isso cria uma animação adicional que executa um "desvio". Isso é reproduzido entre onde o gesto foi concluído até nosso alvo conhecido.

Isso funciona porque as animações têm uma prioridade com base na ordem de criação: nesse caso, driftPlayer tem precedência sobre o player. Quando a driftPlayer for concluída, ela e os efeitos vão desaparecer. No entanto, o horário final corresponderá ao currentTime do player subjacente, de modo que a IU permanecerá consistente.

Por fim, se você gosta de gatinhos, há um aplicativo da Web de demonstração que exibe esses gestos. É compatível com dispositivos móveis e usa o polyfill para compatibilidade com versões anteriores. Por isso, tente carregá-lo no seu dispositivo móvel.

Vá em frente e element.animate

O método element.animate é totalmente incrível agora, esteja você usando-o para animações simples ou aproveitando o AnimationPlayer retornado de outras maneiras.

Esses dois recursos também são totalmente compatíveis com outros navegadores modernos por meio de um polyfill leve. Esse polyfill também realiza detecção de recursos. Assim, à medida que os fornecedores de navegadores implementam a especificação, esse recurso ficará mais rápido e melhor com o tempo.

A especificação da Web Animations também continuará evoluindo. Se você quiser testar os próximos recursos, eles também estão disponíveis agora em um polyfill mais detalhado: web-animations-next.