Reputation: 1301
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
Upvotes: 82
Views: 109271
Reputation: 355
This is now easily achievable with the new Web Animations API, which looks like this:
const anim = document.getElementById("foo").animate(
[
{ transform: `rotate(${A_DYNAMIC_VALUE})` }
],
{ duration: 3000, iterations: Infinity }
);
// and later
anim.pause();
The first argument to .animate
takes a list of keyframes, and the second takes the animation options (e.g. duration, how many times it repeats, etc).
Upvotes: 14
Reputation:
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...
Upvotes: 11
Reputation: 477
Found a simple idea with JavaScript by using CSS data URI.
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.
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.
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>
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.
Upvotes: 0
Reputation: 8600
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>
Upvotes: 2
Reputation: 13057
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;"
})
)
Upvotes: 1
Reputation: 4160
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.
Upvotes: 15
Reputation: 3473
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
Upvotes: 49
Reputation: 121
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.
Upvotes: 1
Reputation: 21161
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>
Upvotes: 0
Reputation: 4571
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/
Upvotes: 1
Reputation: 31
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>
Upvotes: 3
Reputation: 1
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,
Upvotes: 0
Reputation: 8027
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);
Upvotes: 73