I wanted to get a list of key-frames in Javascript, to be able to use directly with the Element.animate() API, so I found this question, and was able to improve the getAnimationList()
function to work with css @keyframes
, and here is a sample that works with animate.css :
var effects=getAnimationList(1);
function getAnimationList(offset){
// Returns a list of all animation keyframes in all style sheets
var ss =document.styleSheets, anims={}, step= offset?'offset':'step', rName, trOrig;
for(var s=0;s<ss.length;s++){// loop through all style sheets
if(ss[s].cssRules){// loop through all the rules
for(var r=0;r<ss[s].cssRules.length;r++){
var rule = ss[s].cssRules[r];
if( rule.type === window.CSSRule.KEYFRAMES_RULE || rule.type === window.CSSRule.WEBKIT_KEYFRAMES_RULE ){
if(rule.cssRules && !rule.cssText.startsWith('@-webkit-')){
anims[rule.name]??=[];
for(var k=0;k<rule.cssRules.length;k++){// add all keyframes
var steps=rule.cssRules[k].keyText.split(', ').map(v =>
parseFloat(v)/(String(v).includes("%")?100:1) ),
cssArr= Object.entries(rule.cssRules[k].style).filter( x =>
x[1]!="" && !x[0].startsWith('webkit') && isNaN(parseInt(x[0])) );
steps.forEach( sk => anims[rule.name].push( Object.fromEntries( [[step,sk]].concat(cssArr) ) ) );
}//get ordered frames and merge those with the same offset
anims[rule.name]= anims[rule.name].reduce( (a,b)=>{
var obj=a.filter(x=>x[step]==b[step])[0];
if(!obj){a.push(b);}
else{ for(var k in b){ obj[k]=b[k];} } //bottom rules overwrite top ones like in CSS
return a;
}, [] ).sort( (a,b) => a[step]-b[step] );
}
}else if( (rule.type === window.CSSRule.STYLE_RULE || rule.type === window.CSSRule.WEBKIT_STYLE_RULE) && rule.selectorText.startsWith('.animate__') ){
rName=rule.selectorText.split('.animate__')[1];
trOrig=rule.style.transformOrigin;//using the right transform-origin
if(anims[rName] && trOrig){ anims[rName].map(x=>x.transformOrigin =trOrig);}
}
}
}
} return anims;
}
function fillSelectOpt(sel,vals){
vals.forEach((x,i)=>{
var opt=document.createElement('option');
opt.value=x; opt.innerText=x; sel.append(opt);
});
}
fillSelectOpt(effSel,Object.keys(effects));
animate.onclick=function(e){
document.getElementById('box').animate( effects[effSel.value],
{duration:1200, fill:'backwards', delay:100, iterations:1} );
};
effSel.onchange= function(e){animate.onclick(e);}
#box{
padding: 20px; margin: 50px auto;
background: #c791d5; color: #f7edf9;
border: 2px solid#8512a3; border-radius: 5px;
width: 100px; height: 20px;
text-align: center; font: bold 20px Arial;
}
body{padding:70px 50px; text-align: center;}
<link href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css" rel="stylesheet" crossorigin="anonymous" />
Select effect : <select id="effSel"></select>
<button id="animate">Animate</button>
<div id="box">myElement</div>
The variable effects
holds all found animation names, stored as arrays of keyframe objects with their own css properties, ordered by ascending offset
(a requirement for Element.animate()
, unlike css).
Note : Because of CORS issues, you must have your css file in the same domain as your html file, or at least the server must deliver your css file with "*" origin (which is the case here with "cdnjs.cloudflare.com"), and include it like :
<link rel="stylesheet" href="https://..." crossorigin="anonymous" />
In the worst case, just copy/paste your css rules in the same html file, if you just want to get the css rules and use them with JS somewhere else.
You can adjust getAnimationList()
to work with any other css files, and the offset
parameter is just to avoid confusion with the same css keyword, because Element.animate()
also uses offset
(from 0 to 1) in its keyframes, which acts like the "xx%" in css rules (from == 0 == 0% and to == 1 == 100%).
Note 2 : I was using this form of the API :
element.animate({ offset:[vals], transform:[vals], opacity:[vals] }, opts );
where "vals" is a concatenation of css values in the same respective order, but because not all keyframes have all properties, this broke some animations (like "hinge"), so it's safer to stick with the "normal" form :
element.animate( [ {keyframe1}, {keyframe2}, ..], opts )
.