Original Article: Animator.js: JavaScript animation library
Author: Bernie

Animator.js: biblioteca de animação Javascript

Veja animator.js no GitHub

Animator.js foi uma biblioteca que escrevi em 2006 para lidar com animação em páginas da web. Por um tempo esta foi além de seu tempo: Foi a primeira biblioteca a ter CSS morphing - a habilidade de suavizar a transição entre dois estilos definidos como classes CSS.

O mundo de Javascript teve muito progresso desde então. Animator.js agora foi incorporado na maioria dos frameworks Javascript, seja por diretamente importar o código (Animator.js é lançado sob uma licença BSD que permite às pessoas fazer isso) ou emprestando as técnicas. Em particular, animações CSS produzem melhores resultados e usam menos poder de processamento. Para uma biblioteca moderna que expõe animações CSS através de uma API semelhante, recomendo jQuery.Transit.

Estou mantendo essa página aqui como interesse histórico, porque está escrito num estilo de tutorial que será apropriado se você quer aprender como criar animação programática em qualquer linguagem.

Obrigado a Tim Stone, Kaspar Fischer, Clint Priest e outros desenvolvedores que contribuíram com feedback e recursos.

Histórico e motivação

Removi esta seção porque ela continha críticas de outras bibliotecas JavaScript que existiam em 2006, mas não mais. Também tomei a oportunidade para falar sobre o tópico de herança orientada ao objeto, um tópico que explorei em mais detalhes no artigo Herança é Ruim e Deve Ser Destruída - Bernie Sumption, 2010

Um maneira melhor de fazer animação

Um Animator é um objeto que gere uma série de números entre zero e um. Zero representa o início de uma animação, um sendo o final.

Confira isso:

// este objeto controla o progresso da animação
ex1 = new Animator()
// o sujeito do Animator define seu comportamento
ex1.addSubject(updateButton);
function updateButton(value) {
    var button = document.getElementById("ex1Button");
    button.innerHTML = "Progress: " + Math.round(value * 100) + "%";
}
// agora clique abaixo: cada clique ao botão chama ex1.toggle()

O Animator acima foi deixado com opções padrão, aqui está um exemplo que usa mais configurações:

ex2 = new Animador({
    duration: 1200,
    interval: 400,
    onComplete: function() {
        document.getElementById("ex2Target").innerHTML += "Bing! ";
    }
})
ex2.addSubject(updateButton);
function updateButton(value) {
    document.getElementById("ex2Target").innerHTML += " Badda ";
}

Animando estilos de elementos

Na maior parte do tempo você quer animar uma ou mais propriedades de estilo de um elemento. Essencialmente existem apenas três tipos de valores CSS - aqueles que escalam numericamente (como 10px), aqueles que escalam com um valor de cor RGB (como #RRGGBB), aqueles que não escalam (como bold / italic). Animator oferece três utilidades de classe para cada uma dessas propriedades, e entre elas, podem animar qualquer estilo CSS.

// animar margem esquerda de 0 a 100px
ex3 = new Animator().addSubject(
    new NumericalStyleSubject(
        "ex3Button",
        "margin-left",
        0,
        100));

// anima a cor do plano de fundo de branco para preto
ex4 = new Animator().addSubject(
    new ColorStyleSubject(
        "ex4Button",
        "background-color",
        "#FFFFFF",
        "#F4C"));

// animando ambos - perceba como as chamadsa ao addSubject() podem ser encadeadas:
ex5 = new Animator()
    .addSubject(
        new NumericalStyleSubject(
            "ex5Button",
            "margin-left",
            0,
            100))
    .addSubject(
        new ColorStyleSubject(
            "ex5Button",
            "background-color",
            "#FFFFFF",
            "#F4C"))
    .addSubject(
        new DiscreteStyleSubject(
            "ex5Button", "font-weight",
            "normal",
            "bold",
            0.5));
//por sinal, confira o último sujeito, que faz a font-weight
//mudar de normal para bold no meio da animação

Se você já usou moofx ou scriptaculous então você provavelmente está pensando que é bem verborrágico, e está certo. Animator tem uma função matadora que remove essa verborragia, mas antes de fazermos isso, aqui estão mais algumas coisas que podemos fazer:

Efeitos mais complexos

E se você tiver um número de elementos que quiser animar da mesma maneira? Neste caso, passe uma matriz de elementos no construtor do Subject Não há outra maneira de adicionar e remover elementos de um Subject depois que este já foi construído - se você quiser fazer isso, use um Subject para cada elemento e use addSubject() e removeSubject() no Animator.

// Aplicar o mesmo efeito em elementos diferentes é fácil
ex6 = new Animator().addSubject(
    new NumericalStyleSubject(
        ["dot1", "dot2", "dot3"],
        "margin-right",
        10,
        50));
     

Em exemplos anteriores, cada sujeito foi referido ao mesmo elemento. Esse não precisa ser o caso:

// aplicar efeitos diferentes em elementos diferentes é possível
ex7 = new Animator()
    .addSubject(
        new ColorStyleSubject(
            "ex5ButtonA",
            "background-color",
            "#FF9",
            "#9F9"))
    .addSubject(
        new NumericalStyleSubject(
            "ex5ButtonB",
            "padding",
            "5px",
            "15px"));

   

Opacidade ganha tratamento especial. Já que IE não suporta o estilo CSS de ‘opacidade’ padrão, NumericalStyleSubject irá convertê-lo em um filtro apropriado.

ex8 = new Animator().addSubject(
    new NumericalStyleSubject(
        "ex8Button",
        "opacity",
        1,
        0.25));

Controlando a animação

Quando você clica em um botão exemplo neste artigo, você está chamando toggle() em um objeto animator. Existem mais algumas funções de controle:

  • play() roda a animação do início ao fim
  • stop() para a animação na sua posição atual
  • reverse() roda a animação ao contrário
  • seekTo(pos) roda a animação da posição atual
  • seekdeTo(de, para)
play()
0%
stop()
reverse()
seekTo(0.5)
seekdeTo(0.25, 0.75)

O benefício de usar seekTo() é que esta irá evitar pulos repentinos no estado quando chamada no meio da animação:

essa div usa play() e inverte o mouseover e mouse out
essa div usa seekTo(1) e seekTo(0) no mouseover e mouseout

Transições

Uma transição é uma função que faz um estado (um número entre 0 e 1) e retorna outro número entre 0 e 1. Isso pode ser usado para simular aceleração, desaceleração e efeitos mais complexos. Você pode passar uma transição a um construtor de objeto Animator usando o apropriadamente nomeada propriedade transition.

O objeto Animator.tx oferece algumas transições prontas:
  • Animator.tx.easeInOut é a transição padrão, e cria um efeito suave
  • Animator.tx.linear mantém uma taxa de animação constante
  • Animator.tx.easeIn começa devagar, fica mais rápida
  • Animator.tx.strongEaseIn versão exagerada da acima
  • Animator.tx.easeOut começa rápido, fica mais devagar
  • Animator.tx.strongEaseOut versão exagerada da acima
  • Animator.tx.elastic passa um pouco do ponto de destino, então volta,
  • Animator.tx.veryElastic como a acima, com um passe extra
  • Animator.tx.bouncy atinge o ponto de destino e pica de volta
  • Animator.tx.veryBouncy como acima, mas com 2 picadas extra

Algumas vezes você precisará fazer ajustes finos nas transições acima. Se você olhar o código fonte onde o objeto Animator.tx foi criado, você verá que as funções acima são feitas por quatro funções de fábrica:

Animator.makeEaseIn()

// faz uma transição que gradualmente acelera. Passa em 1 para suavizar
// aceleração gravitacional, valores maiores para um efeito exagerado
ex9 = new Animator({
    transition: Animator.makeEaseIn(3),
    duration: 1000
});
ex9.addSubject(
    new NumericalStyleSubject(
        "ex9Button",
        "margin-left",
        0,
        200));

Animator.makeElastic()

// faz uma transição que, como um objeto com inércia
// atraído a um ponto de destino, passa do destino e depois retorna
ex10 = new Animator({
    transition: Animator.makeElastic(3),
    duration: 2000
});
ex10.addSubject(
    new NumericalStyleSubject(
        "ex10Button",
        "margin-left",
        0,
        200));

Animator.makeBounce()

// faz uma transição que, como uma bola caindo no chão, atinge,
// o destino e pula de volta
ex11 = new Animator({
    transition: Animator.makeBounce(3),
    duration: 2000
});
ex11.addSubject(
    new NumericalStyleSubject(
        "ex11Button",
        "margin-left",
        0,
        200));

Animator.makeADSR()

Um Attack Decay Sustain Release Envelope é uma técnica levantada da produção musical. É muito útil para animações que começam e terminam no mesmo valor.

// Esse exemplo mostra como um envelope ADSR se parece, mas é
// de outra forma inútil que os três primeiros argumentos sejam os pontos
// limites das quatros fases. O último é um nível de suspensão.
// Todos devem ser entre 0 e 1.
ex12 = new Animator({
    transition: Animator.makeADSR(0.25, 0.5, 0.75, 0.5),
    duration: 2000
});
ex12.addSubject(
    new NumericalStyleSubject(
        "ex12Button",
        'margin-left',
        0,
        400));

O uso prático de ADSR é de segurar uma animação em um certo estado por um tempo, como nesse exemplo de dissolver em amarelo

// Essa dissolução em amarelo é enfatizada por aguardar no amarelo total
// pela primeira metade da animação
ex13= new Animator({
    transition: Animator.makeADSR(0, 0, 0.5, 1),
    duration: 1500
});
ex13.addSubject(
    new ColorStyleSubject(
        "ex13Button",
        "background-color",
        "#FFFFFF",
        "#FFFF00"));

Funções customizadas

É claro, você pode escrever suas próprias funções para qualquer tipo de transição:

function setupEx14() {
    var wobbles = parseInt(prompt(
        "Enter a number of wobbles (try between 1 and 5)", ""));
    if (!wobbles) {
        alert("Sorry, I didn't understand that, have 2 wobbles.");
        wobbles = 2;
    }
    // faz umas loucuras trigonométicas. Eu nem sei
    // o que significa, só enlouqueci com as funções matemáticas
    // functions
    ex14Tx = function(pos) {
        return ((-Math.cos(pos*Math.PI*((1+(2*wobbles))*pos))/2) + 0.5);
    }
    ex14 = new Animator({
        transition: ex14Tx,
        duration: 2000
    });
    ex14.addSubject(
        new NumericalStyleSubject(
            "ex14Button",
            "margin-left",
            0,
            100));
    ex14.play();
};

 

A função matadora

Eu queria explicar como Animator funciona sob o capô antes de revelar esta função.

Como eu disse antes, é tudo um pouco verborrágico no momento, e a maioria dos códigos nos exemplos acima é apenas clichê. O que precisamos é algum tipo de linguagem que nos permita definir o estilo que queremos animar com um objeto. Oh, espere um pouco, já temos isso: CSS. Um estilo CSS contém toda a informação que precisamos definir um estado de animação:

ex15 = new Animator().addSubject(
    new CSSStyleSubject(
        "ex15Button",
        "width: 12em; background-color: rgb(256, 256, 256); font-style: normal",
        "width: 40em; background-color: #F39; font-style: italic"));
// perceba como você pode usar qualquer unidade, não somente 'px'.

CSSStyleSubject é um wrapper ao redor dos outros três Subjects. Este analisa conjuntos de regras CSS para cada declaração de propriedade, cria um NumericalStyleSubject se este se parece com um número, ou um ColorStyleSubject se esta se parece com uma cor, ou um DiscreteStyleSubject de qualquer forma. DiscreteStyleSubjects são criados com um limite de 0.5, em outras palavras o estilo muda de normal para itálico meio caminho através de animação.

Convenientemente, você também pode passar nomes de classe CSS ao invés de conjuntos de regras:

ex16 = new Animator().addSubject(new CSSStyleSubject(
    "ex16Button",
    "small white",
    "big blue bendy"));
// As classes pequenas, grandes, brancas, azuis e curvas são
// Definido na fonte desta página.

Quando você está criando animações de classes CSS, é fácil perder exatamente o que o objeto animator está fazendo. O método Animator.inspect() retorna uma linha que descreve o animator:

Isso é ótimo, mas ainda podemos remover mais complicações. Na maior parte dos tempo, o elemento que você quer animar já estará em seu estado inicial. Se este é o caso, você pode omitir um dos conjuntos de regras e o estado inicial será inferido do estilo atual do elemento. Isso usa getComputedStyle (ou Element.currentStyle No IE) para refletir o estilo do elemento após aplicar regras CSS na folha de estilos.

ex17 = new Animator().addSubject(
    new CSSStyleSubject(
        "ex17Button",
        "width: 300px; background-color: #F39"));

Finalmente, há um último pedaço de doce nesta síntaxe para facilitar a aplicação de efeitos. A função Animator.apply(element, style, options) é um wrapper que cria um único CSSStyleSubject. O segundo argumento é o estilo para dissolver para, e o terceiro é um conjunto opcional de parâmetros construtores para o objeto Animator. Se você quiser especificar em sua totalidade os estilos de e para, passe uma linha de dois itens como o segundo parâmetro.

ex18 = Animator.apply("ex18Button", "greenish"); // ta da!

Oh, e mais uma função...

Por demanda popular… muitas pessoas pediram por uma maneira simples de encadear várias animações juntas. AnimatorChain é um objeto que se comporta um pouco como um Animator, mas envolve outros objetos Animator e causa que estes rodem em sequência

var animators = [];
for (var i=0; i<3; i++) {
    animators[i] = Animator.apply("ex18blob-" + i), "blobEnd";
}
ex19 = new AnimatorChain(animators);
// O objeto AnimatorChain tem toggle(), play(), reverse()
// e seekTo(state) Funções como objetos Animator,
// Assim você pode usá-los freqüentemente onde o código espera um Animator

Bom, é isso. Aproveite usar o Animator.js.