Introduction
So far in this tutorial, we've seen a fairly decent number of features being added to our simple slider, including navigation buttons, pagination, autoplay and progress bars. Now is the time to consider some stunning CSS skills at work!
In this unit, we shall discover how to get the slides of our slider to navigate with smooth and elegant transitional effects.
Particularly, we'll be looking over the following set of effects:
- Fade-out-fade-in — the current slide fades out and then the new slide fades in.
- Fade-out — the current slide fades out revealing the new slide.
- Fade-out-slide-in — the current slide fades out and at the same time, the new one slides in.
- Slide — the most basic sliding effect.
In practice, there are literally tons of effects that can be given to a slider such as squeeze, 3D box, flip, rotate and so on.
We consider only a few of them, because either the rest are difficult to code and not worth the grunt work, or simply because they require the most latest and experimental technologies on the browser, that will otherwise fail on older bundles.
Even effects such as fade, slide, etc. may seem easy from the outside, but programatically implementing them in the most compatible way is indeed a task which requires a decent amount of coding and designing experience.
Anyways, it has been a lot of talking, so without wasting any more time, let's dive right into constructing our first effect — fade-out-fade-in.
How the effect works
Before we can write the code to power the fade-out-fade-in effect, we first need to understand exactly how it works.
Once we understand its idea, only then can we go on and convert that to a real implementation.
So, here's how the effect works:
Hence the name, 'fade-out fade-in'.
Now with this description in mind, we would like to get you to think about how to implement it in code, using a bunch of questions...
Which of the following CSS properties would we need for fading out a slide?
opacity
color
transform
Which of the following CSS properties would we need for fading out a slide?
transition
visibility
- Both A) and B)
We'll essentially need only three CSS properties to power a fade effect:
- The
opacity
property to control the transparency of the slide. - The
visibility
property to hide/show the slide. - The
transition
property to change both the properties above in a smooth manner.
Technically, visibility
is not needed for the fade effect — the effect will work the same with just opacity
and transition
as well.
The visibility
property is simply given to hide/show the element being faded. Without visibility
, if the element is faded out, it won't be visible but still remain there. Any buttons or other interactive buttons inside the element would still be clickable. With visibility
, this can be very easily rectified.
Anyways, now that we know what tools to use to construct the fade effect, let's get to the real work.
Changing the CSS
Before we dive into the JavaScript, it's worthwhile to spend a few minutes thinking on whether we need to change our CSS for the sake of implementing this effect.
What do you think? Do we need to change the CSS?
Well, clearly we do. Let's see why...
As we know already by this point, to implement the fade transitions desired in this navigation effect, we need to use CSS transitions. However, the CSS setup that we currently have on each slider element doesn't go well with transitions.
What we are trying to emphasize over here is the display
property set on the .slider_slide
element. display
is not an animatable property per se, and consequently messes up any CSS transitions and animations.
Since we need to use transitions, we have to do something about this display
property. If we stick with it, as we have been thus far in this tutorial, we won't be able to create the beautiful fade-in-fade-out effect at all!
Now the most straightforward thing is to get rid of it completely from the style rule of .slider_slide
.
But then how would we show the first slide only and hide all the rest of them?
Good question. Fortunately, CSS has a very nice solution — use the visibility
property (which we'll already need in the fade effects).
So what we could do instead of using display
on .slider_slide
is to set visibility: hidden
on .slider_slide
and then use visibility: visible
on .slider_slide:first-child
.
.slider_slide {
width: 100%;
visibility: hidden;
}
.slider_slide:first-child {
visibility: visible;
}
To our surprise, this doesn't solve the problem.
Surely, the other slides aren't visible but they still consume area on the webpage. This is just the way visibility
works, i.e. it hides the given element but still uses it when rendering the page.
Now how to solve this problem?
At this stage, we need to gather a little bit of foresight to implement the fade-out-fade-in effect. That foresight will not just ease the implementation of the effect in a while, but also help us solve this visibility
problem.
That is, instead of going with the default block display of the slides, we could stack them on top of each other using some CSS code. In this way, even if the slides below the first slide consume area, that won't be noticeable as they are all on top of each other sharing the same region.
So the problem of hiding all the slides correctly has been effectively reduced down to stacking them on top of each other.
And this raises the question: how to stack the slides on top of each other?
We encourage you to pause here and try accomplishing this task. It'll really prove how skilled you are in CSS.
Assuming you've tried it on your own, let's now write the CSS together.
The first step is to set position: absolute
on .slider_slide
to remove all the slides from the normal layout used in HTML and then position them to the top-left corner using top: 0
and left: 0
:
.slider_slide {
width: 100%;
visibility: hidden;
position: absolute;
top: 0;
left: 0;
}
.slider_slide:first-child {
visibility: visible;
}
After applying this code, the first slide suddenly consumes the entire viewport width, while the parent of .slider_slide
, which is .slider_slides-cont
, gets reduced down to a height of 0
, taking all the elements after it (i.e. the pagination and navigation buttons) behind the slide.
Check out this below:
This is simply because when position: absolute
is applied on a given element, the element gets positioned relative to the root <html>
element. To change this behavior, we ought to set position: relative
on the element relative to which we want the positioning to happen.
In our case, we'll set position: relative
on .slider_slides-cont
since that's what holds all the .slider_slide
elements.
Consider the following code:
.slider_slides-cont {
position: relative;
}
Now let's see the slider:
Clearly, one thing has been solved but not the other. Still the pagination and navigation buttons are hidden behind the slides.
What could we do to solve this problem?
Well, let's see the reason of the problem. By applying position: absolute
on .slider_slide
, the height of their parent .slider_slides-cont
has been reduced down to 0
. Hence, everything after .slider_slides-cont
gets positioned as if it wasn't there at all (due to the zero height).
So to prevent this from happening, we just have to provide a custom height value to .slider_slides-cont
. That's it.
But what height to give?
That's another good question. Programming is just full of solving problems arising again and again.
Well, technically, the height depends on the nature of the content inside the slides.
For instance, in our case, the slides have images in them in the 16:9 aspect ratio, hence it seems sensible to give a height that leads to this width:height ratio for the images.
In another case, we might want to give a height that span the entire viewport, or maybe even just fix it to a constant non-responsive value like height: 300px
.
In our case, we'll go with a responsive height, that scales up or down with the width of the viewport. And it won't be given via the height
property, but instead via the padding-bottom
property.
padding-bottom
gets computed relative to the width of the element on which it's applied, in contrast to height
whose computation never happens relative to the width of the element.padding-bottom
, we could also use padding-top
here.The value that we'll choose for padding-bottom
will be such that it gives a 16:9 aspect ratio to the slides.
Can you think what this value would be? It's just basic math.
It'll be 56.25%
.
Let's add this to our CSS:
.slider_slides-cont {
position: relative;
padding-bottom: 56.25%;
}
Superb.
Now the slides don't hide the pagination and navigation buttons, all thanks to the padding-bottom: 56.25%
declaration.
The last thing left here is just to update the navigateSlider()
function to modify visibility
instead of the display
property of the respective slides.
This is accomplished below:
function navigateSlider() {
/* ... */
slideElements[currentIndex].style.visibility = "hidden";
slideElements[newIndex].style.visibility = "visible";
currentIndex = newIndex;
}
Now we can be 100% sure that the slider works perfectly before we move on to implement the logic of the fade-out-fade-in effect.
Implementing the effect
As we know from what we've covered so far in this tutorial, navigateSlider()
encapsulates all the navigation logic of our slider.
The statements hiding the current slide and then showing the new one, all go inside this function.
Consequently, it shouldn't sound surprising that we'll need to focus on navigateSlider()
while developing any sort of effect for our slider's navigation.
So, with this thing in mind, let's review what happens in the fade-out-fade-in effect when we navigate our slider.
We'll start by tackling the first part of the effect i.e. fading out the current slide.
In order to fade out the current slide, we simply need to set the following properties on it: opacity: 0
, visibility: hidden
and finally transition
.
As per the parameters for transition
, we can go with any as we desire. For now, we'll go with a transition duration of 0.5s
and the timing function linear
.
Now to set these properties on the current slide, we can go with a straightforward approach — add them using the JavaScript style
property.
Shown below is the navigateSlider()
function with the statements to fade out the current slide:
function navigateSlider() {
/* ... */
// Fade out the current slide.
slideElements[currentIndex].style.opacity = '0';
slideElements[currentIndex].style.visibility = 'hidden';
slideElements[currentIndex].style.transition = '0.5s linear';
currentIndex = newIndex;
}
Note that the statements hiding and showing the current and new slides, respectively, in the previous definition of navigateSlider()
ought to be removed before putting this new bunch of statements in there.
After all, we are changing the way the navigation happens, and likewise have to change the code that performs it.
Anyways, now it's time to deal with the second part of the effect i.e. fading in the new slide.
The properties to be added to the new slide are exactly the same as with the current one, i.e. opacity
, visibility
and transition
, except for their values.
Besides this, there is one more thing different for the new slide. That is, it has be faded in only after the current slide has been faded out. In other words, there has to be a delay in its appearance.
How can we give this delay? Any ideas?
There are two options: use the setTimeout()
function or the CSS transition-delay
property.
What do you think is easier?
Well clearly it's the latter option i.e. using the transition-delay
property of CSS.
With setTimeout()
we have to first create a new function, put the fade-in logic for the new slide within it, and then specify the delay (which is the transition duration of the the fade-out effect of the first slide) as an argument to it.
Comparing this to the CSS way, where we just have to write the value 0.5s
in the transition-delay
property (or even as a parameter to transition
), we see that CSS surely outruns JavaScript in terms of simplicity for this problem's solution.
0.5s
in our case, we'll need the same transition delay of 0.5s
on the new slide. After this delay, the new slide will witness the fade-in effect right when the current slide has completely faded out.Altogether, we get to the following addition in the navigateSlider()
function:
function navigateSlider() {
/* ... */
// Fade out the current slide.
slideElements[currentIndex].style.opacity = '0';
slideElements[currentIndex].style.visibility = 'hidden';
slideElements[currentIndex].style.transition = '0.5s linear';
// Fade in the new slide.
slideElements[newIndex].style.opacity = '1';
slideElements[newIndex].style.visibility = 'visible';
slideElements[newIndex].style.transition = '0.5s linear 0.5s';
currentIndex = newIndex;
}
And this completes our effect! Check it out in the link below.
Separation of concerns
Despite the fact that the effect we created above is working perfectly, it is usually recommended to keep CSS away from JavaScript. This is formally known as separation of concerns.
It's hard to maintain CSS in two places (stylesheets and scripts) and is therefore recommended that developers keep CSS limited to stylesheets.
In the code above, we change the styles of the current and new slides manually using the style
property. This is an example of CSS in JavaScript, and should be avoided as much as we can.
But how to avoid it?
Well, we already know of the answer — just use HTML classes.
Instead of giving certain styles to elements directly using the style
property, we define those styles in HTML classes and then add those classes to the elements in order to apply the styles.
This separates both the concerns — the styles go in CSS stylesheets whereas the logic goes in the JavaScript files.
Alright, let's now think about what class names should we come up with that clearly indicate the purpose of their underlying styles.
The current slide is faded out; so we'll call the class that represents a faded-out slide as .slider_slide--faded-out
(following the BEM naming convention we've been using throughout our slider's markup).
Here is the definition of .slider_slide--faded-out
:
.slider_slide--faded-out {
opacity: 0;
visibility: hidden;
transition: 0.5s linear
}
These are the same styles that we gave to the current slide in the navigateSlider()
function above in lines 5 - 7.
Over to dealing with the new slide.
The new slide is faded in; thereby, we'll call the class that represents a faded-in slide as .slider_slide--faded-in
.
Below shown is the definition of .slider_slide--faded-in
:
.slider_slide--faded-in {
opacity: 1;
visibility: visible;
transition: 0.5s linear 0.5s
}
These are the same styles that we gave to the new slide in the navigateSlider()
function above in lines 10 - 12.
With these classes in place, the last thing we have to do is to get them added to the respective slides. And this would obviously be done in our same old navigateSlider()
function.
The current slide gets the class .slider_slide--faded-out
while the new one gets the class .slider_slide--faded-in
:
function navigateSlider() {
/* ... */
// Fade out the current slide.
slideElements[currentIndex].classList.add("slider_slide--faded-out");
// Fade out the new slide.
slideElements[newIndex].classList.add("slider_slide--faded-in");
currentIndex = newIndex;
}
Let's try out the slider made by this class toggle logic.
Notice any issues?
The fade-out-fade-in effect has just gone away. We only get a fade-out effect on the first two navigations, after which even that goes away!
There is definitely something wrong with this slider.
It's time to think about it...
Below we break down each step that occurs when we navigate the slider for the first time to the right after the page load, to understand where the problem lies.
Understanding the problem in the code above
Initially, when the page is loaded, the first slide gets the classes .slider_slide--faded-out
and .slider_slide--faded-in
added due to the call to navigateSlider()
, at the end of the script.
Now when we navigate forward by pressing the Next → button, the first slide (which is the current slide) gets the class .slider_slide--faded-out
added, while the second slide (the new slide) gets the class .slider_slide--faded-in
added.
Since .slider_slide--faded-out
is already there on the first slide, its addition has no effect, and we witness no fade-out effect. It's only the fade in effect that's witnessed.
Going forward once again, the second slide (current one) gets the class .slider_slide--faded-out
while the third slide (new one) gets the class .slider_slide--faded-in
.
Since the second slide already has the class .slider_slide--faded-in
on it, its transition is delayed for 0.5s. As a result, no fade-out effect is seen until 0.5s; at which point the third slide simultaneously fades in as well.
Now from this point onwards, the navigations have no effect — we witness no effect at all.
This happens simply because now all the slides have both the classes .slider_slide--faded-out
and .slider_slide--faded-in
already on them, and so adding them don't cause any effect.
Reading this long block of text, we can clearly see that the problem is coming from adding the two classes. Or better to say, the problem is coming from the way we are adding the two classes.
The solution is pretty straightforward — when a class is added to a slide, the other class is to be removed from it. In this way, we can be rest assured that at any point, each slide has exactly one class on it.
In the following code, we accomplish this very idea:
function navigateSlider() {
/* ... */
// Fade out the current slide.
slideElements[currentIndex].classList.add("slider_slide--faded-out");
slideElements[currentIndex].classList.remove("slider_slide--faded-in");
// Fade in the new slide.
slideElements[newIndex].classList.add("slider_slide--faded-in");
slideElements[newIndex].classList.remove("slider_slide--faded-out");
currentIndex = newIndex;
}
The current slide gets the class .slider_slide--faded-out
, so we remove the other one from it i.e .slider_slide--faded-in
. Similarly, the new slide gets the class .slider_slide--faded-in
, so we remove the other one from it i.e .slider_slide--faded-out
.
Let's now see the slider.
Woah. Even after this long discussion, there is still a problem in the slider.
To our surprise, this time the problem lies in the CSS code.
Can you figure it out?
Well, the .slider_slide:first-child
selector is the culprit here.
The .slider_slide:first-child
selector has a higher precedence than .slider_slide--faded-out
and .slider_slide--faded-in
. This simply means that when we apply either of these classes to the first slide, they have no effect on it.
The solution is to simply increase the precedence of the styles applied to .slider_slide--faded-out
and .slider_slide--faded-in
.
And that could be done by prefixing both these class selectors in the CSS code with the .slider_slide
class selector.
The following code demonstrates this:
.slider_slide.slider_slide--faded-out {
opacity: 0;
visibility: hidden;
transition: 0.5s linear
}
.slider_slide.slider_slide--faded-in {
opacity: 1;
visibility: visible;
transition: 0.5s linear 0.5s
}
Now let's try our slider.
Voila! All our efforts have paid back.