Impressive Slideshow with Stunning 3D Effect

in tutorials by Mirza Hodzic5 min read
Impressive Slideshow with Stunning 3D Effect
DEMOGET CODE

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.

Impressive Slideshow with Stunning 3D Effect - Sketch

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

More like this

Ready for more? Here are some related posts to explore