How to dynamically create '@-Keyframe' CSS animations?

JavascriptJqueryHtmlCss

Javascript Problem Overview


I have a requirement to rotate a div and stop at a particular position ( The value will be received from the server).

I tried native JS to rotate and stop but it is eating up my CPU big time.

I can rotate with CSS animation but I need to create a class which will dynamically describe where to stop the animation. Something like

@-webkit-keyframes spinIt {
    100% {
        -webkit-transform: rotate(A_DYNAMIC_VALUE);
    }
}
@-moz-keyframes spinIt {
    100% {
        -webkit-transform: rotate(A_DYNAMIC_VALUE);
    }
}

Here is one reference

http://jsfiddle.net/bVkwH/8/

Thanks in Advance

Javascript Solutions


Solution 1 - Javascript

You can insert stylesheet rules dynamically to override previous styles in the head. This helps avoid adding yet another library for a single task.

var style = document.createElement('style');
style.type = 'text/css';
var keyFrames = '\
@-webkit-keyframes spinIt {\
    100% {\
        -webkit-transform: rotate(A_DYNAMIC_VALUE);\
    }\
}\
@-moz-keyframes spinIt {\
    100% {\
        -webkit-transform: rotate(A_DYNAMIC_VALUE);\
    }\
}';
style.innerHTML = keyFrames.replace(/A_DYNAMIC_VALUE/g, "180deg");
document.getElementsByTagName('head')[0].appendChild(style);

Solution 2 - Javascript

well i don't think it is easy to create dynamic @keyframes they are inflexible because they must be hard-coded.

Transitions are a little easier to work with, as they can gracefully respond to any CSS changes performed by JavaScript.

However, the complexity that CSS transitions can give you is pretty limited — an animation with multiple steps is difficult to achieve.

This is a problem that CSS @keyframe animations are meant to solve, but they don’t offer the level of dynamic responsiveness that transitions do.

but these links might help you

Link1 : a tool that generates a @-webkit-keyframe animation with many tiny steps. This opens the door to an unlimited selection of easing formula.

Link2 it will be a great help for you to take it as a base as it provides a UI to create animations and exports it to CSS code.

I guess this solution will definitely work for you. Its is used for dynamic keyframes

Solution 3 - Javascript

Let me share an updated (2019) answer to this.

Yes, it's possible without Javascript using CSS Variables (supported by all modern browsers).

--lightScaleStart: 0.8;

.light {
	animation: grow 2s alternate infinite ease-in-out;
}

.light.yellow {
	--lightScaleEnd: 1.1;
}

.light.red {
	--lightScaleEnd: 1.2;
}

@keyframes grow {
  from {
	transform: scale(var(--lightScaleStart));
  }
  to {
	transform: scale(var(--lightScaleEnd));
  }
}

See demo on Codepen Dynamic CSS Animations with CSS Variables

Edit: Here's a CSS Tricks article about it too.

Solution 4 - Javascript

Alex Grande's answer works GREAT for a few keyframes. But, say you want to dynamically keep adding in keyframes over and over again, then your webpage get really laggy really quick. To solve this problem, just STOP creating new DOM elements. Rather, create 1 new DOM stylesheet, and just reuse it with the insertRule. If you want even more keyframes (like if you're generating a new keyframe every animationframe), then you need to set up a system which deletes old keyframes after they're no longer used. This is a good start to how something like this can be achieved.

var myReuseableStylesheet = document.createElement('style'),
    addKeyFrames = null;
document.head.appendChild( myReuseableStylesheet );
if (CSS && CSS.supports && CSS.supports('animation: name')){
    // we can safely assume that the browser supports unprefixed version.
    addKeyFrames = function(name, frames){
        var pos = myReuseableStylesheet.length;
        myReuseableStylesheet.insertRule(
            "@keyframes " + name + "{" + frames + "}", pos);
    }
} else {
    addKeyFrames = function(name, frames){
        // Ugly and terrible, but users with this terrible of a browser
        // *cough* IE *cough* don't deserve a fast site
        var str = name + "{" + frames + "}",
            pos = myReuseableStylesheet.length;
        myReuseableStylesheet.insertRule("@-webkit-keyframes " + str, pos);
        myReuseableStylesheet.insertRule("@keyframes " + str, pos+1);
    }
}

Example usage:

addKeyFrames(
    'fadeAnimation',
    '0%{opacity:0}' + 
    '100%{opacity:1}'
);

Also, Alex Grande, I am pretty sure that document.getElementsByTagName('head')[0] and type = 'text/css' hasn't been needed since IE8, and @keyframes aren't supported till IE10. Just saying...

Solution 5 - Javascript

You can change the style in CSSKeyframeRule, and this works fine for me in Chrome, just as the code below. Hope this will help:)

<html>

<head>
	<style>
		#text {
			display: inline-block;
		}
	</style>
</head>

<body>
	<div id="text">TEXT</div>
	<script>
	
		// Dynamically create a keyframe animation
		document.styleSheets[0].insertRule('\
			@keyframes anim {\
				from { transform: rotateZ(0deg);   }\
				to   { transform: rotateZ(360deg); }\
			}'
		);
		var div = document.getElementById('text');
		div.style.animation = 'anim 1s linear forwards';
		
		// This function will change the anim
		function stopAtSomeDeg(d) {
			var ss = document.styleSheets[0];
			var anim;
			for (var i in ss.cssRules) {
				// Find your animation by name
				if (ss.cssRules[i].name === 'anim') {
					anim = ss.cssRules[i];
					break;
				}
			}
			var stopFrame = anim.cssRules[1]; // This indicates the second line of "anim" above.
			
			// Change any attributes
			stopFrame.style.transform = 'rotateZ(' + d + 'deg)';
		}
		
		stopAtSomeDeg(180);
	</script>
</body>
</html>

Solution 6 - Javascript

This is now easy achievable with the new Web Animations API.

It may look like:

let anim = document.getElementById('foo').animate(
[
  { transform: `rotate(${A_DYNAMIC_VALUE})` }
], {
  duration: 3000 //Dunno if can be Infinite
});

// and later
anim.pause();

Solution 7 - Javascript

In JavaScript is it possible to access to the style sheet with document.styleSheets. Every sheet has a rule and/or cssRule list (browser depending) and a CSSStyleSheet.insertRule() method.

This method allows you to add a new keyframe raw as a string:

JavaScript

function insertStyleSheetRule(ruleText)
{
    let sheets = document.styleSheets;
    
    if(sheets.length == 0)
    {
        let style = document.createElement('style');
        style.appendChild(document.createTextNode(""));
        document.head.appendChild(style);
    }
    
    let sheet = sheets[sheets.length - 1];
    sheet.insertRule(ruleText, sheet.rules ? sheet.rules.length : sheet.cssRules.length);
}

document.addEventListener("DOMContentLoaded", event =>
{
    insertStyleSheetRule("@keyframes spinIt { 0% { transform: rotate(-20deg); } 100% { transform: rotate(20deg); } }");
    
    insertStyleSheetRule("#box { " + 
        "animation: spinIt 1s infinite alternate cubic-bezier(0.5,0,0.5,1); " + 
        "width: 64px; height: 64px; background-color: red; border: 4px solid black; " + 
    "}");
});

html

<div id="box"></div>

demo: https://jsfiddle.net/axd7nteu/

Solution 8 - Javascript

You could create a new stylesheet with the animation you want in it. For Example:

function addAnimation(keyframe){
     var ss=document.createElement('style');
     ss.innerText=keyframe;
     document.head.appendChild(ss);
}

This would create a new stylesheet with your animation.
This method has only been tested in Chrome.

Solution 9 - Javascript

With CSS variables: You can use the pseudo :root of the element to declare a css variable within the css rules, then manipulate that variable using Javascript.

:root {--variable-name:property;} which is basically the root element of the document <html>. Then change the value of the CSS root variable/s using JS with:

element.style.setProperty('--variable-name','value'). Pass the declared root variable --variable-name as the name and assign the new value. Then in your @keyframes css rules, add the root variable name, like: from: { top: var(--top-position)}, to the property within the offset @keyframe rule. Example:

:root {
  --top-position-start: 0px;
  --left-position-start: 0px;
  --top-position-end: 200px;
  --left-position-end: 200px;
}

.element {
  top: var(--top-position-start);
  left: var(--left-position-start);
  animation: movePos 1s ease-in;
}

@keyframes movePos {
  from: {
    top: var(--top-position-start);
    left: var(--left-position-start);
  } 
  to: {
    top: var(--top-position-end);
    left: var(--left-position-end);
  }
}

Then the JS would like something like:

let ran = getRandomInt(99);
let skew = ran + getRandomInt(10);
root.style.setProperty('--top-position-end', `${ran}vw`);
root.style.setProperty('--left-position-end', `${skew}vw`);

By using the CSS variable on the root element, you are able to pass it along to the @keyframes event.

See the following working example using randomly placed div using CSS left and background-color:rgb()(red, green, blue) passed using the html:root style to @keyframes within CSS.

let root = document.documentElement;
let rain = document.querySelectorAll('.drop');

function getMaxInt(max) {
  return Math.floor(Math.random() * Math.floor(max));
}

function getMinMaxInt(min, max) {
  return Math.random() * (max - min) + min;
}
// set an interval to drop the div from randomly positioned view widths on the screen
setInterval(() => {
  let ran = getMaxInt(86);
  let skew = ran + getMaxInt(10);
  let circle = `${getMinMaxInt(3,15)}px`;
  root.style.setProperty('--keyframeLeftStart', `${ran}vw`);
  root.style.setProperty('--keyframeLeftEnd', `${skew}vw`);  
  root.style.setProperty('--animationDuration', `${ getMaxInt(2500)}ms`); 
  root.style.setProperty('--width', circle);
  root.style.setProperty('--height', circle);
  root.style.setProperty('--red', getMinMaxInt(100, 255));
  root.style.setProperty('--green', getMinMaxInt(100, 255));
  root.style.setProperty('--blue', getMinMaxInt(100, 255));
}, getMaxInt(3500))

* {
  padding: 0;
  margin: 0;
  box-sizing: border-box;
}

/* here we define some css variables for the document :root 
   essentially, these will be the first iteration of the elements style
   then JS will take voer and set the values from script */
:root {
  --keyframeTop: 0;
  --keyframeBottom: 98vh;
  --keyframeLeftStart: 2vw;
  --keyframeLeftEnd: 10vw;
  --animationDuration: 1s;
  --width: 5px;
  --height: 5px;
  --red: 100;
  --green: 100;
  --blue: 100;
}

body {
  width: 100vw;
  height: 100vh;
  background-color: #000;
}

#main {
  width: calc(100vw - var(--width));
  height: calc(100vh - var(--height));
  display: flex;
  justify-content: center;
  align-items: center;
  color: #fff;
}

.drop {
  width: var(--width);
  height: var(--height);
  border-radius: 50%;
  position: absolute;
  animation: dropping var(--animationDuration) ease-in infinite;
  top: var(--keyframeTop);
  left: var(--keyframeLeftStart);
  background-color: rgb(var(--red),var(--green), var(--blue));
}

@keyframes dropping {
  0% {
    top: var(--keyframeTop);
    left: var(--keyframeLeftStart);
    background-color: rgb(var(--red),var(--green), var(--blue));
  }
  50% {
    background-color: rgb(var(--green),var(--blue), var(--red));
  }
  100% {
    top: var(--keyframeBottom);
    left: var(--keyframeLeftEnd);
    background-color: rgb(var(--blue),var(--red), var(--green));
  }
}

<div id="main">
    <div class="drop"></div>
</div>

Solution 10 - Javascript

Setting a @keyframe in one call with JavaScript, and use it, using append(), Object.assign(), and template strings.

document.body.append(
  Object.assign(document.createElement("style"), {
    textContent: `@keyframes coolrotate { from { transform: scale(1, 1) translate(-0.1em, 0)} to { transform: scale(-1, 1) translate(0, 0) }} small { display: inline-block; font-size:2.3em; animation: 1s infinite alternate coolrotate } body {font-size: x-large}`
  }),
  Object.assign(document.createElement("span"), {
    innerHTML: `<span>c</span><small>o</small><span>o</span><small>L</small><small>...</small>`,
    style: "font-weight: 1000; font-size: 3.3em;"
  })  
)

Solution 11 - Javascript

the user7892745 wont work for me, need some little adjustement

1° "pos" not understand wot should be, but the console log say "undefined" so I've remove " , pos"

2° " myReuseableStylesheet.insertRule" give me error " is not a function" so I used "innerHTML" insted of "insertRule"

3° finally I've moved " document.head.appendChild( myReuseableStylesheet );" at the end

but after this it work fine and it's exact what I looking for. thanks a lot user7892745 :D

maybe the problem I had, come form the way I use it

this is the script i used with it

var getclass = document.getElementsByClassName("cls");
var countclass = getclass.length;
for (var i=0; i <countclass; i++ ){
	getclass[i].addEventListener('mouseover', function(){
		// get the data-name value to show element whose id are the same
		var x=  this.getAttribute("data-name"); 
		var y =document.getElementById(x);
			y.style.display="block";
			// because the element to show have fixed width, but different text length, they have different height
			// so I need to get the highness, then use the value of height to define the 100% value of animation
			// or the longer ones will be cutted an the shorten have a lot of empty space a the end
		var yHeig= Math.round(parseInt(getComputedStyle(y).getPropertyValue('height')));
			yHeig_ = yHeig - 10; // to shorten a bit the time from end and new passage
		console.log(yHeig+" - "+ yHeig_);
		addKeyFrames(
				'showMe',
				'0%{top:35px;}' + 
				'100%{top:-'+ yHeig_ +'px;}'
			);
		y.style.animation="showMe 7s linear infinite";
 
	},false);

	getclass[i].addEventListener('mouseout', function(){
		var x=  this.getAttribute("data-name");
		document.getElementById(x).style.display="none";
	},false);
}

i know thath a html marquee cuold seem symple to do the same thing, but dont work well,

Solution 12 - Javascript

You can create a <style> element, set its content to the CSS you want, in this case, the declaration of your animation and add it to the <head> of the page.

Also, as others have suggested, if you need to create many different animations, then it would be better to reuse a single <style> tag rather than creating multiple of them and add the new styles using CSSStyleSheet.insertRule().

Lastly, if you can use ES6's template literals/strings, your code will look much cleaner:

let dynamicStyles = null;

function addAnimation(body) {
  if (!dynamicStyles) {
    dynamicStyles = document.createElement('style');
    dynamicStyles.type = 'text/css';
    document.head.appendChild(dynamicStyles);
  }
  
  dynamicStyles.sheet.insertRule(body, dynamicStyles.length);
}

addAnimation(`
  @keyframes myAnimation { 
    0% { transform: rotate(0); }
    20% { transform: rotate(${ 360 * Math.random() }deg); }
    60% { transform: rotate(${ -360 * Math.random() }deg); }
    90% { transform: rotate(${ 360 * Math.random() }deg); }
    100% { transform: rotate(${ 0 }deg); }
  }
`);

document.getElementById("circle").style.animation = 'myAnimation 3s infinite';

html,
body {
  height: 100vh;
}

body {
  display: flex;
  justify-content: center;
  align-items: center;
  margin: 0;
}

#circle {
  width: 100px;
  height: 100px;
  box-shadow:
    0 0 48px -4px rgba(0, 0, 0, .25),
    0 0 0 4px rgba(0, 0, 0, .02);
  border-radius: 100%;
  position: relative;
  overflow: hidden;
}

#circle::before {
  content: '';
  position: absolute;
  top: 0;
  left: 50%;
  transform: translate(-2px);
  border-left: 4px solid #FFF;
  height: 24px;
  box-shadow: 0 -4px 12px rgba(0, 0, 0, .25);
}

<div id="circle"></div>

Or even better:

let dynamicStyles = null;

function addAnimation(name, body) {
  if (!dynamicStyles) {
    dynamicStyles = document.createElement('style');
    dynamicStyles.type = 'text/css';
    document.head.appendChild(dynamicStyles);
  }
  
  dynamicStyles.sheet.insertRule(`@keyframes ${ name } {
    ${ body }
  }`, dynamicStyles.length);
}

addAnimation('myAnimation', `
  0% { transform: rotate(0); }
  20% { transform: rotate(${ 360 * Math.random() }deg); }
  60% { transform: rotate(${ -360 * Math.random() }deg); }
  90% { transform: rotate(${ 360 * Math.random() }deg); }
  100% { transform: rotate(${ 0 }deg); }
`);

document.getElementById("circle").style.animation = 'myAnimation 3s infinite';

html,
body {
  height: 100vh;
}

body {
  display: flex;
  justify-content: center;
  align-items: center;
  margin: 0;
}

#circle {
  width: 100px;
  height: 100px;
  box-shadow:
    0 0 48px -4px rgba(0, 0, 0, .25),
    0 0 0 4px rgba(0, 0, 0, .02);
  border-radius: 100%;
  position: relative;
  overflow: hidden;
}

#circle::before {
  content: '';
  position: absolute;
  top: 0;
  left: 50%;
  transform: translate(-2px);
  border-left: 4px solid #FFF;
  height: 24px;
  box-shadow: 0 -4px 12px rgba(0, 0, 0, .25);
}

<div id="circle"></div>

Solution 13 - Javascript

Found a simple idea with JavaScript by using CSS data URI.

Solution

function addNewCSS(css_text) {
  css_text = encodeURIComponent(css_text);
  const url = `data:text/css,${css_text}`;
  const link = document.createElement("link");
  link.rel = "stylesheet";
  link.href = url;
  document.head.appendChild(link);
}

Function accepts CSS code as text and adds it as a style.

Working

Converts the CSS text to URI encoded form (for passing as data URL). Then creates a link tag with href as the url and relation as "stylesheet" (here rel attribute is required and won't work if not added) Finally appends the link tag to head tag.

Example

function addNewCSS(css_text) {
  css_text = encodeURIComponent(css_text);
  const url = `data:text/css,${css_text}`;
  const link = document.createElement("link");
  link.rel = "stylesheet";
  link.href = url;
  document.head.appendChild(link);
}

const duration = 1;
const colour = ["#2196F3", "#E91E63"];
const css_data = `
  @keyframes change{
    0% {
      background: ${colour[0]};
    }
    100% {
      background: ${colour[1]};
    }
  }
  body {
    animation: change ${duration}s linear infinite alternate;
  }
`;

addNewCSS(css_data);

<html>
  <head></head>
  <body>
    <h1>Wait to see JS adding background color animation</h1>
  </body>
</html>

Conclusion

I haven't tested on all browsers, but works in chrome, and as it is added to the end of head tag it get priority from other tags in head, If you are planning to change values frequently, instead of adding new tags, try to edit the href of previously added tags.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJagadeesh JView Question on Stackoverflow
Solution 1 - JavascriptAlex GrandeView Answer on Stackoverflow
Solution 2 - JavascriptDeep SharmaView Answer on Stackoverflow
Solution 3 - Javascriptsandrina-pView Answer on Stackoverflow
Solution 4 - Javascriptuser7892745View Answer on Stackoverflow
Solution 5 - JavascriptWheatBerryView Answer on Stackoverflow
Solution 6 - JavascriptmcpiromanView Answer on Stackoverflow
Solution 7 - JavascriptMartin WantkeView Answer on Stackoverflow
Solution 8 - JavascriptMatthias SouthwickView Answer on Stackoverflow
Solution 9 - Javascriptdale landryView Answer on Stackoverflow
Solution 10 - JavascriptNVRMView Answer on Stackoverflow
Solution 11 - Javascriptcippu_luxView Answer on Stackoverflow
Solution 12 - JavascriptDanzigerView Answer on Stackoverflow
Solution 13 - Javascript27pxView Answer on Stackoverflow