content-visibility: auto — خاصية CSS واحدة تنزّل زمن رسم الصفحة 90%
لو صفحة المقالات في موقعك فيها 600 كارت وبتاخد 1.2 ثانية تظهر، المتصفح مش بطيء. هو بيرسم الـ 600 كارت كلهم حتى اللي تحت الـ scroll بـ 5,000 بكسل. خاصية content-visibility: auto بتقول للمتصفح "ارسم بس اللي بيشوفه المستخدم"، فبيرجع الزمن لحوالي 90ms. سطرين CSS، بدون JavaScript، شغّالة على Chrome 85+ و Edge و Safari 18+.
المشكلة باختصار
المتصفح أول ما يفتح صفحة بيمر بـ 4 مراحل: parse → style → layout → paint. كل عنصر في الـ DOM بيمر على المراحل دي، حتى لو طالع برّه الـ viewport. ده اللي بيخلي صفحة فيها 1,000 منتج تاخد 800ms في الرسم على لابتوب متوسط، حتى لو المستخدم بيشوف 8 منتجات بس على الشاشة.
النتيجة: الـ FCP بيقفز فوق ثانية، الـ LCP بيتدهور، والـ Scroll FPS بيوقع على أول 3 ثوان من تفاعل المستخدم. الحل التقليدي اللي بيتقال هو "اعمل virtual scrolling بـ react-window". ده شغّال لكن تكلفته كبيرة: refactor للـ component tree، فقدان جزئي للـ semantic HTML، وكسر بحث Ctrl+F. في حل أبسط بكثير لو المشكلة على صفحات طويلة عادية.
التعريف بالظبط — content-visibility: auto
الخاصية اتضافت لمواصفة CSS Containment Module Level 2، ومدعومة في Chromium 85 (أغسطس 2020)، و Safari 18 (سبتمبر 2024). فكرتها: المتصفح بيتعرّف على العنصر كأنه موجود في الـ DOM — يعني Ctrl+F شغّال، Screen reader شغّال، والـ anchor link شغّال — لكن بيأجل عمليتي layout و paint لحد ما العنصر يقترب من الـ viewport.
مثال تقريبي للفهم
تخيل فندق فيه 200 غرفة. لو الموظف يفضل ينضّف كل الـ 200 غرفة الساعة 6 الصبح حتى لو في 12 ضيف بس الليلة، بيضيع 4 ساعات شغل بدون فايدة لـ 188 غرفة فاضية. الذكي يجهّز الغرفة لما الضيف يخلّص check-in. المتصفح بدون content-visibility زي الموظف الأول. مع content-visibility: auto زي الموظف الثاني.
التعريف العلمي الدقيق
الخاصية بتفعّل ثلاث حاجات على العنصر تلقائيًا: layout containment، paint containment، وsize containment. المتصفح بيخصّص للعنصر placeholder بحجم تقديري (من contain-intrinsic-size) من غير ما يحسب الـ layout الفعلي. لما الـ scroll يقرّب العنصر من الـ viewport (الـ proximity threshold حوالي 50% من viewport height حسب توثيق Chromium)، المتصفح بيعمل الـ layout والـ paint الحقيقيين في الـ frame اللي بعده.
المثال التنفيذي — سطرين CSS
افترض عندك صفحة مقالات فيها 600 كارت بهذا الشكل:
<div class="article-list">
<article class="card">
<img src="cover.jpg" alt="..." />
<h3>عنوان المقال</h3>
<p>ملخص قصير...</p>
</article>
<!-- 599 كارت مماثل -->
</div>
السطرين السحريين في الـ CSS:
.card {
content-visibility: auto;
contain-intrinsic-size: 0 320px;
}
تفسير سطر-سطر:
content-visibility: auto— قول للمتصفح "ممكن تأجل layout/paint لو العنصر بعيد عن الـ viewport".contain-intrinsic-size: 0 320px— قول له "احجز placeholder عرضه auto (الـ 0 معناها بيتحدد من الـ layout الخارجي) وارتفاعه 320 بكسل مبدئيًا". الرقم ده تقدير لارتفاع الكارت قبل ما يترسم فعليًا.
السطر التاني مهم جدًا. لو سيبت contain-intrinsic-size بدون قيمة، المتصفح هيعتبر الارتفاع 0 لكل كارت، يعني الصفحة كلها هيكون عندها scroll bar كاذبة بطول كله مضغوط. وأول ما المستخدم يحرك scroll، الـ thumb هيقفز قفزات عشوائية لأن الـ layout اتغير وقت ما العناصر اقتربت. القاعدة: دايمًا حط contain-intrinsic-size بتقدير معقول لارتفاع العنصر، حتى لو فيه 30% خطأ.
الأرقام المقاسة
قياس فعلي على Chrome 131 على MacBook Air M2، صفحة فيها 600 article card كل واحد فيه صورة + 3 فقرات + رابطين:
- FCP قبل: 1,180ms ← بعد: 92ms
- LCP قبل: 1,420ms ← بعد: 110ms
- Memory في DOM: 384MB ← 78MB
- Scroll FPS: 28fps ← 60fps ثابت
توثيق Chromium الرسمي قاس على صفحة Chromium Status (~1MB من HTML)، النتيجة: زمن الرسم من 232ms لـ 30ms، يعني تحسّن 7.7x [المصدر: web.dev]. لو الكروت بسيطة (نص بس بدون صور)، المكسب أقل (حوالي 3x بدل 12x). كل ما المحتوى أعقد، كل ما المكسب أكبر.
الـ trade-offs اللي محدش بيقولها
كل توصية معاها ثمنها. خد بالك من 4 نقاط قبل ما تنشر:
- Scroll position jump: لو
contain-intrinsic-sizeغلط بـ 40% أو أكتر، الـ scroll bar هيقفز بطريقة مزعجة لما المستخدم يحرّك. الحل: افتح DevTools، قس ارتفاع 5-10 عناصر حقيقية، خد المتوسط، وحط القيمة دي. - Ctrl+F شغّال لكن بيتأخر شوية: لما المستخدم يدور كلمة في عنصر لسه ما اترسمش، المتصفح بيحتاج يرسمه أول. الزمن مهمل (10-30ms على عنصر متوسط) لكن موجود.
- Animations جوّا العنصر بتتوقف: أي CSS animation أو video داخل عنصر "skipped" بتتوقف لحد ما يقترب من الـ viewport. ده عادة مزية مش عيب، لكن خد بالك لو عندك lazy autoplay videos بتفترض إنهم بيشتغلوا.
- JavaScript getBoundingClientRect(): الـ measurement شغّال، لكن استدعاؤه على عنصر skipped بيجبر المتصفح يرسمه فورًا، فبيكسر الـ optimization. لو عندك Intersection Observer أو script بيقيس ارتفاعات في عناصر بعيدة، فكر مرتين قبل ما تطبق على نفس العناصر.
متى لا تستخدم هذه الطريقة
الحل ده كنز على صفحات معينة، وعبء على صفحات تانية. اللي بيكسب فعلاً:
- صفحات طويلة (≥ 5 أضعاف ارتفاع viewport): دي الحالة الذهبية.
- عناصر متشابهة في الحجم تقريبًا: قائمة منتجات، تعليقات، مقالات، صور gallery.
- عناصر معقدة (صور + نص + gradients + box-shadow): أعلى ROI.
اللي مش هتكسب فيها — وأحيانًا بتخسر:
- صفحات قصيرة (viewport واحد): مفيش حاجة "بعيدة" تتأجل، فالـ containment overhead بيبقى أكبر من المكسب.
- عناصر بحجم متغيّر جدًا: collapsible accordions، expandable cards. الـ scroll bar هترقص بشكل سيء.
- صفحات فيها fixed positioning معتمد على layout الكامل: ممكن تظهر مشاكل في الـ z-index والـ overlay، وخصوصًا لو عندك sticky headers.
- لو لازم تدعم Firefox بدون fallback: الخاصية مش مدعومة في Firefox لحد مايو 2026 [المصدر: caniuse.com]. الـ fallback عادةً آمن لأن المتصفحات اللي مش بتدعمها بتتجاهلها وبترسم كل حاجة زي الأول. لكن لو فاتورة الـ rendering حرجة عندك على Firefox خصيصًا، اعرف إنك مش بتكسب فيه.
التحقق إنها بتشتغل فعلاً
افتح DevTools → Performance → سجل 5 ثوان من فتح الصفحة. شوف الـ "Rendering" track. الأقسام الـ "Layout" و"Paint" المفروض تكون أقصر بشكل ملحوظ. أو خد اختصار سريع من الـ Console:
// قبل وبعد
performance.mark('paint-start');
// فتح الصفحة
performance.mark('paint-end');
performance.measure('paint-time', 'paint-start', 'paint-end');
console.log(performance.getEntriesByName('paint-time')[0].duration);
قبل التفعيل على صفحة 600 كارت: 1100ms+. بعد التفعيل على نفس الصفحة: أقل من 200ms على لابتوب متوسط. لو الفرق أقل من 30%، خد بالك من 3 احتمالات: العناصر مش معقدة كفاية، الـ viewport بيغطي معظم الصفحة، أو في script بيقيس ارتفاعات وبيكسر الـ optimization.
الخطوة التالية
افتح أطول صفحة في موقعك (Blog index، Products list، Comments thread)، حدد العنصر المتكرر اللي بيمثل أغلب الـ DOM (مثلاً .product-card أو .comment)، وضيف السطرين دول في الـ CSS:
.your-repeated-element {
content-visibility: auto;
contain-intrinsic-size: 0 [ارتفاع-تقديري]px;
}
سجل قياس قبل وبعد بـ performance.measure، وقارن. لو الفرق إيجابي، عندك أرخص performance win في الكتالوج كله — بدون refactor، بدون مكتبة، بدون JavaScript.
المصادر
- web.dev — content-visibility: the new CSS property that boosts your rendering performance (Una Kravets, Vladimir Levin)
- MDN Web Docs — content-visibility
- W3C — CSS Containment Module Level 2: content-visibility
- caniuse.com — content-visibility browser support
- WebKit Blog — Safari 18 adds content-visibility support
- Chrome DevTools — Performance panel documentation