14

If I have a CSS keyframe animation like this

@keyframes flash-red {
  50% {
    background: #f00;
  }
}

#goflash.anm-flash {
  animation-name: flash-red;
  animation-duration: .5s;
  background: rgba(255, 0, 0, 0);
}

Then I can always trigger the animation like this:

var gf = document.querySelector("#goflash");
gf.classList.remove("anm-flash");
setTimeout(function() {
  gf.classList.add("anm-flash");
}, 50);

Is there any way to override the animation-duration/animation-timing-function to be dependent on JavaScript? I'd like to be able to say something like gf.animate("flash-red", "50%") to make the background of gf red, or gf.animate("flash-red", "75%") to make the background more like rgba(255, 0, 0, .5).

Ideally, the same technique would work for transitions. gf.transitionTo("new-class", "50%") would show the element as half way transitioned.

Obviously the flash-red is just an example—I'd like to be able to do this with any animation.

Dan
  • 2,321
  • 3
  • 27
  • 40
Aaron Yodaiken
  • 19,163
  • 32
  • 103
  • 184
  • No, you can't do that. –  Jun 20 '13 at 18:20
  • CSS3 animations (and transitions) don't appear to be flexible enough for this. You could create (or search for) a JavaScript function or jQuery plug-in to calculate and apply the affected styles, given 2 CSS rules (one for the start state and one for the end state) and a percentage value. In some cases, it might be easier to just hard code a set of CSS rules for the intermediate states. – Matt Coughlin Jun 25 '13 at 20:13
  • Edited my answer to include a working example, I think that this is what you wanted ? – vals Jun 29 '13 at 19:03

5 Answers5

12

With the built-in animation:

Unfortunately, no

The internals of the transition isn't exposed for JavaScript so you cannot tap into it to set or get the data. And this is for a purpose - if the data were exposed it would mean reduced efficiency as the JavaScript event queue had to be updated. As JS is single-threaded and the animation goes on a separate thread you'll would soon loose the benefit of it running in compiled code internally on a separate thread.

You can however make your own transitions. This involve calculation transitions yourselves.

This is not as complicated as it sounds like as you simply use an interpolation formula for what you want to animate:

current = source + (destination - source) * fraction;

For example, for color you can use it with the color component. Lets assume we have color objects with properties r, g, b:

var color1 = {r: 100, g: 200, b: 55};  //some random color
var color2 = {r: 0,   g: 100, b: 100};

var fraction = 0.5; //0-1

Here the current RGB would be:

r = color1.r + (color2.r - color1.r) * fraction;
g = color1.g + (color2.g - color1.g) * fraction;
b = color1.b + (color2.b - color1.b) * fraction;

For positions:

 var pos1x = 100;
 var pos1y = 100;
 var pos2x = 500;
 var pos2y = 250;

var fraction = 1; //0-1

posX = pos1x + (pos2x - pos1x) * fraction;
posY = pos1y + (pos2y - pos1y) * fraction;

And so forth.

By making wrapper functions you can easily calculate these and even put them in a loop to animate them.

Example function for setting transition between color 1 and color 2. Style can be ie. backgroundColor, color etc.:

function setColor(element, style, color1, color2, fraction) {

    var r = color1.r + (color2.r - color1.r) * fraction;
    var g = color1.g + (color2.g - color1.g) * fraction;
    var b = color1.b + (color2.b - color1.b) * fraction;

    element.style[style] = 'rgb(' + (r|0) + ',' + (g|0) + ',' + (b|0) + ')';
}

(the r|0 is simply cutting off the decimal part).

And for position, for example:

var pos1 = {x: 0, y: 0};
var pos2 = {x: 200, y: 0};

function setPosition(element, pos1, pos2, fraction) {

    var x = pos1.x + (pos2.x - pos1.x) * fraction;
    var y = pos1.y + (pos2.y - pos1.y) * fraction;

    element.style.left = x + 'px';
    element.style.top = y + 'px';
}

A simple demo (use Chrome or Aurora 23 to see sliders, slider comes in next version of FF 23).

Fiddle

enter image description here

Manually set transition at any point between source and destiny, or animate them.

  • Do you know any of the libraries that will allow you to specify two classes for properties? So something like `getMixed(el, 'classA', 'classB', .4)`? If so, that could work. – Aaron Yodaiken Jun 30 '13 at 15:53
  • @AaronYodaiken I dunno of a library which can do this and allow setting a fraction. You can perhaps utilize f.ex. window.getComputedStyle(elem, null).getPropertyValue('width') to extract values. The challenge with this is the destination style which must belong to an element that is part of the DOM to get a computed style so it will end up being hack-ish as you need to set the dest. class, get style, and then set it back to source. Also, the style returned from source class is current so you'll need to cache the styles from initial state. –  Jul 01 '13 at 00:46
4

say you have only one animation over your element gf, you can simply control it with animation-delay and animation-play-state:

gf.__proto__.animate = function(percent) {
  this.style["animation-play-state"] = "paused";
  this.style["animation-delay"] = (
    (parseFloat(this.style["animation-duration"] || 1) * -percent) + "s"
  );
};

and you can get the computed style as following:

window.getComputedStyle(gf).background

to step through at any speed:

(function animation(time) {
  gf.animate( ((time || 0) % desireSpeed ) / desireSpeed );
  requestAnimationFrame(animation);
})();

note: this will override animation-delay from css so you'll probably want to keep it in a vairable and add it as an offset in gf.__proto__.animate().

Kirby Wu
  • 81
  • 5
3

You can't do that as you want it.

Your only posibility is to change play-state after a given delay.

In your case, since the animation lasts 0.5 seconds, to get the animation at 50% you should set a timeout of 0.25 seconds and then set animation-play-state : paused.

Of course that won't be exactly at 50%, don't trust the precision of this method.

editing

Added demo for webkit:

fiddle

The HTML is trivial

<div id="goflash">TEST</div>
<input type="button" value="animate" onclick="animate()">

And the CSS easy

#goflash {
    width: 200px;
    height: 200px;
    left: 35px;
    top: 35px;
    position: absolute;
    background-color: red;
}
.anm-flash {
    -webkit-animation-name: flash;
    -webkit-animation-duration: 5s;
    -webkit-animation-timing-function: linear;
    -webkit-animation-iteration-count: 1;
}

@-webkit-keyframes flash {
    from {   -webkit-transform: rotate(0deg);
             background-color: red;        }
    50% {    -webkit-transform: rotate(120deg);
             background-color: yellow;}
    to {    -webkit-transform: rotate(360deg);
            background-color: red;
    }
}

And the javascript is an extension from what you supplied:

function animate () {
    var gf = document.querySelector("#goflash");
    gf.classList.remove("anm-flash");
    setTimeout(function() {
        gf.classList.add("anm-flash");
        gf.style.webkitAnimationPlayState = "running";
    }, 50);
    setTimeout(function() {
        gf.style.webkitAnimationPlayState = "paused";
    }, 2550);
}

You reset the class, after a small pause start the animation, and a calculated delay after the start, you stop it. Since the animation time was 5s,and the initial delay 50 ms, the second delay has to be (5000/2) + 50. Since you have set now the play state to paused, to de able to re-run the animation you have to set the state to running again.

Community
  • 1
  • 1
vals
  • 61,425
  • 11
  • 89
  • 138
1

Perhaps using CSS DOM to parse animation's intent (if that's even possible?) and then reconstructing everything in JavaScript.

But that's no mean feat!

I wonder if a CSS preprocessor would help constructing code like this. This is very much all in theory.

Alex KeySmith
  • 16,657
  • 11
  • 74
  • 152
0

Yes, You can just overide the duration or timing of an animation. Hope I understood what you want to do:

http://jsfiddle.net/SEHyW/

var gf = document.querySelector("#goflash"),
    animationDuration = '1s'

gf.classList.remove("anm-flash");
setTimeout(function() {
  gf.classList.add("anm-flash");
  gf.style["-webkit-animation-duration"] = animationDuration;
}, 1000);
Mircea
  • 11,373
  • 26
  • 64
  • 95