Web Design
Animations
Asscroll

[ani] Asscroll with GSAP

無法解決 resize 時 trigger 會跳動的問題,兩個函式庫直接搭配時有 Bug,改用 smooth-scrollbar 吃進 proxy 就沒問題

the timing of setting scroll and triggers matters!

console log to make sure triggers are being set after scroll proxy init

setup func

js (after mount: useEffect)

const setupScroll = () => {
  const ASScroll = require("@ashthornton/asscroll");
 
  // 用query以className抓
  // 直接放asscroll-container跟asscroll-content在div的props,lsp會不爽
  const containerElement = document.querySelector(".asscroll-container");
  const scrollElements = document.querySelector(".asscroll-content");
 
  const asscroll = new ASScroll({
    // 要加ease不然trigger反應會很慢
    ease: 0.6,
    disableRaf: true,
    // 指定上面的元素
    containerElement,
    scrollElements,
  });
  gsap.ticker.add(asscroll.update);
 
  ScrollTrigger.defaults({
    scroller: asscroll.containerElement,
  });
 
  ScrollTrigger.scrollerProxy(asscroll.containerElement, {
    scrollTop(value) {
      return arguments.length
        ? (asscroll.currentPos = value)
        : asscroll.currentPos;
    },
    getBoundingClientRect() {
      return {
        top: 0,
        left: 0,
        width: window.innerWidth,
        height: window.innerHeight,
      };
    },
    fixedMarkers: true,
  });
 
  // gsap scroll triggers!
  // 確定在這時候才設定scroll triggers
  // gsap scroll triggers!
 
  asscroll.on("update", ScrollTrigger.update);
  ScrollTrigger.addEventListener("refresh", asscroll.resize);
 
  return asscroll;
};

html

<!-- container -->
<div className="asscroll-container">
  <!-- scroll content -->
  <div className="asscroll-content"></div>
</div>

如果要直接可以用,可以直接 enable()不用 return

用 function 回 asscroll 回來,剩下 enable()還沒做 因為不一定希望一開始就啟動 scroller

但這個 setup 要在其他 gsap trigger 用之前先做 (proxy 有設定 scroll container) 不然 trigger 會參照到錯誤的 container

enable

// ass!
const ass = setupScroll();
 
// 在想要讓user可以scroll時使用(比如說intro動畫放完後)
// 要不要requestAnimationFrame?
// 看不出指定元素有沒有差
// 三種肉眼沒看出差異
// 應該用 gsap.ticker.add(asscroll.update) 就已經有RAF了;
 
ass.enable();
 
ass.enable({
  newScrollElements: document.querySelectorAll(
    ".gsap-marker-start, .gsap-marker-end, [asscroll]"
  ),
});
 
requestAnimationFrame(() => {
  ass.enable({
    newScrollElements: document.querySelectorAll(
      ".gsap-marker-start, .gsap-marker-end, .asscroll-content"
    ),
  });
});

urls

https://codepen.io/GreenSock/pen/rNyyxBP (opens in a new tab)

https://codepen.io/ashthornton/pen/PoZRwPW (opens in a new tab)

greensock demo

import ASScroll from "https://cdn.skypack.dev/@ashthornton/asscroll";
import gsap from "https://cdn.skypack.dev/gsap@3.6.1";
import ScrollTrigger from "https://cdn.skypack.dev/gsap@3.6.1/ScrollTrigger";
gsap.registerPlugin(ScrollTrigger);
 
window.addEventListener("load", () => {
  const asscroll = setupASScroll();
  const isTouch = "ontouchstart" in document.documentElement;
  const totalScroll = asscroll.containerElement.scrollHeight - innerHeight;
 
  gsap.to(".peach", {
    scrollTrigger: {
      pin: true,
      pinType: isTouch ? "fixed" : "transform",
      end: "200%",
      scrub: 0.2,
      trigger: ".peaches",
    },
    y: (i, target) => -totalScroll * target.dataset.speed,
    ease: "none",
  });
 
  gsap.from(".gif img", {
    scrollTrigger: {
      pin: true,
      pinType: isTouch ? "fixed" : "transform",
      scrub: true,
      trigger: ".gif",
    },
    scale: 0.2,
    autoAlpha: 0,
    ease: "sine.out",
  });
});
 
function setupASScroll() {
  // https://github.com/ashthornton/asscroll
  const asscroll = new ASScroll({
    // requestAnimationFrame
    disableRaf: true,
  });
 
  gsap.ticker.add(asscroll.update);
 
  ScrollTrigger.defaults({
    scroller: asscroll.containerElement,
  });
 
  ScrollTrigger.scrollerProxy(asscroll.containerElement, {
    scrollTop(value) {
      if (arguments.length) {
        asscroll.currentPos = value;
        return;
      }
      return asscroll.currentPos;
    },
    getBoundingClientRect() {
      return {
        top: 0,
        left: 0,
        width: window.innerWidth,
        height: window.innerHeight,
      };
    },
    fixedMarkers: true,
  });
 
  asscroll.on("update", ScrollTrigger.update);
  ScrollTrigger.addEventListener("refresh", asscroll.resize);
 
  requestAnimationFrame(() => {
    asscroll.enable({
      newScrollElements: document.querySelectorAll(
        ".gsap-marker-start, .gsap-marker-end, [asscroll]"
      ),
    });
  });
  return asscroll;
}

how

觀察 style 可以發現是把整個 content 用 transform: translateY 往上移

遇到移動完才觸發 trigger 怎麼辦?