Get/Set Current @Keyframes Percentage/Change Keyframes

Get/set current @keyframes percentage/change keyframes

I achieved roughly what I wanted using pure javascript with my CSS3.

For my experiment to come up with a way to do these objectives, I created a basic CSS3 animation rotating a circle around a small circular path. My goal was to, when a button was clicked, change the origin of the animation to the new location

To achieve the first goal of getting the percentage value of the animation I simply approximated the current percentage using the following setInterval, which displays the approximated current percent. This runs every 40 milliseconds to correspond with the duration of the animation (milliseconds / 100)

var result = document.getElementById('result'), currentPercent = 0;
var showPercent = window.setInterval(function() {
if(currentPercent < 100)
{
currentPercent += 1;
}
else {
currentPercent = 0;
}
result.innerHTML = currentPercent;
}, 40);

Note on this solution:

  • It is not perfect due because the counter keeps running when another tab is clicked but the animation stops, so they become un-synchronized
  • It is also faulty when the button is clicked long after the last click. Evidently the setInterval runs a little bit longer than the animation, so they become less and less synced each iteration
  • I looked everywhere for a more perfect solution but have been unable to come up with one as of yet

To achieve the second goal of setting a new definition for an animation's % value it took a bit of a more complex solution. After pouring through dozens of articles and web pages (the important ones listed below), I managed to come up with the following solution:

var circle = document.getElementById('circle'), 
button = document.getElementById('button');
var result = document.getElementById('result'),
totalCurrentPercent = 0,
currentPercent = 0;
var showPercent = window.setInterval(function() {
if(currentPercent < 100)
{
currentPercent += 1;
}
else {
currentPercent = 0;
}
result.innerHTML = currentPercent;
}, 40);
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;
}
function change(anim) {
var keyframes = findKeyframesRule(anim),
length = keyframes.cssRules.length;
var keyframeString = [];
for(var i = 0; i < length; i ++)
{
keyframeString.push(keyframes[i].keyText);
}
var keys = keyframeString.map(function(str) {
return str.replace('%', '');
});
totalCurrentPercent += currentPercent;
if(totalCurrentPercent > 100)
{
totalCurrentPercent -= 100;
}
var closest = getClosest(keys);
var position = keys.indexOf(closest),
firstPercent = keys[position];
for(var i = 0, j = keyframeString.length; i < j; i ++)
{
keyframes.deleteRule(keyframeString[i]);
}
var multiplier = firstPercent * 3.6;
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); }");
circle.style.display = "inherit";
circle.style.webkitAnimationName = anim;
window.clearInterval(showPercent);
currentPercent = 0;
showPercent = self.setInterval(function() {
if(currentPercent < 100)
{
currentPercent += 1;
}
else {
currentPercent = 0;
}
result.innerHTML = currentPercent;
}, 40);
}
button.onclick = function() {
circle.style.webkitAnimationName = "none";
circle.style.display = "none";
setTimeout(function () {
change("rotate");
}, 0);
}
function getClosest(keyframe) {
var curr = keyframe[0];
var diff = Math.abs (totalCurrentPercent - curr);
for (var val = 0; val < keyframe.length; val++) {
var newdiff = Math.abs(totalCurrentPercent - keyframe[val]);
if (newdiff < diff) {
diff = newdiff;
curr = keyframe[val];
}
}
return curr;
}

Check out the experiment itself here including comments describing what each part of the javascript is doing

Notes on this solution:

  • I tried to make the change function as non-hard-coded as possible
  • It works alright for approximating the current @keyvalue percentage
  • The transition from one animation to the other is only as smooth as however close the nearest % value of the animation is to the current % of the animation, so adding more % definitions to the animation would make it even more smooth

In the process of trying to come up with a solution for the problem, I found these useful resources:

  • RussellUresti's answer in this SO post and corresponding example was quite influential and greatly aided my final solution
  • To get the closest value based on an input and array values, I used paxdiablo's method in this SO post (thank you)
  • This plugin, while I didn't use it myself, seemed to achieve a very similar (though seemingly not quite as customizable) effect in jQuery

---EDIT---

If you are just using CSS transitions or you can change your animation to just use transitions instead, you can use this simpler approach. You can pause the transition by copying the attributes changed by the transition, setting the attributes to those changed attributes, and then removing the class that animates it. Of course if you are using the same object to animate/pause you will need to animate the first click then pause it the next click. You can easily do the pure javascript equivalent as well

Note: the !important in the CSS changed attribute is necessary unless you have a more leveled selector for the animation class than the jQuery selector, aka something like div #defaultID.animationClass { as opposed to just #defaultID.animationClass {. Since #defaultID and #defaultID.animationClass are both one level, this example requires the !important

--Another Edit--

For more information on this topic, check out my post on CSS-Tricks

CSS animation keyframe percentage issue

The keyframe percentages relate to your animation duration. For example: with a 10 second animation, 10% is the 1 second mark, 50% the 5 second mark, and 100% the 10 second mark.

If your entire animation is 9 seconds long and you have 3 slides that all start with an offset of 3 seconds, then you'll want to make sure that your slides do their thing in the first 33.33% (3 seconds) of the key-frame animation and stay hidden for the remaining 66.66% (6 seconds).

Now in your case the slide transitions aren't instant, so you'll have to go slightly beyond 33.33% to ensure that your animations overlap nicely without a gap. The delay you were seeing are these gaps.

So with that in mind you can simplify them like so:

@keyframes slider {
/* Start left off screen */
0% {
transform: translateX(-100%);
}
/* Move to visible position within 8% of 9 seconds (less than a second). */
8% {
transform: translateX(0);
}
/* Stay until at least 3 second mark (33% of 9s). */
33.33% {
transform: translateX(0);
}
/* Move offscreen to the right while
the next slide is moving in.
Same duration as slide-in (8%), but starting at 33.33%,
so 33.33% + 8% = 41.33%.
*/
41.33% {
transform: translateX(100%);
}
/* Stay there until the end. */
100% {
transform: translateX(100%);
}
}

Here a snippet:

.wrapper{

position: relative;

height: 330px;

display: block;

overflow: hidden;

width:600px;

background:#f1f1f1;

}

.slide {

position: absolute;

top: 0;

left: 0;

height: 100%;

width: 100%;

animation: slider 9s cubic-bezier(0.5, 1, 0.5, 1) infinite;

color:#fff;

font-size:30px;

text-align:center;

padding-top:25%;

}

.slide:first-of-type {

animation-delay: -9s;

background:red;

}

.slide:nth-of-type(2) {

animation-delay: -6s;

background:blue;

}

.slide:last-of-type {

animation-delay: -3s;

background:black;

}

.wrapper{

position: relative;

height: 330px;

display: block;

overflow: hidden;

width:600px;

background:#f1f1f1;

}

.slide {

position: absolute;

top: 0;

left: 0;

height: 100%;

width: 100%;

animation: slider 9s cubic-bezier(0.5, 1, 0.5, 1) infinite;

color:#fff;

font-size:30px;

text-align:center;

padding-top:25%;

}

.slide:first-of-type {

animation-delay: -9s;

background:red;

}

.slide:nth-of-type(2) {

animation-delay: -6s;

background:blue;

}

.slide:last-of-type {

animation-delay: -3s;

background:black;

}

@keyframes slider {

/* Start left off screen */

0% {

transform: translateX(-100%);

}

/* Move to visible position within 8% of 9 seconds (less than a second). */

8% {

transform: translateX(0);

}

/* Stay until at least 3 second mark (33% of 9s). */

33.33% {

transform: translateX(0);

}

/* Move offscreen to the right while

the next slide is moving in.

Same duration as slide-in (8%), but starting at 33.33%,

so 33.33% + 8% = 41.33%.

*/

41.33% {

transform: translateX(100%);

}

/* Stay there until the end. */

100% {

transform: translateX(100%);

}

}
<div class="wrapper">

<div class="slide">

1

</div>

<div class="slide">

2

</div>

<div class="slide">

3

</div>

</div>

Use javascript in order to set the current percentage of a css animation

You can specify a negative animation-delay property.

Example: http://jsfiddle.net/vyhjt6mu/4/

In that example I've set animation-delay: -30s so the animation will start from the middle point


For this task you could set 24 different classes in CSS (one for each hour) like so

.h00 {
animation-delay: 0s;
-webkit-animation-delay: 0s;
}

.h01 {
animation-delay: -2.5s;
-webkit-animation-delay: -2.5s;
}

.h02 {
animation-delay: -5s;
-webkit-animation-delay: -5s;
}

...

.h22 {
animation-delay: -55s;
-webkit-animation-delay: -55s;
}

.h23 {
animation-delay: -57.5s;
-webkit-animation-delay: -57.5s;
}

where the difference of delay between each hour is 2.5 seconds (60s/24); then, via JS, get the current hour via getHours() method and apply the right class name to your element

Expressing CSS3 @keyframes using seconds instead of percentages

Don't forget you can run multiple animations on the same element, and that you can set their duration, delay and all other animation-... rules independently.

E.g, you can split all your keyframes to single-key @keyframes rules.

Then it's easy to control when they'll kick in and to chain them.

div {
width: 120px;
height: 120px;
background-color: violet;
animation-fill-mode: forwards;
animation-name: orange, yellow, green, cyan, blue, violet;
animation-delay: 0s, 3s, 6s, 9s, 12s, 15s, 18s;
animation-duration: 3s; /* same for all */
}

@keyframes orange {
to { background-color: orange; }
}
@keyframes yellow {
to { background-color: yellow; }
}
@keyframes green {
to { background-color: green; }
}
@keyframes cyan {
to { background-color: cyan; }
}
@keyframes blue {
to { background-color: blue; }
}
@keyframes violet {
to { background-color: violet; }
}
<div></div>

JavaScript animate key frames by percentage

You do it with the offset property. It must be between 0 and 1, so just convert your percentages by dividing by 100.

function animateTip() {
document.getElementById("x").animate(
[{
transform: "rotate(0deg)"
},
{
transform: "rotate(30deg)", offset: 0.3,
},
{
transform: "rotate(0deg)"
}
], {
duration: 3500,
iterations: 1
})
}

animateTip();
#x {
padding: 10px;
background: chartreuse;
display: inline-block;
width: 100px;
height: 100px;
}
<div id="x"></div>

How do I change @keyframes using JS?

I guess we are in the territory of CSS+JS = CJSSS thats lot of Ss to handle tbh. JS deals with Document object model and CSS deals with CSS object model. Browser object model deals with both of these.

That's being said JS have no interaction with CSSOM. What we see on screen is BOM taking the two and painting it on screen. It is when its painted aka DOM is represented Js is able to access and manipulate objects.

With above in mind when we change style values with JS e.g. element.style.height=100% it is happening after the fact widely known as computed value.

By computed it refers to what got painted on screen so element.height would return hight in pixels not the CSS rule from which it was painted that being percentage.

Thus when we are intending to change @keyframe we are intending to manipulate CSS rule before the fact not after the fact. thats problem no 1.

BOM only provides set number of CSS style properties to be manipulated through style function e.g. height color etc It does not include @keyframe in that set.

so we have to do some leg work to handle it after the fact.

root = document.documentElement;


setTimeout(function(){ root.style.setProperty('--change', 30 + "px"); }, 5000);
:root {
--change: 280px;
}
#progressBar{
background-color: #247BA0;
width: 150px;
padding: 10px;
border-radius: 5px;
animation: progressBar 3s ease;
animation-fill-mode:both;
text-align: center;
box-sizing: content-box;
}

@keyframes progressBar {
0% { width: 0; }
100% { width: var(--change); }
}
<div id="progressBar"></div>

Set Webkit Keyframes Values Using Javascript Variable

Okay, not what your actual code looks like, but you can't throw JavaScript variables into CSS, it won't recognize them.

Instead, you need to create the CSS rules through JavaScript and insert them into the CSSOM (CSS Object Model). This can be done a few ways -- you can either just create a keyframe animation and add it in, or you can overwrite an existing animation. For this sake of this question, I'm going to assume you want to continually overwrite an existing keyframe animation with different rotation values.

I've put together (and documented) a JSFiddle for you to take a look at: http://jsfiddle.net/russelluresti/RHhBz/2/

It starts off running a -10 -> 10 degree rotation animation, but then you can click the button to have it execute a rotation between random values (between -360 and 360 degrees).

This example was hacked together primarily from earlier experimentation done by Simon Hausmann. You can see the source here: http://www.gitorious.org/~simon/qtwebkit/simons-clone/blobs/ceaa977f87947290fa2d063861f90c820417827f/LayoutTests/animations/change-keyframes.html (for as long as that link works, gitorious is pretty bad at maintaining urls).

I've also taken the randomFromTo JavaScript function code from here: http://www.admixweb.com/2010/08/24/javascript-tip-get-a-random-number-between-two-integers/

I've added documentation to the parts of the Simon Hausmann script that I've taken from him (though I've slightly modified them). I've also used jQuery just to attach the click event to my button--all other script is written in native JavaScript.

I've tested this for Chrome 18 and Safari 5.1, and it seems to work fine in both browsers.

Hope this helps.



Related Topics



Leave a reply



Submit