เพิ่มเอฟเฟกต์การเข้าและออกให้กับองค์ประกอบเมื่อเข้าหรือออกจาก Scrollport

เอฟเฟกต์การเข้าและออกคือภาพเคลื่อนไหวที่ทริกเกอร์เมื่อองค์ประกอบเข้าสู่หรือออกจากวิวพอร์ต ซึ่งสามารถใช้เพื่อสร้างประสบการณ์ของผู้ใช้ที่น่าสนใจและเป็นแบบไดนามิก เช่น คุณสามารถใช้เอฟเฟกต์ทางเข้าเพื่อทำให้องค์ประกอบค่อยๆ ปรากฏขึ้นเมื่อเลื่อนเข้ามาในมุมมอง หรือใช้เอฟเฟกต์ทางออกเพื่อลดขนาดองค์ประกอบเมื่อเลื่อนออกจากมุมมอง

วิธีการนำไปใช้งาน

หากต้องการเพิ่มเอฟเฟกต์ทางเข้าและทางออกให้กับองค์ประกอบ คุณต้องรวมพร็อพเพอร์ตี้ CSS 2-3 รายการเข้าด้วยกัน ต่อไปนี้คือคำแนะนำทีละขั้นตอน:

  1. สร้าง @keyframes แยกกันสำหรับภาพเคลื่อนไหวสำหรับการเข้าและออกฉาก ระบบจะใช้ภาพเคลื่อนไหวสำหรับการเข้าฉากเมื่อองค์ประกอบเข้าสู่วิวพอร์ต และจะใช้ภาพเคลื่อนไหวสำหรับการออกจากฉากเมื่อองค์ประกอบออกจากวิวพอร์ต

    @keyframes slide-in {
      from { transform: translateX(-100%); }
    }
    @keyframes slide-out {
      to { transform: translateX(100%); }
    }
    
  2. แนบคีย์เฟรมเข้าและออกกับองค์ประกอบ ซึ่งทำได้โดยกำหนดภาพเคลื่อนไหวหลายรายการในพร็อพเพอร์ตี้ animation

    • กำหนด animation-fill-mode เป็น backwards ให้กับภาพเคลื่อนไหวขาเข้า เพื่อให้ใช้สถานะเริ่มต้นก่อนที่ภาพเคลื่อนไหวจะเริ่ม
    • กำหนดให้ภาพเคลื่อนไหวขาออกมี animation-fill-mode เป็น forwards เพื่อให้คงสถานะสุดท้ายไว้หลังจากภาพเคลื่อนไหวเสร็จสมบูรณ์
    .animated-element {
      animation:
        slide-in 1s linear backwards,
        slide-out 1s linear forwards;
    }
    
  3. สร้าง View Timeline และลิงก์กับภาพเคลื่อนไหว ไทม์ไลน์มุมมองคือประเภทของไทม์ไลน์ที่ลิงก์กับระดับการเข้าถึงขององค์ประกอบในวิวพอร์ต คุณสร้างได้โดยใช้ฟังก์ชัน view() แล้วนำไปใช้กับภาพเคลื่อนไหวโดยใช้พร็อพเพอร์ตี้ animation-timeline

    .animated-element {
      animation-timeline: view();
    }
    

    โดยค่าเริ่มต้น view() จะติดตามองค์ประกอบบนแกน block หากต้องการติดตามบนแกน inline คุณสามารถใช้ view(inline) ได้

  4. จำกัดภาพเคลื่อนไหวให้อยู่ในช่วง entry และ exit พร็อพเพอร์ตี้ animation-range ช่วยให้คุณระบุส่วนของไทม์ไลน์ที่ภาพเคลื่อนไหวควรทำงานได้

    • ช่วง entry จะครอบคลุมเวลาตั้งแต่ที่องค์ประกอบเข้าสู่วิวพอร์ตเป็นครั้งแรกจนกระทั่งมองเห็นได้ทั้งหมด
    • ช่วง exit ครอบคลุมเวลาตั้งแต่ที่องค์ประกอบเริ่มออกจากวิวพอร์ตจนกระทั่งซ่อนโดยสมบูรณ์
    .animated-element {
      animation-range: entry, exit;
    }
    

โค้ดตัวอย่าง

โค้ดนี้จะทำให้องค์ประกอบย่อยโดยตรงของตัวเลื่อนเคลื่อนไหวเมื่อเลื่อนโดยใช้ view-timeline แบบไม่ระบุชื่อ

@media (prefers-reduced-motion: no-preference) {
  @supports ((animation-timeline: view()) and (animation-range: entry)) {
    @keyframes grow {
      from {
        scale: 0.5;
      }
    }
    @keyframes shrink {
      to {
        scale: 0.5;
      }
    }

    .scroller > * {
      animation:
        grow auto linear backwards,
        shrink auto linear forwards;
      animation-timeline: view(inline);
      animation-range: entry, exit;
    }
  }
}

เมื่อองค์ประกอบเข้าสู่พอร์ตการเลื่อน ระบบจะเล่นภาพเคลื่อนไหว grow และเมื่อองค์ประกอบออกจากพอร์ตการเลื่อน ระบบจะเล่นภาพเคลื่อนไหว shrink

โค้ดต่อไปนี้มีผลลัพธ์ภาพเหมือนกัน แต่จะทำให้องค์ประกอบย่อยโดยตรงของตัวเลื่อนเคลื่อนไหวเมื่อเลื่อนโดยใช้ view-timeline ที่มีชื่อ

@media (prefers-reduced-motion: no-preference) {
  @supports ((animation-timeline: view()) and (animation-range: entry)) {
    @keyframes grow {
      from {
        scale: 0.5;
      }
    }
    @keyframes shrink {
      to {
        scale: 0.5;
      }
    }

    .scroller > * {
      view-timeline: --tl inline;
      animation:
        grow auto linear backwards,
        shrink auto linear forwards;
      animation-timeline: --tl;
      animation-range: entry, exit;
    }
  }
}

แนวทางปฏิบัติแนะนำ

เมื่อใช้ภาพเคลื่อนไหวที่ขับเคลื่อนด้วยการเลื่อน คุณควรทำตามแนวทางปฏิบัติแนะนำต่อไปนี้เพื่อให้ผู้ใช้ได้รับประสบการณ์การใช้งานที่ราบรื่นและเข้าถึงได้

  • ควรรวมการตรวจหาฟีเจอร์: เบราว์เซอร์บางรายการไม่รองรับภาพเคลื่อนไหวที่ขับเคลื่อนด้วยการเลื่อน ใช้ @supports ((animation-timeline: view()) and (animation-range: entry)) เพื่อตรวจสอบการรองรับและระบุเบราว์เซอร์สำรองสำหรับเบราว์เซอร์ที่ไม่รองรับ
    • ต้องรวมการตรวจสอบ (animation-range: entry) ไว้ที่นี่เพื่อกรองเบราว์เซอร์ที่รองรับเพียงบางส่วนออก
    • อย่าใช้แพ็กเกจ scroll-timeline-polyfill สำหรับกลยุทธ์สำรอง เนื่องจากฟีเจอร์ยังไม่สมบูรณ์และมีปัญหาที่ทราบอยู่แล้วจำนวนมาก
    • หากภาพเคลื่อนไหวถือเป็นเพียงการตกแต่ง ให้เลือกใช้การเพิ่มประสิทธิภาพแบบต่อเนื่อง และอย่าระบุทางเลือกสำรอง
  • ควรเคารพค่ากำหนดของผู้ใช้: ผู้ใช้บางรายต้องการให้มีการเคลื่อนไหวน้อยลงบนเว็บ ใช้คิวรีสื่อ prefers-reduced-motion เพื่อปิดใช้หรือลดภาพเคลื่อนไหวสำหรับผู้ใช้เหล่านี้
  • ควรพยายามทำให้เฉพาะพร็อพเพอร์ตี้ CSS ที่มีประสิทธิภาพเคลื่อนไหว: เพื่อให้ภาพเคลื่อนไหวราบรื่นที่สุด ให้ใช้พร็อพเพอร์ตี้ที่เคลื่อนไหวซึ่งจัดการได้โดยเทรด Compositor ของเบราว์เซอร์ เช่น transform และ opacity การทำให้พร็อพเพอร์ตี้อื่นๆ เช่น width หรือ height เคลื่อนไหวอาจทำให้เกิดปัญหาด้านประสิทธิภาพ
  • ควรใช้ลำดับการประกาศที่ถูกต้อง: เมื่อใช้พร็อพเพอร์ตี้ชวเลข animation ให้ประกาศ animation-timeline หลังจากพร็อพเพอร์ตี้ดังกล่าวเพื่อป้องกันไม่ให้ชวเลขรีเซ็ตไทม์ไลน์

เมื่อใช้ฟังก์ชัน view() เพื่อสร้างภาพเคลื่อนไหวที่ขับเคลื่อนด้วยการเลื่อน ให้ทำดังนี้

  • ไม่บังคับ ระบุแกนที่จะติดตามอย่างชัดเจน: เมื่อไม่ได้กำหนดเป้าหมายแกน block เริ่มต้น (เช่น ในแถบเลื่อนแนวนอน) ให้ระบุแกนที่จะติดตามอย่างชัดเจนด้วย view(block) หรือ view(inline)
  • เมื่อไม่ได้ใช้ภาพเคลื่อนไหวกับวัตถุที่ติดตาม ให้ใช้ไทม์ไลน์มุมมองที่มีชื่อ

เมื่อใช้พร็อพเพอร์ตี้ view-timeline เพื่อสร้างภาพเคลื่อนไหวที่ขับเคลื่อนด้วยการเลื่อน ให้ทำดังนี้

  • ควรใช้ CSS <dashed-ident> สำหรับชื่อ
  • ไม่บังคับ ระบุแกนที่จะติดตามอย่างชัดเจน: เมื่อไม่ได้กำหนดเป้าหมายแกน block เริ่มต้น (เช่น ในแถบเลื่อนแนวนอน) ให้ระบุแกนที่จะติดตามด้วย view-timeline-axis อย่างชัดเจน
  • ควรตรวจสอบว่าขอบเขตของการค้นหาใช้งานได้: เมื่อองค์ประกอบที่ประกาศ view-timeline ไม่ใช่ระดับบนสุดแบบ Flat Tree ขององค์ประกอบที่เคลื่อนไหว ให้เพิ่มระดับการมองเห็นชื่อของ view-timeline โดยใช้ timeline-scope ในระดับบนสุดที่ใช้ร่วมกัน

ใช้ view-timeline ที่มีชื่อเมื่อองค์ประกอบหลายรายการหรือองค์ประกอบย่อยของเรื่องที่ติดตามต้องเคลื่อนไหว

การรองรับเบราว์เซอร์และกลยุทธ์สำรอง

ดังนั้น โดยทั่วไปจึงต้องมีกลยุทธ์สำรอง

สำหรับเบราว์เซอร์ที่ไม่รองรับภาพเคลื่อนไหวที่ขับเคลื่อนด้วยการเลื่อน คุณสามารถใช้โหมดสำรองเพื่อสร้างเอฟเฟกต์ภาพขึ้นมาใหม่ได้ โดยปกติแล้ว ฟังก์ชันสำรองจะสร้างขึ้นด้วย Listener การเลื่อน (สำหรับเอฟเฟกต์ ScrollTimeline) หรือ IntersectionObserver API (สำหรับเอฟเฟกต์ ViewTimeline)

ในเบราว์เซอร์ที่มีการรองรับภาพเคลื่อนไหวที่ขับเคลื่อนด้วยการเลื่อนในตัว ให้ใช้การติดตั้งใช้งาน CSS แบบเนทีฟเสมอ เนื่องจากมีประสิทธิภาพมากกว่า

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

สำหรับ Use Case นี้โดยเฉพาะ สคริปต์ต่อไปนี้จะใช้โหมดสำรองสำหรับเบราว์เซอร์ที่ไม่รองรับภาพเคลื่อนไหวที่ขับเคลื่อนด้วยการเลื่อน โดยใช้ IntersectionObserver เพื่อติดตามระดับการมองเห็นขององค์ประกอบ .wrapper และอัปเดตพร็อพเพอร์ตี้ transform ของเลเยอร์ตามตำแหน่งการเลื่อน

<script>
  if (!CSS.supports('(animation-timeline: view()) and (animation-range: entry)')) {
    const observer = new IntersectionObserver(
      (entries) => {
        for (const entry of entries) {
          // This matches the effect as defined in the CSS example above.
          // Customize this further if needed.
          entry.target.style.scale = 0.5 + entry.intersectionRatio * 0.5;
        }
      },
      {
        threshold: Array.from({ length: 101 }, (_, i) => i / 100),
      }
    );

    document.querySelectorAll('.scroller > *').forEach((el) => {
      observer.observe(el);
    });
  }
</script>