โมชัน

การเคลื่อนไหวมีหน้าที่ บอกลำดับ ตอบกลับการกระทำ หรือเชื่อมสองสถานะ ถ้าอธิบายเหตุผลในประโยคเดียวไม่ได้ ไม่ต้องขยับ

กฎสี่ข้อ

transform, opacity, clip-pathขยับเฉพาะสามอย่างนี้ ไม่ทำ width, top หรือ letter-spacing
prefers-reduced-motionใช้ gsap.matchMedia ทุกครั้ง ผู้ใช้ที่ขอลดจะเห็นสถานะสุดท้ายทันที
ห้ามขวางงานฟอร์ม การชำระเงิน และการจอง ต้องใช้ได้ทันทีโดยไม่รอแอนิเมชัน
ซ่อนเมื่อมี JS เท่านั้นเนื้อหาที่รอเข้าฉากถูกปลดออกเองใน 2.4 วินาทีถ้าสคริปต์ไม่ทำงาน

เส้นโค้งความเร่ง

สามเส้นต่อแบรนด์ ค่าเดียวกันใช้ได้ทั้งใน CSS transition และ GSAP CustomEase กดเล่นเพื่อดูความต่าง

ระยะเวลา 1.12 วินาที ช้ากว่า --dur-slow ให้เห็นรูปเส้นชัดขึ้น

--ease-out0.16, 1, 0.3, 1

เข้า เปิดเผย และตอบกลับ ใช้บ่อยที่สุด

--ease-in-out0.76, 0, 0.24, 1

ย้ายตำแหน่ง เปลี่ยนสถานะ และม่านเปลี่ยนหน้า

--ease-spring0.34, 1.56, 0.64, 1

ผลตอบรับเล็กๆ เช่น ปุ่มกลมของสวิตช์ เด้งเกินเล็กน้อย

ระยะเวลา

--dur-fast 180msชี้ กด สีเปลี่ยน
--dur-base 320msเมนู สวิตช์ การ์ดยก
--dur-slow 700msเปิดเผยเนื้อหา toast เข้า
--dur-fast 160msชี้ กด สีเปลี่ยน
--dur-base 280msเมนู สวิตช์ การ์ดยก
--dur-slow 600msเปิดเผยเนื้อหา toast เข้า
ใช้โทเคนใน CSS
.ds-switch__track::after {
  transition: transform var(--dur-base) var(--ease-spring);
}
.ds-card[data-interactive]:hover {
  transform: translateY(-3px);
  transition: transform var(--dur-base) var(--ease-out);
}

การ์ดแรกเปลี่ยนตามแบรนด์ ที่เหลือใช้ได้ทั้งสองแบรนด์

ประตูองศา

ลายเซ็นของ ASWINOLOGY° วงกลมหมุนเข้ามา เติมสีไลม์ แล้วขยายเต็มพื้นที่ ใช้ครั้งเดียวต่อการเข้าเว็บ

เปิดหัวข้อทีละบรรทัด

เรียนทีละนิดเติบโตทีละองศา

บรรทัดเลื่อนขึ้นจากหลังหน้ากาก ใช้กับหัวข้อหลักของหน้าเท่านั้น

ทยอยเข้า

รายการเข้าทีละชิ้นห่างกัน 60 ms บอกว่าเป็นกลุ่มเดียวกัน ไม่เกินแปดชิ้นต่อครั้ง

เช็ดเปิด
a°

ภาพหรือแผ่นสีเปิดจากซ้ายไปขวาด้วย clip-path ไม่ทำให้เลย์เอาต์ขยับ

สลับแบรนด์

ตัวสลับที่หัวเว็บใช้ View Transitions API วงกลมของแบรนด์ใหม่ขยายออกจากปุ่มที่กด สะท้อนประตูองศาของ ASWINOLOGY° เบราว์เซอร์ที่ไม่รองรับหรือผู้ใช้ที่ขอลดการเคลื่อนไหวจะสลับทันที

site/brand-state.ts
const transition = document.startViewTransition(() => {
  document.documentElement.dataset.brand = next;
});
transition.ready.then(() => {
  document.documentElement.animate(
    { clipPath: [`circle(0px at ${x}px ${y}px)`, `circle(${r}px at ${x}px ${y}px)`] },
    { duration: 720, easing: "cubic-bezier(0.76, 0, 0.24, 1)", pseudoElement: "::view-transition-new(root)" },
  );
});

สูตร GSAP ใน React

useGSAP ล้างแอนิเมชันให้เอง matchMedia จัดการผู้ใช้ที่ขอลดการเคลื่อนไหว

components/reveal.tsx
"use client";
import { useRef } from "react";
import { gsap } from "gsap";
import { useGSAP } from "@gsap/react";

gsap.registerPlugin(useGSAP);

export function Reveal({ children }: { children: React.ReactNode }) {
  const scope = useRef<HTMLDivElement>(null);
  useGSAP(() => {
    const mm = gsap.matchMedia();
    mm.add("(prefers-reduced-motion: no-preference)", () => {
      gsap.fromTo("[data-reveal]",
        { autoAlpha: 0, y: 24 },
        { autoAlpha: 1, y: 0, duration: 0.9, ease: "power3.out", stagger: 0.08 });
    });
    return () => mm.revert();           // cleans up on unmount and on media change
  }, { scope });
  return <div ref={scope}>{children}</div>;
}

ใช้เมื่อไร

ควรทำ

  • ใช้ timeline เมื่อมีหลายขั้นตอน แทนการต่อ delay
  • ใช้ autoAlpha แทน opacity เพื่อให้ซ่อนแล้วกดไม่ได้
  • ใช้ ScrollTrigger กับหน้าเล่าเรื่องของ ASWINOLOGY° เท่านั้น

ไม่ควรทำ

  • ฟัง scroll event เอง
  • ผสม GSAP กับไลบรารีโมชันอื่นใน component เดียวกัน
  • ใส่ marquee หรือแอนิเมชันวนไม่รู้จบเพื่อตกแต่ง