Search code examples
javascriptjquerycssgsapscrolltrigger

Using GSAP to create horizontal scroll section


I'm trying to achieve a section which, when in view, becomes a horizontal scroller until the items in the scroller (in my case, images) are finished, at which point, it becomes a vertical scroller again.

I've seen and adapted my approach on the following demos:

The asthetic I'm trying to achieve is to show two images by default, then scroll to see rest. See below for a mockup:

enter image description here

Here is my current approach:

$(function() {

  let scroll_tl = gsap.timeline({
    scrollTrigger: {
      trigger: '.horizontalScroller__images',
      start: "top center",
      // pin: true,
      scrub: true,
      end: "+=300",
      // markers: true,
    }
  });

  var images = document.querySelectorAll('.horizontalScroller__item');

  scroll_tl.to('.horizontalScroller__intro', {
    duration: 1,
    ease: "slow"
  });

  scroll_tl.to(images, {
    xPercent: -85 * (images.length - 1),
    scrollTrigger: {
      trigger: ".horizontalScroller__images",
      start: "center center",
      pin: true,
      // horizontal: true,
      // pinSpacing:false,
      markers: true,
      scrub: 1,
      snap: 1 / (images.length - 1),
      // end: () => `+=4320`
    }
  });

});
.spacer{
  height: 300vh;
  background: lightblue;
}

.horizontalScroller {
  padding: 114px 0 80px 0;
  height: 100vh;
  position: relative;
  background-color: #5d209f;
}
.horizontalScroller__intro {
  margin-bottom: 25px;
  color: #FFFFFF;
}
.horizontalScroller__header {
  margin-bottom: 17px;
}
.horizontalScroller__images {
  display: flex;
  justify-content: center;
  align-items: center;
}
.horizontalScroller__scroll {
  position: relative;
  overflow: hidden;
  padding: 60px 0;
}
/* .horizontalScroller__item {
  width: 25vw;
} */
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.9.0/gsap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.9.0/ScrollTrigger.min.js"></script>

<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">

<div class="spacer"> Scroll down </div>

<section class="horizontalScroller">

  <div class="container">
    <div class="row">
      <div class="col-12 col-md-8 col-xl-6">
        <div class="horizontalScroller__intro">
          <h2 class="horizontalScroller__header">This is the header</h2>
          <p class="horizontalScroller__standfirst mb-0">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
        </div>
      </div>
    </div>
  </div>

  <div class="horizontalScroller__scroll">

    <div class="horizontalScroller__images" id="horizontal-scroll">
      <div class="horizontalScroller__item">
        <img class="horizontalScroller__image" src="https://picsum.photos/200/300" alt="image">
      </div>
      <div class="horizontalScroller__item">
        <img class="horizontalScroller__image" src="https://picsum.photos/200/300" alt="image">
      </div>
      <div class="horizontalScroller__item">
        <img class="horizontalScroller__image" src="https://picsum.photos/200/300" alt="image">
      </div>
      <div class="horizontalScroller__item">
        <img class="horizontalScroller__image" src="https://picsum.photos/200/300" alt="image">
      </div>
    </div>

  </div>

</section>

In my above demo, you can see that when scrolling in that section, the images move down (not intended) and then they start moving towards the left (whereas the horizontal scroll should move to the right).

Also, to show the two cards initially, I've tried to set a width to the .horizontalScroller__item, but it just messes it up even more.

Unsure why my code is performing the way it is, any ideas?


Solution

  • You need to use top most parent container horizontalScroller, that is spanning entire viewport height as a scrollTrigger:

    $(function() {
      let images = gsap.utils.toArray(".horizontalScroller__item");
    
      gsap.to(images, {
        xPercent: -100 * (images.length - 1),
        ease: "none",
        scrollTrigger: {
          trigger: ".horizontalScroller",
          pin: true,
          scrub: 1,
          snap: 1 / (images.length - 1),
          end: () => "+=" + document.querySelector(".horizontalScroller__images").offsetWidth
        }
      });
    
    });
    * {
      box-sizing: border-box;
      padding: 0;
      margin: 0;
    }
    
    .spacer {
      height: 300vh;
      background: lightblue;
    }
    
    .horizontalScroller {
      padding: 2rem 0 1rem 0;
      height: 100vh;
      position: relative;
      background-color: #5d209f;
    }
    
    .horizontalScroller__intro {
      margin-bottom: 1.5rem;
      color: #FFFFFF;
    }
    
    .horizontalScroller__header {
      margin-bottom: 1rem;
    }
    
    .horizontalScroller__scroll {
      height: 70%;
      position: relative;
      overflow: hidden;
      padding: 2rem 0;
    }
    
    .horizontalScroller__images {
      height: 100%;
      display: flex;
      align-items: center;
    }
    
    .horizontalScroller__item {
      width: 50vw;
      height: 100%;
      display: flex;
      justify-content: center;
      flex: 0 0 auto;
    }
    
    .horizontalScroller__image {
      width: 70%;
      object-fit: fill;
      margin: 0 auto;
    }
    
    .footer {
      height: 80vh;
      width: 100%;
      background-color: yellow;
      font-size: 3rem;
    }
    
    .copyrights {
      height: 20vh;
      width: 100%;
      background-color: lightsalmon;
      font-size: 3rem;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.9.0/gsap.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.9.0/ScrollTrigger.min.js"></script>
    <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
    
    <div class="spacer"> Scroll down </div>
    
    <section class="horizontalScroller">
    
      <div class="container">
        <div class="row">
          <div class="col-12 col-md-8 col-xl-6">
            <div class="horizontalScroller__intro">
              <h2 class="horizontalScroller__header">This is the header</h2>
              <p class="horizontalScroller__standfirst mb-0">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
            </div>
          </div>
        </div>
      </div>
    
      <div class="horizontalScroller__scroll">
    
        <div class="horizontalScroller__images" id="horizontal-scroll">
          <div class="horizontalScroller__item">
            <img class="horizontalScroller__image" src="https://picsum.photos/200/300" alt="image">
          </div>
          <div class="horizontalScroller__item">
            <img class="horizontalScroller__image" src="https://picsum.photos/200/300" alt="image">
          </div>
          <div class="horizontalScroller__item">
            <img class="horizontalScroller__image" src="https://picsum.photos/200/300" alt="image">
          </div>
          <div class="horizontalScroller__item">
            <img class="horizontalScroller__image" src="https://picsum.photos/200/300" alt="image">
          </div>
          <div class="horizontalScroller__item">
            <img class="horizontalScroller__image" src="https://picsum.photos/200/300" alt="image">
          </div>
          <div class="horizontalScroller__item">
            <img class="horizontalScroller__image" src="https://picsum.photos/200/300" alt="image">
          </div>
        </div>
      </div>
    </section>
    <div class="footer">Footer</div>
    <div class="copyrights">Copyrights</div>