Impressive Slideshow with Stunning 3D Effect
When creating this interesting slideshow with 3D animation, we will use CSS, Javascript, and the javascript library GSAP (GreenSock Animation Platform). And no matter how complicated it may seem, in the next few minutes, you will be able to read and see that it's actually a very ordinary and simple code. Of course, we will break down the code into shorter snippets as usual and try to explain each of them in the simplest way possible.
Are you ready? Coffee cup on the table, music playing in the background? Let's get started! :)
HTML Structure
First, let's define the HTML structure for our slideshow.
This part represents the navigation section of the slideshow. It consists of two buttons, with IDs prev
and next
, wrapped inside a nav
element. Each button has an ID and a class, along with a data attribute called data-increment
. The data-increment
attribute helps us determine the direction in which the animation will be performed. We have set it to 1
for the next
button, animating the slider forward, and -1
for the prev
button, animating the slider backward.
1<!-- wannabedev:slideshow:navigation START -->
2<nav id="nav">
3 <button id="prev" class="nav-item" data-increment="-1">
4 <img src="assets/img/icon-prev.png" alt="prev" />
5 </button>
6 <button id="next" class="nav-item" data-increment="1">
7 <img src="assets/img/icon-next.png" alt="next" />
8 </button>
9</nav>
10<!-- wannabedev:slideshow:navigation END -->
We will use a section
element with the class wannabedev-3d-carousel
to contain the slideshow items. Each item will consist of a background image, a content container, and various elements such as description, headline, and a "Discover More" link.
1<!-- wannabedev:slideshow START -->
2<section class="wannabedev-3d-carousel">
3 <div class="stage">
4
5<!-- wannabedev:slideshow:item START -->
6 <div class="item">
7 <div class="bcg" style="background-image: url(path/to/the/image.jpeg);"></div>
8 <div class="content">
9 <div class="description">
10 <p class="description-target">1483 - 1843</p>
11 </div>
12 <div class="headline">
13 <h2 class="headline-target">Dream big</h2>
14 </div>
15 <div class="discover-more">
16 <a href="#" class="discover-more-target">Discover More</a>
17 </div>
18 </div>
19 </div>
20 <!-- wannabedev:slideshow:item END -->
21
22</section>
23<!-- wannabedev:slideshow END -->
This structure is repeated for each item in the slideshow, with different content and background images.
In our example, there must be exactly 3 slides in the slideshow. If you want to change the number of slides, you need to make certain changes not only in the HTML code but also in the JavaScript code.
JavaScript Functionality
Now, let's break down the JavaScript code into smaller snippets and provide a better explanation of the functions that bring life to our slideshow.
The code begins by declaring various variables, such as carousel
to select the carousel container, loader
to select the loader element, timeline
to define a GSAP timeline, boxes
to select the carousel items, stage
to select the stage/container of the carousel, and angle
to determine the rotation angle for each item. Additionally, there are variables for selecting description, headline, and discover more elements within each carousel item.
1const carousel = document.querySelector('.wannabedev-3d-carousel');
2const loader = document.querySelector('.loader');
3const timeline = gsap.timeline();
4const boxes = document.querySelectorAll('.item');
5const stage = document.querySelector('.stage');
6const nav = document.querySelector('#nav');
7const angle = 360 / 3;
8const descTarget = document.querySelectorAll('.description-target');
9const headTarget = document.querySelectorAll('.headline-target');
10const discTarget = document.querySelectorAll('.discover-more-target');
Here's a brief explanation of each variable.
- carousel - carousel container element.
- loader - loader element.
- timeline - GSAP timeline used for sequencing animations.
- boxes - array of carousel item elements.
- stage - stage element where the 3D transformations occur.
- nav - navigation element.
- angle - angle (in degrees) between each carousel item.
- descTarget - array of elements that hold the description content.
- headTarget - array of elements that hold the headline content.
- discTarget - array of elements that hold the "Discover More" content.
If you want to change the number of slides in your slideshow, you will also need to adjust the angle
variable according to your needs.
We then need to defines two functions: handleNextClick
and handlePrevClick
, which handle the animation when the next and previous buttons are clicked, respectively. Inside these functions, the GSAP timeline (timeline) is used to create a sequence of animations. These animations involve modifying the position, size, rotation, and visibility of the carousel items and associated content.
1const handleNextClick = () => { ... }
2const handlePrevClick = () => { ... }
In handleNextClick
, a timeline is created using GSAP. The timeline
object represents the GSAP timeline and chains multiple animations together.
First, we perform animations on the headTarget
, descTarget
, and discTarget
elements, specifically the Reveal Animation. We achieved this effect by adding overflow: hidden
to the parent div using CSS styles. For the target elements themselves, we updated the vertical position to 100%
if we click on the next
button and -100%
if we click on the prev
button.
After that, we move on to animating the entire slide, including the background image. This part of the animation targets the elements specified by boxes
and animates multiple properties to specific values, such as scaling down the slide and rotating it. Once this animation is complete, we need to reset the variable values to their initial state.
1// …
2
3const handleNextClick = () => {
4 timeline
5 .to(descTarget, { duration: 0.5, yPercent: 100, ease: `power2.out`, stagger: 0.025 })
6 .to(headTarget, { duration: 0.5, yPercent: 100, ease: "power2.out", stagger: 0.025 }, "-=0.5")
7 .to(discTarget, { duration: 0.5, yPercent: 100, ease: "power2.out", stagger: 0.025 }, "-=0.5")
8 .to(boxes, { duration: 0.5, width: "80%", height: "80%", top: "10%", right: "10%", bottom: "10%", left: "10%", ease: "power2.out", stagger: 0.1 })
9 .to(boxes, { duration: 1, rotationX: (index, element) => {
10 const y1 = +element.dataset.rotationX;
11 const y2 = y1 - angle;
12 element.dataset.rotationX = y2;
13 return y2;
14 }, ease: "expo.inOut", stagger: 0 })
15 .to(boxes, { duration: 0.5, width: "100%", height: "100%", top: "0%", right: "0%", bottom: "0%", left: "0%", ease: "power2.out", stagger: 0.1 }, "+=0.3")
16 .to(descTarget, { duration: 0.5, yPercent: 0, ease: "power2.out", stagger: 0.025 }, "-=0.5")
17 .to(headTarget, { duration: 0.5, yPercent: 0, ease: "power2.out", stagger: 0.025 }, "-=0.5")
18 .to(discTarget, { duration: 0.5, yPercent: 0, ease: "power2.out", stagger: 0.025 }, "-=0.5");
19};
The handlePrevClick
function follows a similar structure but animates the elements in the opposite direction to create a slide-up effect. As mentioned in the text, we update the vertical position to -100%
.
1// …
2
3const handlePrevClick = () => {
4 timeline
5 .to(descTarget, { duration: 0.5, yPercent: -100, ease: "power2.out", stagger: 0.025 })
6 .to(headTarget, { duration: 0.5, yPercent: -100, ease: "power2.out", stagger: 0.025 }, "-=0.5")
7 .to(discTarget, { duration: 0.5, yPercent: -100, ease: "power2.out", stagger: 0.025 }, "-=0.5")
8 .to(boxes, { duration: 0.5, width: "80%", height: "80%", top: "10%", right: "10%", bottom: "10%", left: "10%", ease: "power2.out", stagger: 0.1 })
9 .to(boxes, { duration: 1, rotationX: (index, element) => {
10 const y1 = +element.dataset.rotationX;
11 const y2 = y1 + angle;
12 element.dataset.rotationX = y2;
13 return y2;
14 }, ease: "expo.inOut", stagger: 0 })
15 .to(boxes, { duration: 0.5, width: "100%", height: "100%", top: "0%", right: "0%", bottom: "0%", left: "0%", ease: "power2.out", stagger: 0.1 }, "+=0.3")
16 .to(descTarget, { duration: 0.5, yPercent: 0, ease: "power2.out", stagger: 0.025 })
17 .to(headTarget, { duration: 0.5, yPercent: 0, ease: "power2.out", stagger: 0.025 }, "-=0.5")
18 .to(discTarget, { duration: 0.5, yPercent: 0, ease: "power2.out", stagger: 0.025 }, "-=0.5");
19};
The code inside the imagesLoaded
function is executed once the carousel images are fully loaded. It hides the loader element, sets the necessary CSS properties for the stage element to enable 3D transformations, and assigns the initial rotation values to each carousel item.
1// …
2
3imagesLoaded(carousel, { background: true }, () => {
4 // hide loader
5 loader.classList.add('is-loaded');
6
7 // set stage properties
8 gsap.set(stage, { perspective: "86vh", transformStyle: "preserve-3d" });
9 if (navigator.userAgent.search("Safari") >= 0 && navigator.userAgent.search("Chrome") < 0) {
10 gsap.set(stage, { perspective: "8000px", transformStyle: "preserve-3d" });
11 }
12
13 // set data rotation for each item
14 boxes.forEach((element, index) => {
15 const rotationX = index * angle;
16 gsap.set(element, { rotationX });
17 element.dataset.rotationX = rotationX;
18 });
19
20 // …
21});
At the end, we also add click event listeners to the next
and previous
buttons, which trigger the respective functions.
1imagesLoaded(carousel, { background: true }, () => {
2 // …
3
4 // click on next item
5 nav.querySelector('#next').addEventListener('click', handleNextClick);
6
7 // click on prev item
8 nav.querySelector('#prev').addEventListener('click', handlePrevClick);
9});
I hope we have managed to roughly explain the logic behind our 3D Slideshow and that you have found our tutorial helpful. If that's the case, we would be delighted if you visit us again soon for more of such (and even better) tutorials. Enjoy and see you soon! :)
Credits
- Simple Loader by loading.io
- The inspiration for effects comes from Nicholas Ødegaard
- GSAP by Greensock
- imagesLoaded by David DeSandro
- Font Montserrat by Google Fonts
- Color Palette by coolors.co
- Arrow Icons made by Lyolya from Flaticon are licensed by CC 3.0