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

![Somefolk.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3566567/3ee9c636-3619-ff9e-b1ab-525973714d39.png)

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

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

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


## 用件定義

### PC版

![pc.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3566567/e63dee59-9c5d-b921-3c12-2ca5ce7ed76e.gif)

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

### Mobile版

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

## 結論

[サンプルページ]はこちら

1.惰性スクロールには [lenis] を使います。
2.パララックス、要素の固定表示、画像のスケール変更には [gsap] と [scroll 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
      );
    });

```


[Jason Harvey氏のポートフォリオサイト]:https://www.somefolk.co.uk/
[lenis]:https://lenis.studiofreight.com/
[gsap]:https://greensock.com/gsap/
[scroll trigger]:https://greensock.com/scrolltrigger/
[サンプルページ]:https://portfolio-technics.com/sample/1-Parallax&ScrollTrigger-SomeFolk/index.html
[公式サイト]:https://github.com/studio-freight/lenis
[GSAP]:https://github.com/studio-freight/lenis#gsap-scrolltrigger-integration
[SCROLL TRIGGER]:https://greensock.com/scrolltrigger/