How to dynamically create '#-Keyframe' CSS animations? - javascript
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/
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);
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
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.
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...
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).
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>
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>
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/
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.
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;"
})
)
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,
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>
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.
Related
How do I change #keyframes using JS? [duplicate]
This question already has answers here: Passing parameters to css animation (3 answers) Closed 2 years ago. I'm using #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; } and #keyframes progressBar { 0% { width: 0; } 100% { width: 280px; } } I want to change the width number of #keyframeusing a JS variable. How could I do this (whithout jQuery) ?
you can use css variables for this case. define two variable in root of page and use these in keyframe : :root { --my-start-width: 0; --my-end-width: 280px; } ... #keyframes progressBar { 0% { width: var(--my-start-width); } 100% { width: var(--my-end-width); } } now you can get and set this property in js with these functions : //set property: document.documentElement.style .setProperty('--my-variable-name', '100px'); //get property getComputedStyle(document.documentElement) .getPropertyValue('--my-variable-name'); // returns value
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> So here is my solution allow me to introduce CSS Variables I have created a CSS variable in CSSOM globle scope :root { --change: 280px; } then have accessed the same with in CSS. Benefit is when ever the value of variable is changed in root it will be automatically be represented where ever the variable is called. #keyframes progressBar { 0% { width: 0; } 100% { width: var(--change); } } No we need to access this in after the fact. I have used document.documentElement to grab the whole document as an element including all the css that is in it. then I have used style.setProperty to modify the modifies an existing CSS property in a CSS declaration block. Keyword declaration block not the computed painted block. root.style.setProperty('--change', 30 + "px"); Above is changing the property of the document which is set in global scope of CSSOM and it has no sub properties etc. We still cannot access rules e.g. root.style.setProperty('#keyframes progressBar', 30 + "px") simply wont work. final example to use it as before the fact. root = document.documentElement; root.style.setProperty('--change', 30 + "px"); :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> I have just put set time out function to show how it works. Obviously this time with out set timeout. KEY TAKE AWAY. manipulating CSS block after the fact wont rerun the animation again as seen in the first example unless you run another set of function that reverses every thing then redoes it. Depends on your context. manipulating CSS block before the fact will consider JS manipulation as considered value as shown in the second example. By context I mean what ever you intend to do really as sown in example below using set time out we are changing css variable after 3 seconds obviously animation is ran twice to but on second run bar goes even longer. so CJSSS alo needs you context. root = document.documentElement; setTimeout(function(){ root.style.setProperty('--change', 500 + "px"); }, 3000); :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; animation-iteration-count: 2; } #keyframes progressBar { 0% { width: 0; } 50% { width: var(--change); } 100% { width: 0; } } <div id="progressBar"></div> Hope answer gives you enough head ways to move forward.
Add css to elements rendered using js in VueJs
I want to add css to elements which I rendered in a method using js. buttonClicked(event) { console.log(event); let x = event.clientX - event.target.offsetLeft; let y = event.clientY - event.target.offsetTop; let ripples = document.createElement("span"); console.log(ripples); ripples.style.left = x + "px"; ripples.style.top = y + "px"; document.getElementById("btn").appendChild(ripples); } So this is a function which get called when a user clicks on a button. I want to add the css to <span> which gets created in this method. The css for <span> element is not rendering when I'm using scoped css. Its working fine without the scoped keyword. I googled the problem and find this link. Here it's told we can use /deep/ or >>> operator to access the span elements. But it's still not working. Here's the CSS button >>> span { z-index: 3; position: absolute; background: white; transform: translate(-50%, -50%); pointer-events: none; border-radius: 50%; animation: animate 1s linear infinite; }
I believe you are correct that it is necessary to use /deep/ or >>> with injected HTML when using scoped. Your code essentially works. However, I had to add some width and height to the actual appended element for anything visually to appear. Not sure what the rest of your code looks like, but the following component works on my machine: <template> <button #click="makeRipple"> CLICK ME </button> </template> <script> export default { name: 'Button', methods: { makeRipple (event) { const x = event.clientX - event.target.offsetLeft const y = event.clientY - event.target.offsetTop const ripples = document.createElement('span') ripples.style.left = x + 'px' ripples.style.top = y + 'px' event.target.appendChild(ripples) } } } </script> <style scoped> #keyframes ripple { to { opacity: 0; transform: scale(4); } } button >>> span { z-index: 3; position: absolute; background: white; transform: translate(-50%, -50%); pointer-events: none; border-radius: 50%; animation: ripple 1s linear infinite; width: 6px; height: 6px; } </style>
Animating a div element
Looking to make a specific animation for a div element. I want it to go down (smoothly) and when it reaches the bottom of the screen to come back up (smoothly). The code I have is as follows: The Javascript part at the If statement is where I am having difficulties. I want the box to come down and come back up smoothly. HTML: <div class="verticalDiv" id="verticalDiv" onclick="verticalMove()"></div> CSS: .verticalDiv { position: absolute; top: 0px; right: 500px; width: 100px; height: 100px; margin: 100px auto; background: red; } JS: myVar1 = setInterval(verticalMove, 50); v = 0; function verticalMove() { redBox = document.getElementById('verticalDiv') redBox.style.top = v + "px"; if (v >= 0) { v++;} if (v === 200) { v--; } console.log(v); }
I think, best way is to use css animation. You don't have to care about animation logic. Just use keyframes. Here is example: HTML <div id="verticalDiv" class="verticalDiv"></div> CSS .verticalDiv { height: 20px; width: 20px; background: red; } #keyframes move { 0% { transform: translateY(0); } 50% { transform: translateY(200px); } 100% { transform: translateY(0); } } .verticalDiv.move { animation: move 3s ease-in-out; } JS const verticalDiv = document.getElementById('verticalDiv'); verticalDiv.addEventListener('click', () => { verticalDiv.classList.toggle('move'); }); WORKING DEMO click on red div to start animation. BTW If you want animate something. It is always better to animate properties that doesn't force layout updates: transform and opacity. Other properties, like top, bottom, margin are expensive for browser to animate. You should avoid them if possible. Read more
You need to differentiate between the two phases, moving down and moving up. It can be a simple true/false boolean, but storing a "speed" or "delta" value (like +/-1) is also a very typical approach. var v = 0; var delta=1; function verticalMove() { redBox = document.getElementById('verticalDiv') v += delta; redBox.style.top = v + "px"; if (v <= 0) delta = 1; if (v >= 50) delta = -1; } function startMove(event) { setInterval(verticalMove,30); event.target.onclick=""; } .verticalDiv { position: absolute; top: 0px; right: 500px; width: 100px; height: 100px; background: red; } <div class="verticalDiv" id="verticalDiv" onclick="startMove(event)"></div>
List rotation with limited elements
I have div container with list (cards) inside. When I hover it, cards start to moving (translateX animation). container's width is 300px, elements count in container:3, each element width:100px. So you can see 3 elements in container together overflow:hidden. What I want to make?, is that when there is no element to show translateX animation -100px = 100px blank space after third element, it start from 1 elements in the list immediately after last, with no blank space. For now, I have no idea how it could be done without duplicates and etc. Here is what I have at the moment: Fiddle (Hover cards to see translation animation) UPD 1: The code and data (cards count, container size) was taken for example, i'll try to explain better what i want: My goal is to built list of cards and after button was pressed, the list will start moving (like in example with translateX animation) for some time (for example translateX: 12491px, animation-duration: 15s;) and stops. But problem is that amount of crads in the list would be in range of 3-40 cards (each card is 100px width & height). So, when i'll set translateX: 12491px for example, it will be out of range and after the last card in the list would appear blank space. I want first and last card to be tied somehow and after the last card immediately appears first card in the list and etc.. Maybe i am searching for solution in a wrong way, but i guess you understand the main idea. UPD 2: I found that cs:go uses animation that i wanted to write on html\css\js. Here is video: youtube.com html: <div class="container"> <div class="cards"> <div class="card"> 1 </div> <div class="card"> 2 </div> <div class="card"> 3 </div> </div> </div> css: .container { width:300px; height: 100px; border: 2px solid black; overflow: hidden; } .card { float:left; height: 100px; width: 100px; background-color:blue; box-sizing: border-box; border: 2px solid red; color: white; font-size: 23px; } .cards:hover { transform: translateX(-100px); transition-duration: 3s; animation-duration: 3s; animation-fill-mode: forwards; }
start from 1 elements in the list immediately after last, with no blank space This is beyond CSS and you will need Javascript for that. Because, you have tagged the question with Javascript and not jQuery, my answer would be limited to pure Javascript only. Look ma, no JQuery ;) I have no idea how it could be done without duplicates Here is a DIY (do it yourself) idea.. The main trick is to show at least one item less than the total you have. If you have 3 cards, show only 2. If you have 4 cards, show only 3. Why, because you need to re-position a card when it goes out of view and wrap it back at the end. If you show exactly the same number of cards that you have, then you cannot break half-a-card and wrap it and you will see some blank space until the first one goes out of view. You get the idea? Do not use translate or you will end up complicating things for yourself while scripting it out. Keep things simple. Do not use a wrapper for your cards. Why? Because, we will be re-positioning the cards which have gone out of view. When we do that, the next card will take up its place and immediately go out of view making things further difficult for you. To keep things simple, arrange your cards with absolute positioning relative to its container. To start with, let all cards stack up at top:0; and left: 0;. Next wire-up Javascript to position the left property based on the width of each card and arrange them linearly. Use requestAnimationFrame to control the animation. Keep track of the left-most card and its left position. When this goes out of view (which is 0 minus width), appendChild this card to its container. This will move the card to the end of cards. Also, change the left property to it based on the last card in the list. That' all there is to it. Below is a demo. To make it easy for you to experiment, I have used a settings object to keep the configurable properties which you can easily tweak and see. Look closely at the code and you will find it simple to understand. You can set the iterations settings to 0 to make the animation infinite. Also, note that you do not need to duplicate or fake the cards. Try the demo and add as many cards you want to. The inline code comments in the snippet, will further help you understand each line of code and relate to the steps above. Snippet: var list = document.querySelector('.cardList'), // cache the container cards = document.querySelectorAll('.card'), // cache the list of cards start = document.getElementById('start'), // buttons stop = document.getElementById('stop'), reset = document.getElementById('reset'), raf, init = 0, counter = 0, lastCard, currentIteration = 0, // general purpose variables settings = { // settings object to help make things configurable 'width': 100, 'height': 100, 'speed': 2, 'iterations': 2, 'count': cards.length } ; start.addEventListener('click', startClick); // wire up click event on buttons stop.addEventListener('click', stopClick); reset.addEventListener('click', resetClick); initialize(); // initialize to arrange the cards at start function initialize() { // loop thru all cards and set the left property as per width and index position [].forEach.call(cards, function(elem, idx) { elem.style.left = (settings.width * idx) + 'px'; }); init = -(settings.width); // initialize the view cutoff lastCard = cards[settings.count - 1]; // identify the last card counter = 0; currentIteration = 0; // reset some counters settings.speed = +(document.getElementById('speed').value); settings.iterations = +(document.getElementById('iter').value); } function startClick() { initialize(); raf = window.requestAnimationFrame(keyframes); // start animating } function stopClick() { window.cancelAnimationFrame(raf); } // stop animating function resetClick() { // stop animating and re-initialize cards to start again window.cancelAnimationFrame(raf); document.getElementById('speed').value = '2'; document.getElementById('iter').value = '2'; initialize(); } // actual animation function function keyframes() { var currentCard, currentLeft = 0, newLeft = 0; // iterate all cards and decrease the left property based on speed [].forEach.call(cards, function(elem, idx) { elem.style.left = (parseInt(elem.style.left) - settings.speed) + 'px'; }); currentCard = cards[counter]; // identify left-most card currentLeft = parseInt(currentCard.style.left); // get its left position if (currentLeft <= init) { // check if it has gone out of view // calculate position of last card newLeft = parseInt(lastCard.style.left) + settings.width; list.appendChild(currentCard); // move the card to end of list currentCard.style.left = newLeft + 'px'; // change left position based on last card lastCard = currentCard; // set this as the last card for next iteration counter = (counter + 1) % settings.count; // set the next card index if ((settings.iterations > 0) && (counter >= (settings.count - 1))) { currentIteration++; // check settings for repeat iterations } } if (currentIteration >= settings.iterations) { return; } // when to stop raf = window.requestAnimationFrame(keyframes); // request another animation frame }; * { box-sizing: border-box; padding: 0; margin: 0; } .cardList { position: relative; height: 100px; width: 300px; margin: 10px; border: 2px solid #33e; overflow: hidden; white-space: nowrap; } .card { position: absolute; left: 0; top: 0; text-align: center; height: 100px; width: 100px; line-height: 100px; background-color: #99e; font-family: monospace; font-size: 2em; color: #444; border-left: 1px solid #33e; border-right: 1px solid #33e; } div.controls, button { margin: 10px; padding: 8px; font-family: monospace; } div.controls input { width: 48px; padding: 2px; text-align: center; font-family: monospace; } <div class="controls"> <label>Speed <input id="speed" type="number" min="1" max="8" value="2" />x</label> | <label>Iterations <input id="iter" type="number" min="0" max="8" value="2" /></label> </div> <div class="cardList"> <div class="card">1</div> <div class="card">2</div> <div class="card">3</div> <div class="card">4</div> </div> <button id="start">Start</button> <button id="stop">Stop</button> <button id="reset">Reset</button> Fiddle: http://jsfiddle.net/abhitalks/1hkw1v0w/ Note: I have left out a few things in the demo. Especially, although width and height of the cards is part of the settings object, but currently it left fixed. You can easily use the settings object to make the dimensions of the cards configurable as well. Edit: (as per Op's comment) If you want a greater control over distance to scroll, duration and timing-functions (easing), then you could implement those yourself using a library. A couple of such good libraries are the Robert Penner's Easing Functions and a jQuery plugin from GSGD. Although you can implement all of that with pure Javascript, it would be easier if you use a library like jQuery. Catch here is that in order to do so effectively, you must then duplicate the cards. You can do so easily by cloning the entire list a couple of times. Although you have not tagged this question with jQuery, here is a small demo (using jQuery to get it done quickly) where you can configure the speed and the distance. Snippet 2: var $cardList = $('.cardList').first(), $cards = $('.card'), $speed = $('input[name=speed]'), width = 100, randomize = true, distance = 20 * width ; for (var i = 0; i < 50; i++) { $cards.clone().appendTo($cardList); } function spin() { var newMargin = 0, newDistance = distance, speed = +($speed.filter(':checked').val()); if (randomize) { newDistance = Math.floor(Math.random() * $cards.length * 5); newDistance += $cards.length * 5; newDistance *= width; } newMargin = -(newDistance); $cards.first().animate({ marginLeft: newMargin }, speed); } $('#spin').click(function() { $cards.first().css('margin-left', 0); spin(); return false; }); * { box-sizing: border-box; padding: 0; margin: 0; } .cardList { height: 100px; width: 302px; position: relative; margin: 10px; border: 1px solid #33e; overflow: hidden; white-space: nowrap; } .card { display: inline-block; text-align: center; height: 100px; width: 100px; line-height: 100px; background-color: #99e; font-family: monospace; font-size: 2em; color: #444; border-left: 1px solid #33e; border-right: 1px solid #33e; } .cardList::before, .cardList::after { content: ''; display: block; z-index: 100; width: 0px; height: 0px; transform: translateX(-50%); border-left: 8px solid transparent; border-right: 8px solid transparent; } .cardList::before { position: absolute; top: 0px; left: 50%; border-top: 12px solid #33e; } .cardList::after { position: absolute; bottom: 0px; left: 50%; border-bottom: 12px solid #33e; } div.controls, button { margin: 10px; padding: 8px; font-family: monospace; } div.controls input { width: 48px; padding: 2px; text-align: center; font-family: monospace; } <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> <div class="controls"> <label>Speed: </label> | <label><input name="speed" type="radio" value='6000' />Slow</label> <label><input name="speed" type="radio" value='5000' checked />Medium</label> <label><input name="speed" type="radio" value='3000' />Fast</label> </div> <div class="cardList"><!-- --><div class="card">1</div><!-- --><div class="card">2</div><!-- --><div class="card">3</div><!-- --><div class="card">4</div><!-- --></div> <button id="spin">Spin</button> Fiddle 2: http://jsfiddle.net/abhitalks/c50upco5/
If you don't want to modify the dom elements you could take advantage of flex-item's order property; to do this you'd still need a little JS to add this property after animation has ended; I also changed to animation instead of transition so it automatically resets the transform property at the end of animation. $('.cards').mouseenter(function() { setTimeout(function() { $('.card').first().css("order", "2"); }, 3000); }); $('.cards').mouseleave(function() { $('.card').first().css("order", "-1"); }); .container { width: 300px; height: 100px; border: 2px solid black; overflow: hidden; } .card { float: left; /* height: 100px; width: 100px;*/ background-color: blue; box-sizing: border-box; border: 2px solid red; color: white; font-size: 23px; flex: 0 0 25%; } .cards:hover { animation: trans 3s; } /**/ .cards { width: 400px; height: 100%; display: flex; transition: transform 3s; } #keyframes trans { 0% { transform: translateX(0) } 100% { transform: translateX(-100px) } } <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script> <div class="container"> <div class="cards"> <div class="card">1</div> <div class="card">2</div> <div class="card">3</div> </div> </div> fiddle But if you're OK to use JS I suggest you manipulate the order of DOM elements directly,taking the first child element of .cards and appending it to the end of list at the end of each animation; try this: var anim; $('.cards').mouseenter(function(){ anim = setInterval(function(){ $('.cards').append($('.card').first()) },3000) }); $('.cards').mouseleave(function(){ clearInterval(anim) }); .container{ width:300px; height: 100px; border: 2px solid black; overflow: hidden; } .card{ float:left; /* height: 100px; width: 100px;*/ background-color:blue; box-sizing: border-box; border: 2px solid red; color: white; font-size: 23px; /**/ flex:0 0 25%; } .cards:hover{ animation: trans 3s infinite; } /**/ .cards{ width:400px; height:100%; display:flex; } #keyframes trans { 0% { transform: translateX(0) } 100% { transform: translateX(-100px) } } <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script> <div class="container"> <div class="cards"> <div class="card"> 1 </div> <div class="card"> 2 </div> <div class="card"> 3 </div> </div> </div> in case you want one card to be present at same time both at the beginning and at the end of card-list you'll need to make a deep-copy / clone of the element; here's an example;
Update 2: I wrote a jquery plugin that may act the way you want: you can add as many cards as you want, right now the "translateX" is random (the script will choose randomly the final card) link to the demo Update: I know, I used duplicates, but now my code works on three cards: I added three "fake" cards Each "real" card has it's own animation the "fake" cards will be overlapped by the real ones once their cycle is finished ("when there is no element to show" as you asked) check the snippet: .container { width: 300px; height: 100px; border: 2px solid black; overflow: hidden; } .card { float: left; height: 100px; width: 100px; background-color: blue; box-sizing: border-box; border: 2px solid red; color: white; font-size: 23px; } .cards { width: 600px; } .container:hover .card1{ animation: 1600ms slide1 infinite linear; } .container:hover .card2{ animation: 1600ms slide2 infinite linear; } .container:hover .card3{ animation: 1600ms slide3 infinite linear; } .fakecard{z-index:-1000;} .container:hover .fakecard{ animation: 1600ms fakeslide infinite linear; } #keyframes slide1 { 0% { transform: translateX(0px); } 33% { transform: translateX(-100px); } 33.1% { transform: translateX(+200px); } 100% { transform: translateX(0px); } } #keyframes slide2 { 0% { transform: translateX(0px); } 66% { transform: translateX(-200px); } 66.1% { transform: translateX(100px); } 100% { transform: translateX(0px); } } #keyframes slide3 { 0% { transform: translateX(0px); } 99% { transform: translateX(-300px); } 99.1% { transform: translateX(+300px); } 100% { transform: translateX(0px); } } #keyframes fakeslide { 0% { transform: translateX(0px); } 99% { transform: translateX(-300px); } 99.1% { transform: translateX(+300px); } 100% { transform: translateX(0px); } } <div class="container"> <div class="cards"> <div class="card card1"> 1 </div> <div class="card card2"> 2 </div> <div class="card card3"> 3 </div> <div class="card fakecard"> 1 (fake) </div> <div class="card fakecard"> 2 (fake) </div> <div class="card fakecard"> 3 (fake) </div> </div> </div> Previous answer: Is this what you are trying to achieve? I don't think you can do it without duplicates... If not, can you explain better what you are trying to achieve here? [snipped code removed]
Here is the same effect that you mentioned, with a little tweak on your CSS and a helpful hand from jQuery. CSS Change your selector for the translateX animation to apply on each of the .card boxes when their immediate parent is hovered, and not the .cards (which is the immediate parent of the .cards). This is because you'd want the cards to move to the left, and not the window through which they appear while making the movement. That is, .cards:hover .card { transform: translateX(-100px); transition-duration: 1.5s; animation-duration: 1.5s; animation-fill-mode: forwards; } jQuery var $container = $('.container'); var cardWidth = 100; $container.on('mouseenter', function (e) { e.preventDefault(); var $card0Clone = $('.card').eq(0).clone(); // clone of the first .card element $('.cards').append($card0Clone); updateWidth(); }); $container.on('mouseleave', function (e) { e.preventDefault(); var $cards = $('.card'); $cards.eq(0).remove(); // remove the last .card element }); function updateWidth() { $('.cards').width(($('.card').length) * cardWidth); // no of cards in the queue times the width of each card would result in a container fit enough for all of them } Code Explained As you move in the mouse pointer, a clone of the first card is created, and appended to the end of the cards collection. Further, as you move the mouse out of the hover area, the original .card (which was cloned earlier) will be removed from the head of the queue - hence, producing a cyclic effect. The real trick though is with the updateWidth function. Every time the mouse enters the .container the width of the .cards' immediate parent (i.e. .cards div) is updated, so that .cards div is wide enough to fit in all the .cards, and therefore, making sure that each of the cards push against each other and stay in one line at the time the translation animation is being done.
Here is a simple technique that manipulates the Dom to create your desired effect Javascript: document.querySelector('.cards').addEventListener('mousedown', function(e) { if (e.clientX < (this.offsetWidth >> 1)) { this.appendChild(this.removeChild(this.firstElementChild)); } else { this.insertBefore(this.lastElementChild, this.firstElementChild); }}); then in you css use the nth-of-type selector to position elements as required. Here is your fiddle If you are using mouseover you might need to wait for transitionend event before firing again.
Check out this demo Here I used JQuery, you can configure your animation using two variables var translateX = 1000; //adjust the whole distance to translate var stepSpeed = 100; //adjust the speed of each step transition in milliseconds After setting your variables, on the click event of the cards do the following:- Get the number of the steps required based on translateX Loop for the number of steps Inside each loop (each step) move the cards 1 step to the left, then put the first card to the end of the cards to form the connected loop, then return back the cards to it's initial position Here is the code: var stepsNumber = translateX/100; for(var i=0; i< stepsNumber; i++) { $('.cards').animate({'left' : -100}, stepSpeed,function(){ $('.cards div:last').after($('.cards div:first')); $('.cards').css({'left' : '0px'}); }); }
Is it possible to loop changing opacity values in HTML5 or css?
This is the code I'm currently working with. It works to my purposes of layering the two images. What I am trying to do is have the layer0 opacity lower to 0 as the layer1 opacity increases to 100 over a few seconds. {and then on to layer1 with layer2 and so on eventually looping back to layer0} Any help would be appreciated. <head> <style> div.layer0 { width: 371px; height: 345px; background:url(image2.jpg); opacity:1; filter:alpha(opacity=100); /* For IE8 and earlier */ } div.layer1 { width: 371px; height: 345px; background:url(image3.jpg); opacity:0; filter:alpha(opacity=0); /* For IE8 and earlier */ } </style> </head> <body> <div class="layer0"> <div class="layer1"> </div> </div> </body>
To continually do this in a loop, you'll need some javascript to add an appropriate active class to the image you want displayed. Then using CSS transitions you can achieve the fading between images that you require. I created a jsfiddle to give you an example of this working: http://jsfiddle.net/pacso/H6dqq/ The basics are as follows. Some simple HTML divs which you'll be fading: <div class='red square active'></div> <div class='yellow square'></div> <div class='green square'></div> <div class='blue square'></div> These are just going to be coloured squares, but yours could contain images. Next, some CSS markup: .red { background-color: red; } .blue { background-color: blue; } .green { background-color: green; } .yellow { background-color: yellow; } .square { width: 200px; height: 200px; position: absolute; top: 20px; left: 20px; opacity: 0; transition: opacity 2s; -webkit-transition: opacity 2s; /* Safari */ } .active { opacity: 1; } Note that my transition will alter the opacity of the div itself. You may need to change this as needed. Now the javascript to make it work on an endless loop: jQuery(function() { window.setInterval(function () { activeSquare = $('.active'); nextSquare = activeSquare.next() if (nextSquare.length == 0) { nextSquare = activeSquare.siblings().first(); } nextSquare.addClass('active'); activeSquare.removeClass('active'); }, 3000); }); Fairly straightforward. Click the link to my fiddle and hit the run button if you want to see a working demo.
Short answer: not easily. You're probably better off with javascript for the looping. You could make a delayed keyframe animation, but that won't allow you to loop from the start again: jsfiddle.net/G4PTM (firefox/ie10) -- You could make a lot of keyframes with different timings and you can make it work, but it would require quite a bit of code and not scale well (say you wanted to add another layer/image the code would quickly become unmanagable) With some javascript, you can just loop through the divs and add and remove a classname to trigger the transitions, like Jon mentioned. Here is a working demo (using jQuery for simplicity, let me know if you need vanilla js) html <div class="layer0"> </div> <div class="layer1"> </div> <div class="layer2"> </div> css div { width: 371px; height: 345px; opacity: 0; position: absolute; transition: opacity 2s; } div.active { opacity: 1; } div.layer0 { background:url(http://lorempixel.com/373/345); } div.layer1 { background:url(http://lorempixel.com/372/345); } div.layer2 { background:url(http://lorempixel.com/374/345); } js+jquery var firstDiv = $(".layer0"); var current; function loopsie() { // if first iteration or reached end, use first div if (!current || !current.length) current = firstDiv; current.addClass("active"); setTimeout(function() { current.removeClass("active"); setTimeout(function() { current = current.next(); loopsie(); // recurse }, 2000); }, 2000); } //initialize loopsie(); Working demo at http://jsfiddle.net/G4PTM/2/ Plain JavaScript (Without jQuery): var firstDiv = document.querySelector(".layer0"); // IE 8+ var current; function loopsie() { // if first iteration, use first div if (!current) current = firstDiv; current.classList.add("active"); // IE 10+, shim at https://developer.mozilla.org/en-US/docs/Web/API/Element.classList setTimeout(function() { current.classList.remove("active"); // account for text node (if there is whitespace in html) if (current.nextSibling && current.nextSibling.nodeName == "DIV") { current = current.nextSibling; } else if (current.nextSibling && current.nextSibling.nextSibling && current.nextSibling.nextSibling.nodeName == "DIV") { current = current.nextSibling.nextSibling; } else { // reached end current = firstDiv; } loopsie(); // recurse }, 2000); } //initialize loopsie(); http://jsfiddle.net/G4PTM/6/
You can use CSS transitions. The example below fades .layer0 in and out in a timespan of 500 ms: div.layer0 { opacity: 1; -webkit-transition:opacity 500ms ease-out; -moz-transition:opacity 500ms ease-out; -o-transition:opacity 500ms ease-out; transition:opacity 500ms ease-out; } div.layer0:hover { opacity: 0; }