LoginSignup
5
6

惰性スクロール、画像のパララックスアニメーション、スクロール途中固定表示の使い方|Somefolk from london

Posted at

Somefolk.png

ロンドンをベースに、ウェブデザイナーやウェブディベロッパーとして活動する、Jason Harvey氏のポートフォリオサイトがとてもクールなので紹介します。

ポートフォリオサイトのsomefolkがイケているのはもちろんですが、サイトに掲載されているいくつかの制作実績に目を通すと、全ての実績に共通してアニメーション及びイージングのセンスの良さが見受けられます。

ここにJason Harvey氏へのリスペクトを示しつつ、彼のポートフォリオサイトで用いられている表現技法を学びましょう。

用件定義

PC版

pc.gif

◼︎ 惰性スクロール
◼︎ メインビジュアルのパララックス表示
◼︎ 要素の途中固定
◼︎ 固定した要素内の画像のスケールを変更

Mobile版

◼︎ 惰性スクロール
◼︎ メインビジュアルのパララックス表示
◼︎ 要素の途中固定
◼︎ 固定した要素内の画像のスケールを変更

結論

サンプルページはこちら

1.惰性スクロールには lenis を使います。
2.パララックス、要素の固定表示、画像のスケール変更には gsapscroll trigger を使います。
3.それぞれのライブラリー専用のcssは不用です。

コード

index.html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <meta name="description" content="Description" />
  <link rel="stylesheet" href="https://portfolio-technics.com/sample/0-css/style.css" />
  
  <!-- style -->
  <style>
    .mv {
      height: 50vh;
      margin-bottom: 4rem;
      overflow: hidden;
      position: relative;
      width: 100%;
    }
    .mv img {
      -webkit-transform: translateX(-50%) scale(1.2);
      -webkit-transform-origin: 50% 50%;
      display: block;
      height: auto;
      left: 50%;
      position: absolute;
      top: 0;
      transform: translateX(-50%) scale(1.2);
      transform-origin: 50% 50%;
      width: 100%;
    }
    .fix {
      margin: 0 auto;
      max-width: 96rem;
    }
    .fix__list {
      display: flex;
      flex-direction: column;
      margin-bottom: 4rem;
      row-gap: 4rem;
    }
    .fix__item {
      aspect-ratio: 1080/608;
      overflow: hidden;
    }
    .fix__item img {
      display: block;
      height: auto;
      width: 100%;
    }
    .fix__unfix {
      align-items: center;
      background-color: black;
      color: #fff;
      display: flex;
      height: 300rem;
      justify-content: center;
      margin-bottom: 4rem;
      position: relative;
    }
    .footer {
      align-items: center;
      background-color: black;
      color: #fff;
      display: flex;
      height: 400rem;
      justify-content: center;
    }
  </style>
  <!-- style -->

</head>

<body>

  <div id="js-page" class="page">
    <main class="main page__main">
      <div class="mv js-parallax">
        <img loading="lazy" src="https://dl.dropbox.com/s/4hrgv4272zvctj6/sample20.jpg?dl=0" alt="" />
      </div>
      <div class="fix">
        <ul class="fix__list">
          <li class="fix__item js-fix --1">
            <img loading="lazy" src="https://dl.dropbox.com/s/oq7so2vc6j7wgk6/sample1.jpg?dl=0" alt="" />
          </li>
          <li class="fix__item js-fix --2">
            <img loading="lazy" src="https://dl.dropbox.com/s/03sa966wy301vt1/sample2.jpg?dl=0" alt="" />
          </li>
          <li class="fix__item js-fix --3">
            <img loading="lazy" src="https://dl.dropbox.com/s/aa7uk8vh5hhermx/sample3.jpg?dl=0" alt="" />
          </li>
          <li class="fix__item js-fix --4">
            <img loading="lazy" src="https://dl.dropbox.com/s/9h64k0vppliw3m9/sample4.jpg?dl=0" alt="" />
          </li>
        </ul>
        <div class="fix__unfix js-unfix">
          <p>
            The upper items will be unfixed when this content reaches the top of the screen.
          </p>
        </div>
      </div>
    </main>
    <footer class="footer page__footer">
      <p>
        footer
      </p>
    </footer>
  </div>

  <!-- CDN -->
  <script src="https://cdn.jsdelivr.net/gh/studio-freight/lenis@1.0.25/bundled/lenis.min.js"></script> 
  <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/ScrollTrigger.min.js"></script>

  <!-- js -->
  <script>

    class MomentumLenis {
      constructor() {
        this._lenisInit();
      }

      _lenisInit() {
        gsap.registerPlugin(ScrollTrigger);

        const lenis = new Lenis({
          lerp: 0.1,
        });

        lenis.on("scroll", ScrollTrigger.update);

        gsap.ticker.add((time) => {
          lenis.raf(time * 1000);
        });

        // Images parallax
        gsap.utils.toArray(".js-parallax").forEach((parallaxBoxes) => {
          const parallaxImages = parallaxBoxes.querySelector("img");

          const tl = gsap.timeline({
            scrollTrigger: {
              trigger: parallaxBoxes,
              scrub: true,
              pin: false,
              // markers: true,
            },
          });

          tl.fromTo(
            parallaxImages,
            {
              yPercent: -20,
              ease: "none",
            },
            {
              yPercent: 20,
              ease: "none",
            }
          );
        });

        // Fix the li elements and gradually scale the images on scroll
        gsap.utils.toArray(".js-fix").forEach((fixItem) => {
          const image = fixItem.querySelector("img");

          const tl = gsap.timeline({
            scrollTrigger: {
              trigger: fixItem,
              start: "top 10px",
              endTrigger: ".js-unfix",
              end: "top 10px", // Change the end condition to keep the item pinned until it reaches the top
              scrub: 1,
              pin: true,
              pinSpacing: false,
              // markers: true,
            },
          });

          tl.to(fixItem, { y: 0, ease: "none" }).to(
            image,
            {
              scale: 1.2,
              ease: "none",
            },
            0
          );
        });
      }
    }

    new MomentumLenis();

  </script>
  <!-- js -->

</body>

</html>

実践

❶lenisで惰性スクロールを実装する。

lenisの使い方|公式サイトに基づく

基本のコード

main.js
// smooth scroll setting
    const lenis = new Lenis({
      lerp: 0.2, // Linear interpolation (lerp) intensity (between 0 and 1)
      duration: 1, // The duration of scroll animation (in seconds). Useless if lerp defined
    });

    function raf(time) {
      lenis.raf(time);
      requestAnimationFrame(raf);
    }

    requestAnimationFrame(raf);

❷1にgsap + scroll triggerで画像のparallax効果と拡大アニメーションを実装する。

lenisにgsap & scroll triggerを組み合わせる|公式サイトに基づく

GSAP
SCROLL TRIGGER

main.js
gsap.registerPlugin(ScrollTrigger);

    const lenis = new Lenis({
      lerp: 0.1,
    });

    lenis.on("scroll", ScrollTrigger.update);

    gsap.ticker.add((time) => {
      lenis.raf(time * 1000);
    });

    // Images parallax
    gsap.utils.toArray(".js-parallax").forEach((parallaxBoxes) => {
      const parallaxImages = parallaxBoxes.querySelector("img");

      const tl = gsap.timeline({
        scrollTrigger: {
          trigger: parallaxBoxes,
          scrub: true,
          pin: false,
          // markers: true,
        },
      });

      tl.fromTo(
        parallaxImages,
        {
          yPercent: -20,
          ease: "none",
        },
        {
          yPercent: 20,
          ease: "none",
        }
      );
    });

❸2にスクロール途中で固定、スケールを変更。

main.js
const lenis = new Lenis({
      lerp: 0.1,
    });


    gsap.registerPlugin(ScrollTrigger);


    gsap.ticker.add((time) => {
      lenis.raf(time * 1000);
    });


    lenis.on("scroll", ScrollTrigger.update);


    // Images parallax
    gsap.utils.toArray(".js-parallax").forEach((parallaxBoxes) => {
      const parallaxImages = parallaxBoxes.querySelector("img");


      const tl = gsap.timeline({
        scrollTrigger: {
          trigger: parallaxBoxes,
          scrub: true,
          pin: false,
          // markers: true,
        },
      });


      tl.fromTo(
        parallaxImages,
        {
          yPercent: -20,
          ease: "none",
        },
        {
          yPercent: 20,
          ease: "none",
        }
      );
    });


    // Fix the li elements and gradually scale the images on scroll
    gsap.utils.toArray(".js-fix").forEach((fixItem) => {
      const image = fixItem.querySelector("img");


      const tl = gsap.timeline({
        scrollTrigger: {
          trigger: fixItem,
          start: "top 10px",
          endTrigger: ".js-unfix",
          end: "top 10px", // Change the end condition to keep the item pinned until it reaches the top
          scrub: 1,
          pin: true,
          pinSpacing: false,
          // markers: true,
        },
      });


      tl.to(fixItem, { y: 0, ease: "none" }).to(
        image,
        {
          scale: 1.2,
          ease: "none",
        },
        0
      );
    });

5
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
6