DEV Community

Cover image for How to Use Momentum Scrolling, Image Parallax Animation, and Scroll Midway Fixed Display | somefolk from London
PortfolioTechnics
PortfolioTechnics

Posted on

How to Use Momentum Scrolling, Image Parallax Animation, and Scroll Midway Fixed Display | somefolk from London

Based in London, we'd like to introduce the portfolio site of Jason Harvey, who works as a web designer and web developer. His portfolio site, "somefolk," is really cool.

While "somefolk" as a portfolio site is impressive on its own, what stands out across all of his showcased works are his excellent sense of animation and easing.

Let's learn from the techniques used in Jason Harvey's portfolio site while showing our respect to him.

Requirements definition

PC

Image description

◼︎ Momentum Scrolling
◼︎ Parallax Display in the Main Visual
◼︎ Partially Fixed Elements
◼︎ Changing the Scale of Images within Fixed Elements

Mobile

Image description

◼︎ Parallax Display in the Main Visual
◼︎ Partially Fixed Elements
◼︎ Changing the Scale of Images within Fixed Elements

Conclusion

sample site

1.For momentum scrolling, we use "lenis."
2.For parallax, element fixation, and image scaling, we use "gsap" and "scroll trigger."
3.There is no need for dedicated CSS for each of these libraries.

<!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>
Enter fullscreen mode Exit fullscreen mode

Practice

❶Implement momentum scrolling with Lenis.

basic usage|based in official site.

https://github.com/studio-freight/lenis

// 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);
Enter fullscreen mode Exit fullscreen mode

❷In addition to Step 1, implement image parallax effect and zoom animation using GSAP + Scroll Trigger.

Combine GSAP & Scroll Trigger with Lenis | Based on the official website.

https://github.com/studio-freight/lenis#gsap-scrolltrigger-integration
https://greensock.com/scrolltrigger/

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",
        }
      );
    });

Enter fullscreen mode Exit fullscreen mode

❸In addition to Step 2, fix elements midway through scrolling and change their scale.

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
      );
    });

Enter fullscreen mode Exit fullscreen mode

Top comments (0)