Reputation:
I'm working on an image fader. I have it so that it fades the image (or in this case div with a background color) selected by the getElementById
method, but I'm wondering how I can modify the JavaScript code to select all div elements and fade out, one after the other.
var c = document.getElementById("one");
function fade(){
if (!c.style.opacity){
c.style.opacity = 1;
};
var interval = setInterval (fadeout, 200);
function fadeout(){
c.style.opacity -= .05;
if (c.style.opacity <= 0){
clearInterval(interval);
};
};
};
fade();
#container{
position: relative;
margin: 0 auto;
text-align: center;
width: 350px;
height: 350px;
background-color: rgb(200,200,200);
}
.inner{
position: absolute;
margin: 0 auto;
left: 25px;
top: 25px;
width: 300px;
height: 300px;
}
#one{
background-color: rgb(100,100,100);
}
#two{
background-color: rgb(0,160,230);
}
#three {
background-color: rgb(0,255,130);
}
#four{
background-color: rgb(255,130,255);
}
<div id="container">
<div class="inner" id="one"></div>
<div class="inner" id="two"></div>
<div class="inner" id="three"></div>
<div class="inner" id="four"></div>
</div>
I also need a way (possibly making the function recursive) to keep going from four back to one, and I will not use jQuery for this.
Upvotes: 1
Views: 1496
Reputation: 90058
This is what you're looking for:
var interval = 4000,
collection = document.querySelectorAll('#container>.inner');
for (var i = 0; i < collection.length; i++) {
(function(i) {
setTimeout(function() {
collection[collection.length - i - 1].style.opacity = 0;
}, i * interval);
})(i)
}
#container {
position: relative;
margin: 0 auto;
text-align: center;
width: 350px;
height: 350px;
background-color: rgb(200, 200, 200);
}
.inner {
position: absolute;
margin: 0 auto;
left: 25px;
top: 25px;
width: 300px;
height: 300px;
transition: opacity 4s linear;
}
#one {
background-color: rgb(100, 100, 100);
}
#two {
background-color: rgb(0, 160, 230);
}
#three {
background-color: rgb(0, 255, 130);
}
#four {
background-color: rgb(255, 130, 255);
}
<div id="container">
<div class="inner" id="one"></div>
<div class="inner" id="two"></div>
<div class="inner" id="three"></div>
<div class="inner" id="four"></div>
</div>
To give it a more natural feel (impression of intent, gesture, livelyness) you need to change linear
to the easing of your choice in CSS:
var interval = 4000,
collection = document.querySelectorAll('#container>.inner');
for (var i = 0; i < collection.length; i++) {
(function(i) {
setTimeout(function() {
collection[collection.length - i - 1].style.opacity = 0;
}, i * interval);
})(i)
}
#container {
position: relative;
margin: 0 auto;
text-align: center;
width: 350px;
height: 350px;
background-color: rgb(200, 200, 200);
}
.inner {
position: absolute;
margin: 0 auto;
left: 25px;
top: 25px;
width: 300px;
height: 300px;
transition: opacity 4s cubic-bezier(.6, 0, .3, 1);
}
#one {
background-color: rgb(100, 100, 100);
}
#two {
background-color: rgb(0, 160, 230);
}
#three {
background-color: rgb(0, 255, 130);
}
#four {
background-color: rgb(255, 130, 255);
}
<div id="container">
<div class="inner" id="one"></div>
<div class="inner" id="two"></div>
<div class="inner" id="three"></div>
<div class="inner" id="four"></div>
</div>
Principle: whenever you want to stagger animations (repeat exact same animation on a collection of elements), chaining the start of one animation upon the end of the previous is, to say the least, improper. You are limiting yourself to always having to wait for one animation to end in order to start the next. But you normally want this behavior when the duration of an action is unknown at the time you are executing the script. Here, it's the same duration so chaining is only a useless limitation.
Instead, use setTimeout()
on the collection and you will end up having the option of animating multiple elements at any given moment and having better (separated) controls over animation details (duration, timing function and stagger step).
Another big problem with your approach is you're making 20 changes for each element, instead of only one. CSS animations give you control over animation-duration
, animation-timing-function
(very important when animating movement) and animation-delay
(not used here). Besides, it's smoother and lighter on the browser.
I'll exemplify below, by animating movement instead of opacity:
// only set the stagger step and the collection in js
// set animation duration and easing in CSS for best results
var interval = 63, // stagger step
collection = document.querySelectorAll('div');
// in your case, use document.querySelectorAll('#container>.inner');
for (var i = 0; i < collection.length; i++) {
(function(i) {
setTimeout(function() {
collection[i].style.transform = 'translateX(0)';
}, i * interval);
})(i)
}
div {
background-color: #ddd;
margin-top: 1px;
min-height: .5rem;
transform: translateX(100%);
/* here it is: duration 300ms, and transition
* I've put in cubic, as it's good for staggered movement
* it looks natural. For opacity it doesn't matter
* it can be linear, ease-out, anything.
*/
transition: transform 1s cubic-bezier(.5, 0, .1, 1);
}
body {
overflow-x: hidden;
margin: 0;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div></div> <div></div> <div></div>
<div></div> <div></div> <div></div>
<div></div> <div></div> <div></div>
<div></div> <div></div> <div></div>
<div></div> <div></div> <div></div>
<div></div> <div></div> <div></div>
<div></div> <div></div> <div></div>
<div></div> <div></div> <div></div>
Initial answer, before noticing a jQuery free solution is required:
// only set the stagger step and the collection in js
// set animation duration and easing in CSS for best results
var interval = 42, // stagger step
collection = $('div'); // in your case, use $('#container>.inner');
collection.each(function(i) {
setTimeout(function() {
collection.eq(i).css('opacity', 0);
}, i * interval);
})
div {
background-color: #ddd;
margin-top: 1px;
min-height: .5rem;
/* here it is: duration 300ms, and transition
* I've put in cubic, as it's good for staggered movement
* it looks natural. For opacity it doesn't matter
* it can be linear, ease-out, anything.
*/
transition: opacity 300ms cubic-bezier(.4, 0, .2, 1);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div></div> <div></div> <div></div>
<div></div> <div></div> <div></div>
<div></div> <div></div> <div></div>
<div></div> <div></div> <div></div>
<div></div> <div></div> <div></div>
<div></div> <div></div> <div></div>
<div></div> <div></div> <div></div>
<div></div>
End note: This answer did not even touch the new (and promising) way to animate: Web Animations Api, because it's still experimental tech.
WAA animates using chaining but provides methods to cancel animation requests. It works in all major browsers and can be used with polyfill in older browsers.
It will probably gain huge popularity once Android browsers will support it. Until then, the best way to animate is using CSS
animations and transitions which, even if not declaratively, also support canceling a request: you just change the property and it automatically starts a new animation on the same property from the current state, canceling the old one. The only CSS current limitation is that it won't calculate the new duration based on how much of the old one has been completed, which is possible in WAA.
What WAA does is unifying CSS animations, CSS transitions and SVG animations under one, common set of specs and will provide a unified set of methods for controlling animations.
Upvotes: 6