Anda di halaman 1dari 20

Controlling CSS Animations and Transitions with JavaScript

By Chris Coyier

O seguinte é um post escrito por um convidado, Zach Saucier. Zach escreveu para mim dizendo-me
que, como um frequentador de fóruns de codificação como "Stack Overflow", ele vê muitas perguntas
sobre o controle de animações CSS com JavaScript, e provou mostrando-me um monte de links. Eu
estive para escrever sobre isso há muito tempo, então eu estou feliz em deixar Zach trabalhar nele e
escrever este tutorial abrangente.

Web designers às vezes acreditam que a animação em CSS é mais difícil do que em JavaScript.
Enquanto a animação CSS tem algumas limitações, na maioria das vezes é mais eficaz do que nós damos
crédito! Para não mencionar, tipicamente mais performance.

Juntamente com um toque de JavaScript, as animações CSS e transições são capazes de realizar
animações aceleradas por hardware e interações de forma mais eficiente do que a maioria das bibliotecas
JavaScript.

Vamos pular direto!

Nota rápida: animações e transições são diferentes

Transições em CSS são aplicadas a um elemento e especificam que, quando uma propriedade muda,
deve fazê-lo gradualmente ao longo de um período de tempo. As animações são diferentes. Quando
aplicadas, elas apenas executam e fazem o que lhes foi solicitado. Elas oferecem controle mais refinado,
como diferentes controles de paradas de animações.

Neste artigo vamos cobrir cada um deles separadamente.

Manipulando Transições CSS

Existem inúmeras perguntas sobre fóruns de codificação relacionados ao disparo e à pausa da transição
de um elemento. A solução é realmente muito simples usando JavaScript.
Para acionar a transição de um elemento, alterne um nome de classe nesse elemento que a ativa.
Para pausar a transição de um elemento, use "getComputedStyle" e "getPropertyValue" no ponto da
transição que você deseja pausá-lo. Em seguida, defina as propriedades CSS desses elementos igual aos
valores que você acabou de obter.

O seguinte é um exemplo dessa abordagem.

DOC HTML
<h3>Pure Javascript</h3>
<div class='box'></div>
<button class='toggleButton' value='play'>Play</button>
<h3>jQuery</h3>
<div class='box'></div>
<button class='toggleButton' value='play'>Play</button>
DOC CSS
.box {
margin: 30px;
height: 50px;
width: 50px;
background-color: blue;
}
.box.horizTranslate {
-webkit-transition: 3s;
-moz-transition: 3s;
-ms-transition: 3s;
-o-transition: 3s;
transition: 3s;
margin-left: 50% !important;
}

DOC JAVASCRIPT
var boxOne = document.getElementsByClassName('box')[0],
$boxTwo = $('.box:eq(1)');

document.getElementsByClassName('toggleButton')[0].onclick = function() {
if(this.innerHTML === 'Play')
{
this.innerHTML = 'Pause';
boxOne.classList.add('horizTranslate');
} else {
this.innerHTML = 'Play';
var computedStyle = window.getComputedStyle(boxOne),
marginLeft = computedStyle.getPropertyValue('margin-left');
boxOne.style.marginLeft = marginLeft;
boxOne.classList.remove('horizTranslate');
}
}

$('.toggleButton:eq(1)').on('click', function() {
if($(this).html() === 'Play')
{
$(this).html('Pause');
$boxTwo.addClass('horizTranslate');
} else {
$(this).html('Play');
var computedStyle = $boxTwo.css('margin-left');
$boxTwo.removeClass('horizTranslate');
$boxTwo.css('margin-left', computedStyle);
}
});
Esta mesma técnica pode ser usada de maneiras mais avançadas. O exemplo a seguir também aciona
uma transição alterando um nome de classe, mas desta vez uma variável mantém o controle da taxa de
zoom atual.

DOC HTML
<h3>Pure Javascript</h3>
<div class="zoomPic"></div>
<button class='zoom'>Zoom</button>
<button class='pause'>Pause</button>
<button class='zoomout'>Zoom Out</button>

<h3>jQuery</h3>
<div class='zoomPic'></div>
<button class='zoom'>Zoom</button>
<button class='pause'>Pause</button>
<button class='zoomout'>Zoom Out</button>

DOC CSS
.zoomPic {
margin: 30px;
width: 300px;
height: 180px;
background-color: blue;
background-image: url(http://placehold.it/1200x720);
background-repeat:no-repeat;
background-position:50% 50%;
background-size: 300px 180px;

-webkit-transition: all 2.5s ease-in-out;


-moz-transition: all 2.5s ease-in-out;
-ms-transition: all 2.5s ease-in-out;
-o-transition: all 2.5s ease-in-out;
transition: all 2.5s ease-in-out;
}
.zoomPic.zoom {
background-size: 1200px 720px !important;
}

DOC JAVASCRIPT
var zoomOne = document.getElementsByClassName('zoomPic')[0],
zoomOneBGSize = window.getComputedStyle(zoomOne).getPropertyValue('background-size'),
$zoomTwo = $('.zoomPic:eq(1)'),
zoomTwoBGSize = $zoomTwo.css('background-size');

document.getElementsByClassName('zoom')[0].onclick = function() {
if(!zoomOne.classList.contains('zoom'))
{
zoomOne.classList.add('zoom');
}
}
document.getElementsByClassName('pause')[0].onclick = function() {
var computedStyle = window.getComputedStyle(zoomOne),
backgroundSize = computedStyle.getPropertyValue('background-size');
zoomOne.style.backgroundSize = backgroundSize;
zoomOne.classList.remove('zoom');
}
document.getElementsByClassName('zoomout')[0].onclick = function() {
zoomOne.classList.remove('zoom');
zoomOne.style.backgroundSize = zoomOneBGSize;
}

$('.zoom:eq(1)').on('click', function() {
if(!$zoomTwo.hasClass('zoom'))
{
$zoomTwo.addClass('zoom');
}
});
$('.pause:eq(1)').on('click', function() {
var backgroundSize = $zoomTwo.css("background-size");
$zoomTwo.css({'background-size': backgroundSize});
$zoomTwo.removeClass('zoom');
});
$('.zoomout:eq(1)').on('click', function() {
$zoomTwo.removeClass('zoom');
$zoomTwo.css('background-size', zoomTwoBGSize);
});

Observe que estamos alterando o tamanho de fundo dessa vez. Existem muitas propriedades CSS
diferentes que podem ser transicionadas ou animadas, normalmente uma que tenha valores numéricos
ou de cor.

Usando Funções CSS de retorno de chamada (callback functions)

Alguns dos truques de JavaScript mais úteis e ainda pouco conhecidos para manipular transições de CSS
e animações são os eventos de DOM que disparam. Como: "animationend", "animationstart" e
"animationiteration" para animações e "transitionend" para transições. Você pode adivinhar o que eles
fazem. Esses eventos de animação são acionados quando a animação de um elemento termina, inicia ou
completa uma iteração, respectivamente.

Esses eventos precisam ser prefixados pelo fornecedor neste momento, então nesta demonstração,
usamos uma função desenvolvida por Craig Buckler chamada "PrefixedEvent", que tem os parâmetros
"element", "type" e "callback" para ajudar a tornar esses eventos independente de navegador.

A idéia nesta demo é ampliar o coração e parar a animação quando ela estiver pairando.
DOC HTML
<script src="http://code.jquery.com/jquery-2.0.0.js"></script>

<h3>Pure CSS</h3>
<div class='heart animated css'></div>

<h3>With Javascript</h3>
<div class='heart animated'></div>

DOC CSS
/* A maioria do CSS do desenho do coração veio das formas do CSS */
/* http://css-tricks.com/examples/ShapesOfCSS/ */
.heart {
position: relative;
width: 100px;
height: 90px;
margin: 30px;

-webkit-transform: scale(1);
-ms-transform: scale(1);
-o-transform: scale(1);
-moz-transform: scale(1);
transform: scale(1);
-webkit-transform-origin: center center;
-moz-transform-origin: center center;
-ms-transform-origin: center center;
-o-transform-origin: center center;
transition: all 1s;
}
.heart.css {
-webkit-animation-delay:1s;
-moz-animation-delay:1s;
-ms-animation-delay:1s;
-o-animation-delay:1s;
animation-dely:1s;
}
.heart.animated {
-webkit-animation: 1600ms pulsate infinite alternate ease-in-out;
-moz-animation: 1600ms pulsate infinite alternate ease-in-out;
-ms-animation: 1600ms pulsate infinite alternate ease-in-out;
-o-animation: 1600ms pulsate infinite alternate ease-in-out;
animation: 1600ms pulsate infinite alternate ease-in-out;
}
.heart:before,
.heart:after {
position: absolute;
content: "";
left: 50px;
top: 0;
width: 50px;
height: 80px;
background: red;
-moz-border-radius: 50px 50px 0 0;
border-radius: 50px 50px 0 0;
-webkit-transform: rotate(-45deg);
-moz-transform: rotate(-45deg);
-ms-transform: rotate(-45deg);
-o-transform: rotate(-45deg);
transform: rotate(-45deg);
-webkit-transform-origin: 0 100%;
-moz-transform-origin: 0 100%;
-ms-transform-origin: 0 100%;
-o-transform-origin: 0 100%;
transform-origin: 0 100%;
}
.heart:after {
left: 0;
-webkit-transform: rotate(45deg);
-moz-transform: rotate(45deg);
-ms-transform: rotate(45deg);
-o-transform: rotate(45deg);
transform: rotate(45deg);
-webkit-transform-origin: 100% 100%;
-moz-transform-origin: 100% 100%;
-ms-transform-origin: 100% 100%;
-o-transform-origin: 100% 100%;
transform-origin :100% 100%;
}
.heart.css:hover {
-webkit-transform: scale(2);
-moz-transform: scale(2);
-ms-transform: scale(2);
-o-transform: scale(2);
transform: scale(2);
-webkit-animation:'';
-moz-animation:none;
-ms-animation:'';
-o-animation:'';
animation:'';
}

@keyframes pulsate {
0% { transform: scale(1); }
50% { transform: scale(1.3); }
100% { transform: scale(1); }
}
@-webkit-keyframes pulsate {
0% { -webkit-transform: scale(1); }
50% { -webkit-transform: scale(1.3); }
100% { -webkit-transform: scale(1); }
}
@-moz-keyframes pulsate {
0% { -moz-transform: scale(1); }
50% { -moz-transform: scale(1.3); }
100% { -moz-transform: scale(1); }
}
@-ms-keyframes pulsate {
0% { -ms-transform: scale(1); }
50% { -ms-transform: scale(1.3); }
100% { -ms-transform: scale(1); }
}
@-o-keyframes pulsate {
0% { -o-transform: scale(1); }
50% { -o-transform: scale(1.3); }
100% { -o-transform: scale(1); }
}

DOC JAVASCRIPT
var heart = document.getElementsByClassName('heart')[1],
pfx = ["webkit", "moz", "MS", "o", ""],
hovered = false;

function AnimationListener() {
if(hovered)
{
heart.classList.remove('animated');
heart.style.webkitTransform = 'scale(2)';
heart.style.MozTransform = 'scale(2)';
heart.style.msTransform = 'scale(2)';
heart.style.OTransform = 'scale(2)';
heart.style.transform = 'scale(2)';
}
}

function TransitionListener() {
if(!hovered)
{
heart.classList.add('animated');
}
}
function PrefixedEvent(element, type, callback) {
for (var p = 0; p < pfx.length; p++) {
if (!pfx[p]) type = type.toLowerCase();
element.addEventListener(pfx[p]+type, callback, false);
}
}

PrefixedEvent(heart, "AnimationIteration", AnimationListener);

heart.onmouseover = function() {
hovered = true;
}
heart.onmouseout = function() {
setTimeout(function() { hovered = false; }, 500);
PrefixedEvent(heart, "TransitionEnd", TransitionListener);
heart.style.webkitTransform = 'scale(1)';
heart.style.MozTransform = 'scale(1)';
heart.style.msTransform = 'scale(1)';
heart.style.OTransform = 'scale(1)';
heart.style.transform = 'scale(1)';
}

A versão CSS pura é jumpy. A menos que você passe o mouse sobre ele no momento perfeito, ele saltará
para um estado particular antes de aumentar para o estado final pairado. A versão JavaScript é muito
mais suave. Remove o salto deixando a animação completa antes de aplicar o novo estado.

Manipulação de animações CSS

Como aprendemos, podemos ver elementos e reagir a eventos relacionados à animação:


"animationStart", "animationIteration" e "animationEnd". Mas o que acontece se você quiser mudar a
animação CSS no meio da animação? Isso requer um pouco de trapaça!

A propriedade animation-play-state

A propriedade "animation-play-state" do CSS é incrivelmente útil quando você simplesmente precisa


pausar uma animação e potencialmente continuar mais tarde. Você pode alterar esse CSS através de
JavaScript como este (memorize seus prefixos):

element.style.webkitAnimationPlayState = "paused";
element.style.webkitAnimationPlayState = "running";

No entanto, quando uma animação CSS é pausada usando "animation-play-state", o elemento é


impedido de transformar da mesma forma que é quando uma animação está sendo executada. Você não
pode pausá-la, transformá-la, retomá-la e esperar que ela funcione fluida do novo estado transformado.
Para fazer isso, temos que nos envolver um pouco mais.
Obter a porcentagem atual do valor-chave

Infelizmente, neste momento, não há nenhuma maneira de obter a exata atual "porcentagem concluída"
de uma animação de quadro-chave (KeyFrame) CSS. O melhor método para aproximá-lo é usando uma
função "setInterval" que itera 100 vezes durante a animação, que é essencialmente: a duração da
animação em ms / 100. Por exemplo, se a animação é de 4 segundos, então o "setInterval" precisa ser
executado a cada 40 Milissegundos (4000/100).

var showPercent = window.setInterval(function() {


if (currentPercent < 100) {
currentPercent += 1;
} else {
currentPercent = 0;
}
//Atualiza um div que exibe a porcentagem atual
result.innerHTML = currentPercent;
}, 40);

Esta abordagem está longe de ser ideal, porque a função realmente é executada com menos freqüência
do que a cada 40 milissegundos. Acho que configurá-lo para 39 milissegundos é mais preciso, mas
confiar nisso é má prática, como provavelmente varia de navegador para navegador e não é um ajuste
perfeito em todos os navegadores.

Obter os valores atuais da propriedade CSS da animação

Em um mundo perfeito, seríamos capazes de selecionar um elemento que está usando uma animação
CSS, remover essa animação e dar-lhe uma nova. Em seguida, começaria a nova animação, a partir de
seu estado atual. Nós não vivemos nesse mundo perfeito, então é um pouco mais complexo.

Abaixo temos uma demonstração para testar uma técnica de obtenção e alteração de uma animação CSS
"mid stream", por assim dizer. A animação move um elemento em um caminho circular com a posição
de início sendo no centro superior ( "doze horas", se preferir) Quando o botão é clicado, ele deve alterar
a posição inicial da animação para a localização atual do elemento . Ele viaja o mesmo caminho, só que
agora "começa" no local em que estava quando você apertou o botão. Essa mudança de origem e,
portanto, mudança de animação, é indicada pela alteração da cor do elemento para vermelho no
primeiro quadro-chave.

DOC HTML
<! - Observação: este exemplo só funciona em Navegadores Webkit ->
<div id="circle"></div>
<div id='button'>ChangeAnimation</div>
<div id='result'></div>

DOC CSS
@-webkit-keyframes rotate {
0% {
-webkit-transform:translate(100px, 100px) rotate(0deg) translate(-100px, -100px) rotate(0deg);
background-color:red;
}
13% {
-webkit-transform:translate(100px, 100px) rotate(45deg) translate(-100px, -100px) rotate(-
45deg);
}
25% {
-webkit-transform:translate(100px, 100px) rotate(90deg) translate(-100px, -100px) rotate(-90deg);
}
38% {
-webkit-transform:translate(100px, 100px) rotate(135deg) translate(-100px, -100px) rotate(0deg);
}
50% {
-webkit-transform:translate(100px, 100px) rotate(180deg) translate(-100px, -100px) rotate(-
180deg);
}
63% {
-webkit-transform:translate(100px, 100px) rotate(225deg) translate(-100px, -100px)
rotate(225deg);
}
75% {
-webkit-transform:translate(100px, 100px) rotate(270deg) translate(-100px, -100px) rotate(-
270deg);
}
88% {
-webkit-transform:translate(100px, 100px) rotate(315deg) translate(-100px, -100px)
rotate(315deg);
}
100% {
-webkit-transform:translate(100px, 100px) rotate(360deg) translate(-100px, -100px) rotate(-
360deg);
}
}
#circle {
height: 50px;
width: 50px;
border-radius:25px;
background-color: teal;
-webkit-animation-duration: 4s;
-webkit-animation-timing-function: linear;
-webkit-animation-name:"rotate";
-webkit-animation-iteration-count: infinite;
position:absolute;
left:30%;
top:20%;
}
#button {
width:130px;
background:teal;
}

DOC JAVASCRIPT

// NOTA: A mudança para vermelho significa o começo da animação

// Permite que elementos sejam acessados de forma limpa


var circle = document.getElementById('circle'),
button = document.getElementById('button');

// Obtém o elemento para mostrar a porcentagem atual


var result = document.getElementById('result'),
// Posição atual do círculo em torno de seu trajeto em porcentagens, em referência ao original
totalCurrentPercent = 0,
// Porcentagem de círculo ao redor de seu caminho em referência à origem mais recente
currentPercent = 0;

// Atualiza a alteração percentual da origem mais recente


var showPercent = window.setInterval(function() {
if(currentPercent < 100)
{
currentPercent += 1;
}
else {
currentPercent = 0;
}
result.innerHTML = currentPercent;
}, 39); // Executa a uma taxa baseada na duração da animação (milissegundos / 100)

// Verifica se a regra especificada está dentro de qualquer uma das folhas de estilo encontradas no
documento; Retorna o objeto de animação se assim for

function findKeyframesRule(rule) {
var ss = document.styleSheets;
for (var i = 0; i < ss.length; ++i) {
for (var j = 0; j < ss[i].cssRules.length; ++j) {
if (ss[i].cssRules[j].type == window.CSSRule.WEBKIT_KEYFRAMES_RULE &&
ss[i].cssRules[j].name == rule) { return ss[i].cssRules[j]; }
}
}
return null;
}

// Substitui a animação com base na porcentagem quando ativada e outras especificações codificadas
function change(anim) {
// Obtém o objeto de animação da animação especificada
var keyframes = findKeyframesRule(anim),
length = keyframes.cssRules.length;

// Faz uma matriz dos valores de porcentagem atual na animação


var keyframeString = [];
for(var i = 0; i < length; i ++)
{
keyframeString.push(keyframes[i].keyText);
}

// Remove todos os valores (%) da matriz para que a função "getClosest" possa executar cálculos
var keys = keyframeString.map(function(str) {
return str.replace('%', '');
});

// Updates the current position of the circle to


// be used in the calculations
totalCurrentPercent += currentPercent;
if(totalCurrentPercent > 100)
{
totalCurrentPercent -= 100;
}
// Variáveis explicativas se você ler a descrição de "getClosest"
var closest = getClosest(keys);

var position = keys.indexOf(closest),


firstPercent = keys[position];

// Remove as regras atuais da animação especificada


for(var i = 0, j = keyframeString.length; i < j; i ++)
{
keyframes.deleteRule(keyframeString[i]);
}

// Ativa o percentual quando ativado no grau correspondente de um círculo


var multiplier = firstPercent * 3.6;

// Essencialmente, isso cria as regras para definir uma nova origem para o caminho com base no
percentual aproximado da animação quando ativado e aumenta o diâmetro do novo caminho circular
keyframes.insertRule("0% { -webkit-transform: translate(100px,100px) rotate(" + (multiplier + 0) +
"deg) translate(-100px,-100px) rotate(" + (multiplier + 0) + "deg); background-color:red; }");
keyframes.insertRule("13% { -webkit-transform: translate(100px,100px) rotate(" + (multiplier + 45) +
"deg) translate(-100px,-100px) rotate(" + (multiplier + 45) + "deg); }");
keyframes.insertRule("25% { -webkit-transform: translate(100px,100px) rotate(" + (multiplier + 90) +
"deg) translate(-100px,-100px) rotate(" + (multiplier + 90) + "deg); }");
keyframes.insertRule("38% { -webkit-transform: translate(100px,100px) rotate(" + (multiplier + 135)
+ "deg) translate(-100px,-100px) rotate(" + (multiplier + 135) + "deg); }");
keyframes.insertRule("50% { -webkit-transform: translate(100px,100px) rotate(" + (multiplier + 180)
+ "deg) translate(-100px,-100px) rotate(" + (multiplier + 180) + "deg); }");
keyframes.insertRule("63% { -webkit-transform: translate(100px,100px) rotate(" + (multiplier + 225)
+ "deg) translate(-100px,-100px) rotate(" + (multiplier + 225) + "deg); }");
keyframes.insertRule("75% { -webkit-transform: translate(100px,100px) rotate(" + (multiplier + 270)
+ "deg) translate(-100px,-100px) rotate(" + (multiplier + 270) + "deg); }");
keyframes.insertRule("88% { -webkit-transform: translate(100px,100px) rotate(" + (multiplier + 315)
+ "deg) translate(-100px,-100px) rotate(" + (multiplier + 315) + "deg); }");
keyframes.insertRule("100% { -webkit-transform: translate(100px,100px) rotate(" + (multiplier + 360)
+ "deg) translate(-100px,-100px) rotate(" + (multiplier + 360) + "deg); }");

// Mostra o círculo novamente


circle.style.display = "inherit";
// Define a animação para as novas regras especificadas
circle.style.webkitAnimationName = anim;

// Redefine o contador percentual de porcentagem de animação


window.clearInterval(showPercent);
currentPercent = 0;
showPercent = self.setInterval(function() {
if(currentPercent < 100)
{
currentPercent += 1;
}
else {
currentPercent = 0;
}
result.innerHTML = currentPercent;
}, 39);
}

// Liga a função de alteração à função do evento "onclick" do botão


button.onclick = function() {
// Remove a animação para que uma nova possa ser definida
circle.style.webkitAnimationName = "none";
// Oculta temporariamente o círculo
circle.style.display = "none";
// Inicializa a função de alteração
setTimeout(function () {
change("rotate");
}, 0);
}

// Obtém o valor de (%) mais próximo da animação com base na (%) aproximada encontrada abaixo
function getClosest(keyframe) {
var curr = keyframe[0];
var diff = Math.abs (totalCurrentPercent - curr);
for (var val = 0, j = keyframe.length; val < j; val++) {
var newdiff = Math.abs(totalCurrentPercent - keyframe[val]);
if (newdiff < diff ) {
diff = newdiff;
curr = keyframe[val];
}
}
return curr;
}

Nós vamos ter que cavar na própria folha de estilos para encontrar a animação original. Você pode
acessar as folhas de estilo associadas a uma página usando "document.styleSheets" e iterar através dela
usando um "loop for". O exemplo seguinte é sobre como você pode usar JavaScript para localizar valores
de uma determinada animação em um objeto "CSSKeyFrameRules":

function findKeyframesRule(rule) {
var ss = document.styleSheets;
for (var i = 0; i < ss.length; ++i) {
for (var j = 0; j < ss[i].cssRules.length; ++j) {
if (ss[i].cssRules[j].type == window.CSSRule.WEBKIT_KEYFRAMES_RULE &&
ss[i].cssRules[j].name == rule) {
return ss[i].cssRules[j]; }
}
}
return null;
}

Uma vez que chamamos a função acima (por exemplo, var keyframes = findKeyframesRule (anim)),
você pode obter o comprimento da animação do objeto (o número total de quantos quadros-chave há
nessa animação) usando "keyframes.cssRules.length". Então precisamos tirar a "%" de cada um dos
quadros-chave para que eles sejam apenas números e JavaScript pode usá-los como números. Para fazer
isso, usamos o seguinte exemplo, que usa o método ".map" do JavaScript.

// Faz uma matriz dos valores de porcentagem atual na animação


var keyframeString = [];
for(var i = 0; i < length; i ++)
{
keyframeString.push(keyframes[i].keyText);
}

// Remove todos os valores (%) da matriz para que a função "getClosest" possa executar cálculos
var keys = keyframeString.map(function(str) {
return str.replace('%', '');
});
Neste ponto, as chaves serão uma matriz de todos os quadros-chave da animação em formato numérico.

Mudando a animação real (finalmente!)

No caso de nossa demonstração de animação circular, precisamos de duas variáveis: uma para rastrear
quantos graus o círculo havia percorrido desde sua localização de início mais recente e outra para
rastrear quantos graus havia percorrido desde o local de início original. Podemos mudar a primeira
variável usando nossa função "setInterval" (usando o tempo decorrido em graus em um círculo). Então
podemos usar o seguinte código para atualizar a segunda variável quando o botão é clicado.

totalCurrentPercent += currentPercent;
// Uma vez que é em porcentagem não deve nunca ser mais de 100
if (totalCurrentPercent > 100) {
totalCurrentPercent -= 100;
}

Então, podemos usar a seguinte função para descobrir qual quadro-chave da animação está mais
próximo da porcentagem atual total, com base na matriz de possíveis percentuais de quadros-chave
obtidos acima.

function getClosest(keyframe) {
// "curr" representa o quadro-chave atual
var curr = keyframe[0];
var diff = Math.abs (totalCurrentPercent - curr);
for (var val = 0, j = keyframe.length; val < j; val++) {
var newdiff = Math.abs(totalCurrentPercent - keyframe[val]);
// Se a diferença entre a porcentagem atual e o quadro-chave iterado for menor, leva a nova diferença
(newdiff ) e o quadro-chave
if (newdiff < diff ) {
diff = newdiff;
curr = keyframe[val];
}
}
return curr;
}

Para obter o primeiro valor de quadro-chave da nova animação para uso nos cálculos posteriores,
podemos usar o método ".indexOf" do JavaScript. Em seguida, excluímos os quadros-chave originais
para que possamos recriar novos.

for (var i = 0, j = keyframeString.length; i < j; i ++) {


keyframes.deleteRule(keyframeString[i]);
}

Em seguida, precisamos mudar a (%) em um graus do círculo. Podemos fazer isso simplesmente
multiplicando a nova primeira porcentagem por 3,6 (porque 10 0 * 3,6 = 360).
Finalmente, criamos as novas regras com base nas variáveis obtidas acima. A diferença de 45 graus entre
cada regra é porque temos 8 quadros-chave diferentes que circundam o círculo. 360 (graus em um
círculo) dividido por 8 é 45.

keyframes.insertRule("0% {
-webkit-transform: translate(100px, 100px) rotate(" + (multiplier + 0) + "deg)
translate(-100px, -100px) rotate(" + (multiplier + 0) + "deg);
background-color:red;
}");
keyframes.insertRule("13% {
-webkit-transform: translate(100px, 100px) rotate(" + (multiplier + 45) + "deg)
translate(-100px, -100px) rotate(" + (multiplier + 45) + "deg);
}");
...continued...

Em seguida, redefinimos a porcentagem atual "setInterval" para que ela possa ser executada novamente.
Observe que o acima é WebKit prefixado. Para torná-lo mais compatível com cross-browser você
poderia fazer algum algoritmo de busca (sniffing UA) para adivinhar quais prefixos seriam necessários:

// Obtém o prefixo do navegador


var browserPrefix;
navigator.sayswho= (function(){
var N = navigator.appName, ua = navigator.userAgent, tem;
var M = ua.match(/(opera|chrome|safari|firefox|msie)\/?\s*(\.?\d+(\.\d+)*)/i);
if(M && (tem = ua.match(/version\/([\.\d]+)/i))!= null) M[2] = tem[1];
M = M? [M[1], M[2]]: [N, navigator.appVersion,'-?'];
M = M[0];
if(M == "Chrome") { browserPrefix = "webkit"; }
if(M == "Firefox") { browserPrefix = "moz"; }
if(M == "Safari") { browserPrefix = "webkit"; }
if(M == "MSIE") { browserPrefix = "ms"; }
})();

Transformando animações em transições

Como vimos, manipular transições CSS pode ser simplificado usando JavaScript. Se você não acabar
recebendo os resultados desejados com animações CSS, você pode tentar torná-la uma transição e
trabalhar com ela dessa forma. Elas têm a mesma dificuldade de codificar, mas podem ser mais
facilmente definidas e editadas.

O maior problema em transformar animações CSS em transições é quando transformamos a animação-


iteração no comando de transição equivalente. Transição não tem equivalente direto, que é por isso que
elas são coisas diferentes.

Relacionando isso com nossa demo de rotação, um pequeno truque é multiplicar tanto a transição-
duração e a rotação por "x". Então você precisa ter / aplicar uma classe para acionar a animação, porque
se você aplicar as propriedades alteradas diretamente para o elemento, bem, não haverá muito de uma
transição a ser visto. Para iniciar a transição (animação falsa), você aplica a classe ao elemento.

Em nosso exemplo, fazemos isso no carregamento da página:

DOC HTML
<h3>Mas ... Qual é a animação?</h3>
<div class='rotator animation'></div>

<h3>É esta aqui?</h3>


<div class='rotator transition'></div>

DOC CSS
.rotator {
width: 100px;
height: 100px;
margin: 20px;
background-color:teal;
}
.transition {
-webkit-transition: all 1000s linear;
-moz-transition: all 1000s linear;
-ms-transition: all 1000s linear;
-o-transition: all 1000s linear;
transition: all 1000s linear;
}
.translateAnimationClass {
-webkit-transform: rotateX(43200deg) rotateY(14400deg);
-moz-transform: rotateX(43200deg) rotateY(14400deg);
-ms-transform: rotateX(43200deg) rotateY(14400deg);
-o-transform: rotateX(43200deg) rotateY(14400deg);
transform: rotateX(43200deg) rotateY(14400deg);
}
.animation {
-webkit-animation: rotator 25s linear infinite;
-moz-animation: rotator 25s linear infinite;
-ms-animation: rotator 25s linear infinite;
-o-animation: rotator 25s linear infinite;
animation: rotator 25s linear infinite;
}

@-webkit-keyframes rotator {
0% { -webkit-transform: rotateX(0deg) rotateY(0deg); }
100% { -webkit-transform: rotateX(1080deg) rotateY(360deg); }
}
@-moz-keyframes rotator {
0% { -moz-transform: rotateX(0deg) rotateY(0deg); }
100% { -moz-transform: rotateX(1080deg) rotateY(360deg); }
}
@-ms-keyframes rotator {
0% { -ms-transform: rotateX(0deg) rotateY(0deg); }
100% { -ms-transform: rotateX(1080deg) rotateY(360deg); }
}
@-o-keyframes rotator {
0% { -o-transform: rotateX(0deg) rotateY(0deg); }
100% { -o-transform: rotateX(1080deg) rotateY(360deg); }
}
@keyframes rotator {
0% { transform: rotateX(0deg) rotateY(0deg); }
100% { transform: rotateX(1080deg) rotateY(360deg); }
}

DOC JAVASCRIPT
window.onload = function() { document.getElementsByClassName('transition')
[0].classList.add('translateAnimationClass'); }

Manipulação de matrizes CSS

Manipular animações CSS também pode ser feito usando um "CSSMatrix". Por exemplo:

var translated3D =
new WebKitCSSMatrix(window.getComputedStyle(elem, null).webkitTransform);

Use sua cabeça

Antes de começar a codificar, pensar e planejar como uma transição ou animação deve ser executada é a
melhor maneira de minimizar seus problemas e obter o efeito desejado. As técnicas e os truques
descritos neste artigo podem não ser sempre a melhor maneira de criar a animação que o seu projeto
solicita.

Aqui está um pequeno exemplo de onde ficar inteligente com HTML e CSS sozinho pode resolver um
problema onde você pode ter pensado em ir para JavaScript.

Digamos que queremos um gráfico para girar continuamente e, em seguida, mudar de direção de
rotação quando o mouse passar sobre ele. Aprendendo o que foi abordado neste artigo, você pode
querer entrar e usar um evento "animationIteration" para alterar a animação. No entanto, uma solução
mais eficiente e de melhor desempenho pode ser encontrada usando CSS e um elemento de recipiente
adicionado.

O truque seria fazer com que a espiral gire em velocidade "x" em uma direção e, quando o mouse passar
sobre ela, fazer o elemento pai girar a uma velocidade "2x" na direção oposta (começando na mesma
posição). As duas rotações que trabalham uma contra a outra criam um efeito líquido da espiral girando
na direcção oposta.
DOC HTML
<!-- NOTA: No Chrome existe uma breve falha durante a transição após a primeira -->
<div class='spiralContainer'><div class='spiral'></div></div>

DOC CSS
.spiralContainer {
border-radius: 50%;
width:200px;
height:200px;
overflow:hidden;
margin:20px;

-webkit-animation: spin 3s linear 0s infinite reverse;


-moz-animation: spin 3s linear 0s infinite reverse;
-ms-animation: spin 3s linear 0s infinite reverse;
-o-animation: spin 3s linear 0s infinite reverse;
animation: spin 3s linear 0s infinite reverse;

-webkit-animation-play-state: paused;
-moz-animation-play-state: paused;
-ms-animation-play-state: paused;
-o-animation-play-state: paused;
animation-play-state: paused;
}
.spiral {
width:200px;
height:200px;
border-radius: 50%;
background-image: url(http://farm7.staticflickr.com/6115/6319704650_0a2bd0dcef_o.jpg);
background-repeat:no-repeat;
background-position:50% 50%;

-webkit-animation: spin 6s linear 0s infinite normal;


-moz-animation: spin 6s linear 0s infinite normal;
-ms-animation: spin 6s linear 0s infinite normal;
-o-animation: spin 6s linear 0s infinite normal;
animation: spin 6s linear 0s infinite normal;
}
.spiralContainer:hover {
-webkit-animation-play-state: running;
-moz-animation-play-state: running;
-ms-animation-play-state: running;
-o-animation-play-state: running;
animation-play-state: running;
}
@keyframes spin {
0% { transform: rotate(360deg); }
100% { transform: rotate(0deg); }
}
@-webkit-keyframes spin {
0% {-webkit-transform: rotate(360deg); }
100% { -webkit-transform: rotate(0deg); }
}
@-ms-keyframes spin {
0% {-ms-transform: rotate(360deg); }
100% { -ms-transform: rotate(0deg); }
}
@-moz-keyframes spin {
0% { -moz-transform: rotate(360deg); }
100% { -moz-transform: rotate(0deg); }
}
@-o-keyframes spin {
0% { -o-transform: rotate(360deg); }
100% { -o-transform: rotate(0deg); }
}

Anda mungkin juga menyukai