المعالجة الفعّالة في الخلفية

ميزات الويب: content-visibility

يمكنك إيقاف المهام الثقيلة في الخلفية مؤقتًا عندما لا يعرض المتصفّح أحد المكوّنات، وذلك للحفاظ على موارد النظام وعمر البطارية.

نظرة عامة

تتيح السمة content-visibility: auto للمتصفّح تخطّي عمليات احتساب العرض للعناصر البعيدة جدًا عن إطار العرض. عندما يقرّر المتصفّح تخطّي عرض عنصر أو استئنافه، يتم تنشيط الحدث contentvisibilityautostatechange على هذا العنصر.

من خلال الاستماع إلى هذا الحدث، يمكنك إيقاف العمليات المكلفة مؤقتًا، مثل صور <canvas> المتحركة أو عرض WebGL أو استطلاع بيانات WebSocket عالي التردد عندما لا تكون هناك حاجة إليها، واستئنافها في الوقت المناسب عندما يستعد المتصفّح لعرض المحتوى.

‫contentvisibilityautostatechange مقابل ‎IntersectionObserver

من المهم فهم حالات استخدام كل واجهة برمجة تطبيقات:

  • استخدِم 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. الاستماع إلى تغييرات الحالة

أضِف متتبِّع أحداث لـ 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 كبديل تقريبي. ويساعد ذلك أيضًا في توفير طاقة البطارية ووحدة المعالجة المركزية على الأجهزة القديمة.

// 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);
}