Sliced Slideshow with Infinite Horizontal Scrolling Navigation

in tutorials by Mirza Hodzic7 min read
Sliced Slideshow with Infinite Horizontal Scrolling Navigation
DEMOGET CODE

The title itself might suggest that this time we'll have a complicated tutorial. 🤪 No, honestly - I wouldn't exactly say it's going to be complicated. I'd rather say it's going to be a bit longer than a few previous ones, but don't worry - this tutorial won't be boring either, I promise.

I will make an effort to break the code into as many small parts as possible and explain each of them in detail. Subconsciously, this time I've also written JavaScript code, through a multitude of smaller JavaScript functions. We will present each of them separately in the following text and explain each one, so that after scrolling to the bottom of this page, there won't be any ambiguities.

Sliced Slideshow with Infinite Horizontal Scrolling Navigation - Sketch

As the title suggests, this time we will attempt to create a sliced slideshow, followed by infinite horizontal scrolling navigation, and finally, connect these two parts through a simple click event. We will create animations using the JavaScript animation library GreenSock (GSAP).

HTML structure and a bit of styling

As usual, we will begin the tutorial by presenting a simplified HTML structure. We will showcase the most important containers and highlight those that we will call and use in our JavaScript section in one way or another.

1<!-- slider START -->
2<section class="wannabedev-slider">
3  <!-- slider:container START -->
4  <div class="slides-container">
5    <!-- … -->
6  </div>
7  <!-- slider:container START -->
8
9  <!-- slider:navigation START -->
10  <div class="slides-navigation">
11    <!-- … -->
12  </div>
13  <!-- slider:navigation END -->
14</section>
15<!-- slider-holder END -->

In the section that will serve as a container for both parts - the sliced slideshow and the infinite scrolling horizontal navigation, we will add a custom class wannabedev-slider.

If you change the name of this class, don't forget to make the change in the JavaScript part as well.

1<!-- slider START -->
2<section class="wannabedev-slider">
3
4  <!-- slider:container START -->
5  <div class="slides-container">
6
7    <!-- slider:container:slide START -->
8    <article id="slide-01" class="slide is-active" data-bcg="assets/img/img-1.jpeg" data-color="#F6F7EB">
9      <div class="slide__background"></div>
10      <div class="slide__background_2"></div>
11    </article>
12    <!-- slider:container:slide END -->
13
14    <!-- slider:container:slide START -->
15    <article id="slide-02" class="slide" data-bcg="assets/img/img-2.jpeg" data-color="#DCB239">
16      <div class="slide__background"></div>
17      <div class="slide__background_2"></div>
18    </article>
19    <!-- slider:container:slide END -->
20
21    <!-- … -->
22  </div>
23  <!-- slider:container START -->
24
25  <!-- slider:navigation START -->
26  <div class="slides-navigation">
27    <div class="wrapper">
28      <p>
29        <span><a class="main-nav__link is-active" href="#slide-01" data-id="0">ART DIRECTION</a></span>
30        <span><a class="main-nav__link" href="#slide-02" data-id="1">STRATEGY</a></span>
31        <!-- … -->
32      </p>
33    </div>
34  </div>
35  <!-- slider:navigation END -->
36
37</section>
38<!-- slider-holder END -->

Inside our slideshow section, as already mentioned, we will have two div elements that will serve as containers for our two slideshow components. To the first one, where we will actually place our sliced slideshow, we will add the class "slides-container", and to the second one, where we will place our navigation, we will add the class "slides-navigation".

1<!-- slider:container START -->
2<div class="slides-container">
3
4  <!-- slider:container:slide START -->
5  <article id="slide-01" class="slide is-active" data-bcg="assets/img/img-1.jpeg" data-color="#F6F7EB">
6    <div class="slide__background"></div>
7    <div class="slide__background_2"></div>
8  </article>
9  <!-- slider:container:slide END -->
10
11  <!-- slider:container:slide START -->
12  <article id="slide-02" class="slide" data-bcg="assets/img/img-2.jpeg" data-color="#DCB239">
13    <div class="slide__background"></div>
14    <div class="slide__background_2"></div>
15  </article>
16  <!-- slider:container:slide END -->
17
18  <!-- … -->
19</div>
20<!-- slider:container START -->

As you can see in the code above, the slideshow will have several items, and each of them will have the class "slide", which will be added using the article HTML element. Additionally, we will add several data attributes to each slide, which will later make it easier for us to program the functionality.

The attributes we will add to each slide are data-bcg, which will contain the path to the background image, and data-color, which will contain the HEX color code that we will use to color and update the background of our slideshow later.

Upon loading, we will add the class "is-active" to the first of the slides. This means that the first slide will be activated on load, and its background image and color will be activated immediately after closing the loading screen, serving as default values.

1<!-- slider:navigation START -->
2<div class="slides-navigation">
3  <div class="wrapper">
4    <p>
5      <span><a class="main-nav__link is-active" href="#slide-01" data-id="0">ART DIRECTION</a></span>
6      <span><a class="main-nav__link" href="#slide-02" data-id="1">STRATEGY</a></span>
7      <span><a class="main-nav__link" href="#slide-03" data-id="2">BRAND IDENTITIES</a></span>
8      <!-- … -->
9    </p>
10  </div>
11</div>
12<!-- slider:navigation END -->

You can see the HTML part for adding navigation in the code above. Why does this part have a somewhat unusual HTML structure? I will try to explain it in the next few sentences. 😀

The position holder for our navigation is the first parent div with the class "slides-navigation", and this part will have no connection to horizontal text scrolling. By adding overflow: hidden, we will prevent the entire slideshow from displaying a scrollbar, and white-space: nowrap will allow all navigation items to be lined up one after another in a single row.

1.slides-navigation {
2  position: absolute;
3  bottom: 13vh;
4  white-space: nowrap;
5  margin: 1em 0em;
6  padding-bottom: 8px;
7  overflow: hidden;
8}

The first child element is marked with the "wrapper" class, and this is the element to which we will add the infinite horizontal scroll effect.

Inside this div HTML element, we will have a p element that we will fill with our navigation items. We will use JavaScript code to copy this p element once on load and immediately add the copy right after it. Why?

In this way, we will ensure that the navigation section has a greater width than the viewport width. The reason for this is that when the navigation is horizontally scrolled, and an item begins to exit the viewport, we will add item by item from the other side to the viewport. With this magic, we will create an effect that will appear as infinite horizontal scrolling.

There might be a better way to program this part, and I am confident that with the help of GSAP, this section can be programmed even more easily. But I'll leave that part for you to try and decide.

JavaScript functionality

As I already mentioned, we will create all the functionality and animations using the JavaScript Animation Library - GSAP, Greensock. Additionally, we will use some other JavaScript Libraries, such as ImagesLoaded which will assist us in loading the web page and, in connection with that, removing the loading screen when all the images used in the demo are loaded.

Let's break down the JavaScript code and provide a brief description of each function.

Initialization of the Slider

The initSlider function is the entry point of our slideshow. It waits for all images to load and then initializes the slider. Here's what happens:

1const initSlider = () => {
2  // Get the slider element
3  const slider = document.querySelector('.wannabedev-slider');
4  let isAnimating = false; // Flag to control the main animation
5  let isNavigating = false; // Flag to control navigation link clicks
6
7  // Wait for images to load
8  imagesLoaded(slider, { background: true }, () => {
9    // Hide the loader
10    const loader = document.querySelector('.loader');
11    loader.classList.add('is-loaded');
12
13    // Get DOM elements
14    const slides = document.querySelectorAll('.slide');
15    const slideActive = document.querySelector('.slide.is-active');
16    const slideNavigation = document.querySelector('.slides-navigation');
17    const body = document.querySelector('body');
18
19    const init = () => {
20      setSlideBackgrounds(slides);
21      fadeInActiveSlide(slideActive, body);
22    };
23
24    init();
25
26    // Set up infinite scrolling navigation
27    setupInfiniteScrolling('.slides-navigation p', isAnimating);
28
29    // Wait for the clone function & add event listeners to navigation links
30    setTimeout(() => {
31      const slideNavigations = document.querySelectorAll('.slides-navigation a');
32
33      slideNavigations.forEach((sn) => {
34        sn.addEventListener('click', (event) => {
35          if (!isNavigating && !isAnimating) {
36            isNavigating = true;
37            handleNavigationClick(event, body, slideNavigation, () => {
38              isNavigating = false;
39            });
40          }
41        });
42      });
43    }, 150);
44  });
45};

This function starts by initializing some flags to control the animation and navigation: isAnimating and isNavigating. We will use these two flags to prevent multiple clicks on a single item and to disable nav items until the timeline finishes its execution.

We used the imagesLoaded library, too - to ensure all images are loaded before proceeding. Once the images are loaded, it hides the loader and sets up the slideshow with background images and to set active slide: setSlideBackgrounds and fadeInActiveSlide.

Additionally, we will initialize the functions responsible for infinite horizontal scrolling and add a click listener to each of the navigation items.

Setting Slide Backgrounds

The setSlideBackgrounds function sets the background images for the slides:

1const setSlideBackgrounds = (slides) => {
2  slides.forEach((slide) => {
3    const thisBcg = slide.getAttribute('data-bcg');
4    const thisBcgBackground = slide.querySelector('.slide__background');
5    const thisBcgBackground2 = slide.querySelector('.slide__background_2');
6    thisBcgBackground.style.backgroundImage = `url(${thisBcg})`;
7    thisBcgBackground2.style.backgroundImage = `url(${thisBcg})`;
8  });
9};

This function loops through all slides, extracts the background image URLs from data attributes, and sets them as background images.

Fading in the Active Slide

The fadeInActiveSlide function animates the active slide to fade it in:

1const fadeInActiveSlide = (slide, body, onCompleteCallback) => {
2  const tl = gsap.timeline({
3    onComplete: onCompleteCallback // Add onComplete callback to the timeline
4  });
5  tl.set(slide, { autoAlpha: 1 })
6    .set(slide.querySelector('.slide__background'), { autoAlpha: 1, y: '0%' })
7    .set(slide.querySelector('.slide__background_2'), { autoAlpha: 1, y: '0%' })
8    .set(body, { className: '-=is-loading' });
9};

This function uses GSAP to create a timeline that sets the opacity and position of the active slide and its background elements to create a smooth fade-in effect.

Handling Navigation Clicks

The handleNavigationClick function handles navigation link clicks:

1const handleNavigationClick = (event, body, slideNavigation, callback) => {
2  const sectionFrom = document.querySelector('.slide.is-active');
3  const sectionFromId = sectionFrom.getAttribute('id');
4  const sectionToId = event.target.getAttribute('href');
5  const sectionTo = document.querySelector(sectionToId);
6
7  if (sectionFromId !== sectionToId && sectionTo !== null) {
8    // Set isAnimating to true to disable clicks
9    isAnimating = true;
10    scrollToSection(sectionFrom, sectionTo, body, slideNavigation, () => {
11      // Callback to set isAnimating to false after the animation is complete
12      isAnimating = false;
13      if (callback && typeof callback === 'function') {
14        callback();
15      }
16    });
17  }
18};

This function is responsible for switching between sections when a navigation link is clicked. It checks for the current and target sections and initiates a scroll animation using GSAP.

Scrolling to a Section

The scrollToSection function handles the scrolling animation:

1const scrollToSection = (sectionFrom, sectionTo, body, slideNavigation, callback) => {
2  // Check if sectionTo is null
3  if (!sectionTo) {
4    return;
5  }
6
7  // Scroll animation details...
8  // (GSAP animations)
9
10  set(body, { className: '-=is-animating' });
11};

This function animates the transition between sections, including changes to the background color and slide positions. It also calls the setActiveSection function to update the active section.

Setting the Active Section

The setActiveSection function updates the active section:

1const setActiveSection = (sectionFrom, sectionTo, slideNavigation) => {
2  const sectionToId = sectionTo.getAttribute('id');
3  sectionFrom.classList.remove('is-active');
4  sectionTo.classList.add('is-active');
5  highlightNavigation(sectionToId, slideNavigation);
6};

This function updates the active section by adding and removing the 'is-active' class. It also calls highlightNavigation to update the navigation highlights.

Highlighting Navigation

The highlightNavigation function highlights the active navigation link:

1const highlightNavigation = (sectionToId, slideNavigation) => {
2  // Highlight the active navigation link...
3};

This function identifies the current slide and updates the navigation links to highlight the active one.

Setting up Infinite Scrolling

The setupInfiniteScrolling function creates and controls infinite scrolling:

1const setupInfiniteScrolling = (selector, isAnimating) => {
2  // Function to create cloned objects and scrolling timelines...
3  // (details not shown here)
4
5  const scrollingTimelines = [];
6
7  document.querySelectorAll(selector).forEach((obj) => {
8    // Create cloned objects...
9    // (details not shown here)
10  });
11
12  document.querySelectorAll('.slides-navigation span').forEach((span) => {
13    // Event listeners for mouse enter and leave...
14    // (details not shown here)
15  });
16};

This function creates cloned objects and scrolling timelines for the navigation links, allowing for infinite scrolling when the user hovers over them. It uses the createClonedObject and createScrollingTimeline functions to achieve this effect.

Initializing the Slider

Finally, the initSlider function is called to initialize the slider:

1initSlider();

This code kicks off the entire process and sets everything in motion. 😀

I hope you've enjoyed the past few minutes and understood each of the functions we've written and used in creating our sliced slideshow with infinite horizontal scrolling navigation items. Until next time...

Credits

More like this

Ready for more? Here are some related posts to explore