Use JavaScript in Order to Set the Current Percentage of a CSS Animation

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

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

Set the moment of an animation-duration

There is a "startDate" value in the JS-Part of the Codepen. By default it is set to new Date();

Just set your date here and you'll be good to go:

var defaults = {}
, one_second = 1000
, one_minute = one_second * 60
, one_hour = one_minute * 60
, one_day = one_hour * 24
, startDate = INSERT_YOUR_DATE_HERE
, face = document.getElementById('lazy');

This will set the time right (but only the timer).
To make the circles go the correct way it's a little bit more difficult but still achievable.

First of all you have to understand that the two oter circles are made with css keyframe animations. Meaning that currently, our javascript has no effect to them. They're just pre-programmed to run every 60 / 3600 seconds.

@keyframes spin1 {
0% {
transform: rotate(225deg);
}
50% {
transform: rotate(225deg);
}
100% {
transform: rotate(405deg);
}
}

@keyframes spin2 {
0% {
transform: rotate(225deg);
}
50% {
transform: rotate(405deg);
}
100% {
transform: rotate(405deg);
}
}

As you probably already know Stack Overflow is no code-writing service, but I'll still give you a lead:

  1. Calculate how far each circle has to be (in JavaScript). For example: if your start time is 10 seconds you should be 16.666% into the animation (of the minute circle).
  2. Set the keyframes percentage to make the circle start at the right position. As there is no way to manipulate the keyframes percentage from javascript, I'd suggest following this StackOverflow post: https://stackoverflow.com/a/29514053/8750569

I hope this helps you. Show some of your attempts if you're stuck somewhere and I'd be glad to help you more :)

CSS animations - the current left expressed in CSS

I've put together this JSFiddle to elaborate somewhat more on my previous comment.
It uses the translate property:

transform:translateX(200px);

To ensure that it works in as many browsers as possible and is also future-proof, use the vendor prefixes:

-webkit-transform:translateX(200px); // chrome and safari
-moz-transform:translateX(200px); // mozilla firefox
-ms-transform:translateX(200px); // internet explorer
-o-transform:translateX(200px); // opera
transform:translateX(200px); // regular

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>

Change current percentage of an infinite running CSS animation

Got it working with CSS and radio buttons (without keyframes). This is great because it runs without jQuery and natively on any device. But it's way harder to create it.

the final result

Get the state of a CSS animation in JavaScript and print it inside an element or on the console so as to later manipulate it

Odd, I don't know if it's a browser bug or what.. but it seems that you can not, in fact, access that property of the element, even if it's explicitly assigned in the css.

getComputedStyle does seem to work though.

var myVar = setInterval(myTimer, 2000);
var marqueeText = document.getElementById('marqueeText');function myTimer() { var computedStyle = window.getComputedStyle(marqueeText); printState(computedStyle.animationPlayState); if(computedStyle.animationPlayState == "running"){ //doSomething(); }
}
function stopInterval(){ clearInterval(myVar); marqueeText.style.animationPlayState = "paused"; var computedStyle = window.getComputedStyle(marqueeText) printState(computedStyle.animationPlayState);}
function printState(state){ var animationState = document.getElementById('animationState'); console.log(state); animationState.innerHTML = state;}
@keyframes marquee    {        0%   { transform: translate(0%, 0); }        100% { transform: translate(-200%, 0);}    }
p { color:#000; margin-left: 100%; padding-inline-end: 50px; display: inline-block; white-space: nowrap; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; font-size: 30pt; z-index: 10; animation: marquee 25s linear 0s 1; }
.animation{ width: 100%; background-color: darkblue; vertical-align: bottom; }
<div class='animationBackground'><p id="marqueeText">Scrolling Text Goes Here</p></div><div id="animationState">Animation State</div><button id='stop' type"button" onclick=stopInterval()>Stop Logging</button>

Chain/sequence animation in CSS

As others have suggested, use animation-delay to offset each element's animation.

In order to loop the entire group, multiply the animation duration by the number of elements and change your keyframes accordingly. Below, I've multiplied the animation duration by three and divided the keyframe percentages by three.

If you have a large number of elements or they are added dynamically, you may want to consider using JavaScript, as mentioned here.

a {  padding: 6px;  display: block;  width: 50px;  font-size: 17px;  margin: 10px auto;  border: 2px solid;  text-decoration: none;  box-shadow: 0 0 0 rgba(204, 169, 44, 0.4);  animation: pulse 6s infinite;}
.btn-100 { animation-delay: 0s;}.btn-500 { animation-delay: 2s;}.btn-1250 { animation-delay: 4s;}
@keyframes pulse { 0% { -moz-box-shadow: 0 0 0 0 rgba(204, 169, 44, 0.4); box-shadow: 0 0 0 0 rgba(204, 169, 44, 0.4); } 23.333% { -moz-box-shadow: 0 0 0 10px rgba(255, 208, 0, 0.2); box-shadow: 00 0 0 10px rgba(255, 208, 0, 0.2); } 33.333% { -moz-box-shadow: 0 0 0 0 rgba(204, 169, 44, 0); box-shadow: 0 0 0 0 rgba(204, 169, 44, 0); }}
<a class="btn-100" href="#">100</a><a class="btn-500" href="#">500</a><a class="btn-1250" href="#">1250</a>


Related Topics



Leave a reply



Submit