【GSAP】横スクロール、パララックス、時間差出現、ドラッグ操作を盛り込んだ実装

今回はGreenSock社が提供するJavaScriptアニメーションライブラリGSAPで
横スクロール、パララックス、時間差出現、ドラッグ操作などの要素を盛り込んだものを実装しました!

コピペしてすぐに実務で実装できるようにしております。ぜひ最後までご覧ください!

1.実装イメージ

実装イメージは以下の通りです。

See the Pen 【GSAP】横スクロール+パララックス+時間差+ドラッグ操作=お洒落 by りょーすけ (@s_ryosuke_1242) on CodePen.

2.HTML

次にHTMLです。
ドラッグ操作を可能にするための要素が11行目にあります。(proxy => 代理、代わり)

<body>
  <main>
    <!-- SLIDER -->
    <section class="description">
      <div class="container">
        <h2>途中から横スクロール</h2>
        <p>横スクロール中はパララックス</p>
      </div>
    </section>
    <div class="vertical-slider__wrap">
      <div class="proxy"></div>
      <section class="vertical-area teal">
        <div class="img-wrap">
          <div class="mask"></div>
            <img src="https://ryo-sukeblog.net/wp-content/uploads/2022/04/sea.jpg" alt="" class="js-img">
          <div class="heading-wrap"><h2>Beautiful World</h2></div>
        </div>
      </section>
      <section class="vertical-area maroon">
        <div class="img-wrap">
          <div class="mask"></div>
            <img src="https://ryo-sukeblog.net/wp-content/uploads/2022/04/cherry-blossoms.jpg" alt="" class="js-img img2">
          <div class="heading-wrap"><h2>Sakura Nagashi</h2></div>
        </div>
      </section>
      <section class="vertical-area olive">
        <div class="img-wrap">
          <div class="mask"></div>
          <img src="https://ryo-sukeblog.net/wp-content/uploads/2022/04/scene.jpg" alt="" class="js-img img3">
          <div class="heading-wrap"><h2>One Last Kiss</h2></div>
        </div>
      </section>
    </div>
    <section class="last-area">
      <h2>横スクロール終了</h2>
    </section>
  <!-- END SLIDER -->
  </main>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.10.4/gsap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.10.4/ScrollTrigger.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.10.4/Draggable.min.js"></script>
</body>

3.CSS

次にCSSです。

*{
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}
.vertical-area h2{
    color: white;
    font-size: 56px;
    width: 100%;
    display: flex;
    justify-content: center;
}
.vertical-area h2 span{
    display: block;
}
.vertical-area .img-wrap{
    width: 70%;
    height: 80%;
    position: relative;
    overflow: hidden;
}
.vertical-area .img-wrap .mask{
    width: 100%;
    height: 100%;
    position: absolute;
    left: 0;
    top: 0;
    background-color: rgba(0,0,0,.1);
    z-index: 1;
}
.vertical-area .img-wrap img{
    width: 130%;
    height: 130%;
    object-fit: cover;
}
.teal{
    background-color:teal;
}
.maroon{
    background-color: maroon;
}
.olive{
    background-color: olive;
}
.last-area{
    background-color: #222;
    width: 100%;
    height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
}
.last-area h2{
    color: white;
    font-size: 40px;
}
.proxy{
    position: absolute;
    visibility: hidden;
}

4.JavaScript

最後にJavaScriptです。
HTMLのbodyタグの手前にも記述されていますがCDNで『ScrollTrigger』と『Draggable』を予めダウンロードしておきましょう


let sections = document.querySelectorAll(".vertical-area");
let scrollContainer = document.querySelector(".vertical-slider__wrap");
let images = gsap.utils.toArray(".js-img");
let heading = gsap.utils.toArray(".heading-wrap h2");

let scrollTween = gsap.to(sections, {
    xPercent: -100 * (sections.length - 1),
    ease: "none"
});

let horizontalScroll = ScrollTrigger.create({
    animation: scrollTween,
    trigger: scrollContainer,
    pin: true,
    scrub: 1,
    // markers:true,
    end: () => "+=" + scrollContainer.offsetWidth,
});

function txtSplit(el){//https://sinciate.co.jp/media/2999/
    var content = el.textContent;
    var text = content.trim();
    var newHtml = "";

    text.split("").forEach(function(v) {
        newHtml += "" + v + "";
    });
    el.innerHTML = newHtml;
}
images.forEach((img, i) => {
    gsap.to(img,{
        scrollTrigger:{
            trigger: img,
            start:'top center',
            end:'bottom center',
            horizontal:true,
            // markers:true,
            scrub:.2,
            containerAnimation:scrollTween,
        },
        x:-150,ease:"none"
    });
});
heading.forEach((h, i) => {
    txtSplit(h);
    if(i === 0){
        gsap.from(h.querySelectorAll('span'),{
            scrollTrigger:{
                trigger: h,
                start:'top 10%',
                // markers:true,
                horizontal:true,
                containerAnimation:scrollTween,
                toggleActions:'play none none reverse'
            },
            stagger:{
                each:0.02,
            },
            y:'30%',autoAlpha:0,ease:Power4.easeIn,duration:.4,
        });
    }else{
        gsap.from(h.querySelectorAll('span'),{
            scrollTrigger:{
                trigger: h,
                start:'10% center',
                // markers:true,
                horizontal:true,
                containerAnimation:scrollTween,
                toggleActions:'play none none reverse'
            },
            stagger:{
                each:0.02,
            },
            y:'50%',autoAlpha:0,ease:Power4.easeIn,duration:.4,
        });
    }
});

// total scroll amount divided by the total distance that the sections move gives us the ratio we can apply to the pointer movement so that it fits. 
var dragRatio = scrollContainer.offsetWidth / (window.innerWidth * (sections.length - 1));
var drag = Draggable.create(".proxy", {
  trigger: scrollContainer,
  type: "x",
  onPress() {
    this.startScroll = horizontalScroll.scroll();
  },
  onDrag() {
    horizontalScroll.scroll(this.startScroll - (this.x - this.startX) * dragRatio);
  }
})[0];