การประมวลผลเบื้องหลังที่มีประสิทธิภาพ

ฟีเจอร์บนเว็บ: content-visibility

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

ภาพรวม

พร็อพเพอร์ตี้ content-visibility: auto ช่วยให้เบราว์เซอร์ข้ามการคำนวณการแสดงผลสำหรับองค์ประกอบที่อยู่นอกวิวพอร์ตได้ เมื่อเบราว์เซอร์ตัดสินใจข้ามหรือแสดงผลองค์ประกอบต่อ เบราว์เซอร์จะเริ่มเหตุการณ์ contentvisibilityautostatechange ในองค์ประกอบนั้น

การฟังเหตุการณ์นี้จะช่วยให้คุณหยุดการดำเนินการที่ใช้ทรัพยากรมากชั่วคราวได้ เช่น ภาพเคลื่อนไหว <canvas>, การแสดงผล WebGL หรือการโพลข้อมูล WebSocket ความถี่สูงเมื่อไม่จำเป็น และกลับมาดำเนินการต่อได้ทันทีเมื่อเบราว์เซอร์เตรียมแสดงเนื้อหา

contentvisibilityautostatechange เทียบกับ IntersectionObserver

คุณควรทราบว่าควรใช้ API ใดเมื่อใด ดังนี้

  • ใช้ IntersectionObserver สำหรับตรรกะของแอปพลิเคชันที่เชื่อมโยงกับการมองเห็นองค์ประกอบในวิวพอร์ต (เช่น ข้อมูลการโหลดแบบ Lazy Loading, ทริกเกอร์การเลื่อนได้ไม่รู้จบ)
  • ใช้ contentvisibilityautostatechange สำหรับงานที่ต้องมีการแสดงผลจำนวนมาก (เช่น การอัปเดต Canvas ที่ซับซ้อนหรือการเปลี่ยนแปลง DOM จำนวนมาก) เหตุการณ์นี้เชื่อมโยงโดยตรงกับวงจรการแสดงผลภายในของเบราว์เซอร์ เบราว์เซอร์มักจะเริ่มแสดงผลองค์ประกอบก่อนที่จะปรากฏบนหน้าจอจริง (ระยะขอบก่อนแสดงผล) เหตุการณ์นี้จะบอกคุณเมื่อเกิดเหตุการณ์ดังกล่าวขึ้น เพื่อให้มั่นใจว่าเนื้อหาของคุณพร้อมให้รับชมแล้ว

การใช้งาน

1. ใช้ CSS Content Visibility

ตั้งค่า content-visibility: auto ในคอนเทนเนอร์ที่มีขนาดใหญ่และระบุขนาดตัวยึดตำแหน่งเพื่อป้องกันไม่ให้แถบเลื่อนกระโดด

.heavy-component {
  /* Defer rendering work when off-screen */
  content-visibility: auto;
  
  /* Mandatory: Provide a placeholder size to prevent layouts shifts.
    - 'auto' is optional and enables the browser to remember the actual size
      once rendered. It must be paired with a <length> value to be used for
      the first render.
    - 'none' tells the browser not to apply any intrinsic width to this element.
      It can be used for either the height or the width value.
    - '500px' is the estimated height of this element. This can be any valid
      CSS <length> value. Replace it with the expected height of your
      component.
   */
  contain-intrinsic-size: auto none auto 500px;
}

2. ฟังการเปลี่ยนแปลงสถานะ

เพิ่ม Listener เหตุการณ์สำหรับ contentvisibilityautostatechange เพื่อหยุดชั่วคราวหรือกลับมาทำงานในเบื้องหลังอีกครั้ง

const component = document.querySelector('.heavy-component');

// Option 1: Direct listener (recommended)
component.addEventListener('contentvisibilityautostatechange', (event) => {
  if (event.skipped) {
    // The browser skipped rendering this content.
    // DO NOT perform heavy mutations or animation loops here.
    stopSimulation();
    pauseWebSocketPolling();
  } else {
    // The browser is about to render the content.
    // Resume your work so it is ready when visible.
    startSimulation();
    resumeWebSocketPolling();
  }
});

// Option 2: Capturing listener for event delegation
document.addEventListener('contentvisibilityautostatechange', (event) => {
  if (event.target.matches('.heavy-component')) {
    if (event.skipped) {
      stopSimulation();
    } else {
      startSimulation();
    }
  }
}, { capture: true });

กลยุทธ์สำรอง

พร็อพเพอร์ตี้ content-visibility และเหตุการณ์ contentvisibilityautostatechange ที่เชื่อมโยงเป็นการเพิ่มประสิทธิภาพแบบต่อเนื่อง ในเบราว์เซอร์ที่ไม่รองรับ ให้ทำดังนี้

  • ระบบจะไม่สนใจพร็อพเพอร์ตี้ CSS และแสดงผลเนื้อหาตามปกติ
  • เหตุการณ์จะไม่เริ่มทำงาน ดังนั้นงานที่ทำงานอยู่เบื้องหลังจะยังคงทำงานตามปกติโดยไม่มีการเพิ่มประสิทธิภาพ

หากคุณต้องรองรับการหยุดงานชั่วคราวในเบราว์เซอร์รุ่นเก่า คุณสามารถใช้ IntersectionObserver เป็นค่าประมาณคร่าวๆ ได้ ซึ่งจะช่วยประหยัดแบตเตอรี่และ CPU ในอุปกรณ์รุ่นเก่าด้วย

// Fallback using IntersectionObserver for older browsers
const target = document.getElementById('target-container');

// Check if content-visibility is supported
const isSupported = 'contentVisibility' in document.documentElement.style;

if (!isSupported) {
  const observer = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        // The element is close to the screen. Start work!
        startSimulation();
      } else {
        // The element is far away. Pause work!
        stopSimulation();
      }
    });
  }, {
    // Use rootMargin to start rendering before it hits the screen
    rootMargin: '200px'
  });

  observer.observe(target);
}