עיבוד יעיל ברקע

תכונות אינטרנט: content-visibility

כדי לחסוך במשאבי המערכת ובחיי הסוללה, אפשר להשהות משימות כבדות ברקע כשרכיב לא עובר עיבוד על ידי הדפדפן.

סקירה כללית

המאפיין content-visibility: auto מאפשר לדפדפן לדלג על חישובי עיבוד של רכיבים שנמצאים מחוץ לאזור התצוגה. כשהדפדפן מחליט לדלג על עיבוד של רכיב או להמשיך אותו, הוא מפעיל את האירוע contentvisibilityautostatechange ברכיב הזה.

האזנה לאירוע הזה מאפשרת להשהות פעולות שצורכות הרבה משאבים, כמו אנימציות <canvas>, רינדור של WebGL או דגימת נתונים בתדירות גבוהה של WebSocket, כשאין בהן צורך, ולהמשיך אותן בדיוק בזמן כשהדפדפן מתכונן להציג את התוכן.

contentvisibilityautostatechange נגד IntersectionObserver

חשוב להבין מתי להשתמש בכל API:

  • משתמשים ב-IntersectionObserver ללוגיקה של האפליקציה שקשורה לנראות החזותית המדויקת של רכיב באזור התצוגה (למשל, נתונים של טעינה מדורגת, טריגרים של גלילה מתמשכת).
  • משתמשים ב-contentvisibilityautostatechange לעבודה שדורשת עיבוד כבד (כמו עדכוני קנבס מורכבים או מוטציות 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. זיהוי שינויים במצב

מוסיפים רכיב event 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 הם שיפורים הדרגתיים (progressive enhancement). בדפדפנים שלא תומכים בהם:

  • המערכת מתעלמת ממאפיין ה-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);
}