تحسين مقياس CLS: دليلك العملي لإصلاح انزياح التخطيط في 2026
دليل عملي كامل لمقياس CLS في 2026: كيف يُحسب عبر نوافذ الجلسة، وأسباب انزياح التخطيط الفعلية، وحلول aspect-ratio وsize-adjust وView Transitions، مع كود قياس ميداني عبر PerformanceObserver وقائمة تدقيق جاهزة.
مقياس Cumulative Layout Shift (CLS) هو مقياس أساسي من Core Web Vitals يقيس مدى استقرار عناصر الصفحة بصريًا أثناء تحميلها؛ لاجتياز التقييم في 2026 يجب أن تكون قيمة CLS للنسبة المئوية 75 من زياراتك 0.1 أو أقل، ويُحسب المقياس عبر أكبر نافذة جلسة (مدتها ٥ ثوانٍ مع فجوة قصوى ثانية واحدة) لمجموع درجات انزياح التخطيط. باختصار: هذا هو الدليل الذي كنتُ أتمنى قراءته قبل ثلاث سنوات حين واجهتُ أول مرة قفزة CLS من 0.05 إلى 0.28 يوم إطلاق موقع تجاري. سأشرح لك كيف يعمل المقياس على مستوى التتبّع، وما الأسباب الحقيقية للانزياح كما رأيتها في مواقع الإنتاج، وكيف تُصلحها باستخدام aspect-ratio وsize-adjust وcontent-visibility وواجهة View Transitions الحديثة.
درجة CLS جيدة تعني ≤ 0.1، ودرجة تحتاج تحسينًا بين 0.1 و0.25، وضعيفة فوق 0.25 عند النسبة المئوية 75 من مستخدميك الحقيقيين.
يُحسب CLS الآن عبر نوافذ الجلسة: كل نافذة تجمع الانزياحات المتتالية التي لا يفصل بينها أكثر من ثانية، بحدّ أقصى خمس ثوانٍ للنافذة.
ثلاثة أسباب تُنتج نحو ٩٠٪ من الانزياحات في الحقل: صور بلا أبعاد، خطوط ويب بدون size-adjust، ومحتوى ديناميكي (إعلانات، تنبيهات، ملصقات) يُحقن فوق المطوى.
الحل الذهبي للصور والفيديو هو حجز المساحة عبر width وheight أو aspect-ratio، وليس عبر min-height بالبكسل.
واجهة View Transitions المُدعّمة الآن في Chrome 126+ وSafari 18 تُحوّل تغييرات DOM إلى انتقالات مُحرَّكة لا تُحسب ضمن CLS.
راقب CLS ميدانيًا عبر PerformanceObserver وأرسل نافذة الجلسة الأسوأ إلى نظام RUM لديك، لأن Lighthouse وحده لا يعكس تجربة المستخدم الحقيقية.
ما هو مقياس CLS وكيف يُحسب في 2026؟
مقياس Cumulative Layout Shift هو مؤشر يُقدّر عدم استقرار الصفحة بصريًا: كلما تحرّك عنصر مرئي من مكانه دون تفاعل مستخدم، أضاف نقاطًا إلى الدرجة النهائية. من ناحية الحساب، لكل انزياح فردي درجة = impact fraction × distance fraction. جزء التأثير هو نسبة إطار العرض (viewport) الذي شغلته العناصر المتأثرة قبل وبعد الانزياح مجتمعةً، وجزء المسافة هو أكبر إزاحة قسمًا على أكبر بُعد لإطار العرض. مثال عملي: زر بارتفاع يُساوي ٢٠٪ من الشاشة تحرّك إلى الأسفل بمقدار ٢٥٪ من ارتفاع الشاشة، فتصبح الدرجة = 0.20 × 0.25 = 0.05.
منذ يونيو 2021 توقّف Google عن جمع كل الانزياحات على طول عمر الصفحة، وتحوّل إلى نوافذ الجلسة (session windows) لأن الطريقة الأولى كانت تُعاقب صفحات SPA الطويلة العمر ظلمًا. اليوم في 2026 القاعدة ثابتة: تُجمع الانزياحات في نافذة تبدأ عند أول انزياح، وتستمر حتى تمضي ثانية واحدة دون انزياح جديد، أو تصل مدتها إلى خمس ثوانٍ (أيهما أسبق). درجة CLS للصفحة هي درجة أكبر نافذة، وليست مجموع النوافذ. راجع توثيق Google الرسمي لمقياس CLS إن أردت البرهان الرياضي كاملًا.
عتبات النجاح في 2026 لم تتغيّر: أقل من 0.1 = جيد، بين 0.1 و0.25 = يحتاج تحسينًا، فوق 0.25 = ضعيف. ما يتغيّر باستمرار هو ما يُحتسب ضمن الانزياح: التفاعلات المستخدمية (input exclusion window = 500ms بعد النقرة أو الضغطة) مستثناة، وكذلك التحوّلات التي تستخدم CSS transform بدلًا من تغيير خصائص التخطيط. هذا التفصيل الأخير هو مفتاح كثير من الحلول التي سنراها.
ما الأسباب الرئيسية لانزياح التخطيط؟
حين أفتح ملف تتبّع (Performance trace) لموقع فيه CLS مرتفع، تتكرّر نفس القائمة تقريبًا كل مرة (صدقًا، صرت أخمّن الأسباب قبل فتح الأداة). رتّبتها هنا بحسب تكرارها في مواقع الإنتاج التي راجعتها في الاثني عشر شهرًا الماضية:
الصور والفيديو بلا أبعاد صريحة: المتصفح يعرض العنصر بارتفاع صفر حتى تصل الميتاداتا، ثم يقفز المحتوى تحته. أكثر مصدر شائع للانزياحات الكبيرة (impact fraction عالٍ).
خطوط الويب: مبادلة الخط الاحتياطي بخط الويب تُغيّر ارتفاع النص إن اختلفت مقاييس الخطّين (ascent، descent، line-gap). ينتج FOUT مرئي وانزياح صغير لكنه متكرّر.
المحتوى المُحقَن في وقت التشغيل: إعلانات، شرائط الكوكيز، نوافذ الاشتراك، الترجمات الديناميكية، أدوات الدردشة. كلها تُضاف إلى DOM بعد التحميل الأولي وتدفع كل ما تحتها للأسفل.
الجداول والشبكات ذات المحتوى غير المتزامن: تحميل بيانات JSON ثم إعادة رسم صفوف بارتفاعات مختلفة.
الرسوم المتحرّكة التي تُغيّر خصائص التخطيط: رسم متحرك على top أو width بدل transform، وكذلك height: auto إلى height: 500px عبر انتقال CSS.
محدّدات ARIA والإشعارات الفورية: عناصر aria-live التي تُدرج نصوصًا تحت عناصر ثابتة تُسبّب انزياحات صغيرة تتراكم.
iframe بأحجام متغيّرة: تضمينات YouTube أو تويتر أو خرائط تُعيد ضبط أبعادها بعد التحميل.
كيف تُصلح انزياح الصور والفيديو نهائيًا؟
القاعدة الأولى: كل عنصر <img> و<video> يجب أن يحصل على مساحة محجوزة في التخطيط قبل بدء التحميل. الطريقة الحديثة هي دمج سمات width وheight بالبكسل مع CSS يجعل الصورة مرنة عبر height: auto. المتصفح يقرأ النسبة من السمتين ويحسب aspect-ratio ضمنيًا، فيرسم صندوقًا بالأبعاد الصحيحة فور الحصول على عرض الحاوية:
<!-- الطريقة الصحيحة: أبعاد صريحة + CSS مرن -->
<img
src="/hero.avif"
width="1600"
height="900"
alt="لقطة من لوحة تحكم المقاييس"
style="width: 100%; height: auto;"
fetchpriority="high"
/>
<!-- إن كنت لا تعرف الأبعاد مسبقًا (رفع مستخدم) -->
<div style="aspect-ratio: 16 / 9; background: #eee;">
<img src="/dynamic.jpg" alt="..." style="width: 100%; height: 100%; object-fit: cover;">
</div>
لسنوات كان الحل الشائع تغليف الصورة بحاوية padding-bottom: 56.25% (حيلة padding trick). في 2026 لا حاجة لها؛ خاصية aspect-ratio مدعومة في كل المتصفحات الرئيسية منذ Safari 15 (سبتمبر 2021)، وتُغني عن التغليف. راجع توثيق MDN لخاصية aspect-ratio لتفاصيل التوافق.
الخطأ الذي أراه كثيرًا (وشخصيًا وقعت فيه أكثر من مرة): مطوّرون يستخدمون width="100%" كسمة HTML بدل قيمة بالبكسل. هذا لا يُعطي المتصفح النسبة، فيبقى الارتفاع صفرًا. اكتبها دائمًا بالأرقام الحقيقية للصورة الأصلية. أما مع <picture> وsrcset، فاجعل الأبعاد على العنصر <img> الداخلي، والمتصفح سيحافظ على النسبة عبر كل المصادر البديلة. لتعميق فهم أولوية تحميل الصور، اقرأ دليل سمة fetchpriority لتحسين أولويات الموارد.
كيف تعالج انزياح الخطوط (FOUT وFOIT)؟
خطوط الويب مصدر انزياح خبيث لأنه صغير الحجم لكنه يُصيب كل النص على الصفحة. الحل يقع على ثلاث طبقات، طبّقها جميعًا للحصول على أفضل نتيجة:
1) استخدم font-display: optional أو swap بحكمة
القيمة optional تعطي المتصفح ١٠٠ ملّي ثانية لتحميل الخط قبل الرسم. إن لم يصل في الوقت، يستخدم الاحتياطي ولا يبدّل. لا FOUT، لا انزياح، لكنك تفقد الخط الأول للمستخدم. القيمة swap تسمح بالمبادلة متى وصل الخط، وهي المناسبة لعناوين الهوية. أما القيمة block فتحجب النص لمدة تصل إلى ٣ ثوانٍ (FOIT) وتضرّ LCP بشدّة.
2) اضبط مقاييس الخط الاحتياطي عبر size-adjust
هذه هي التقنية الأقوى المتاحة في 2026 (وحدها حلّت لي مشكلة CLS في موقع مقالات كنت أعمل عليه). عرّف خطًا احتياطيًا محليًا (Arial أو Times New Roman) وعدّل مقاييسه ليطابق خط الويب تقريبًا. عندما يتم التبديل، تبقى الأسطر بنفس الارتفاع فلا انزياح:
لحساب الأرقام الصحيحة استخدم أداة مولّد الخط الاحتياطي مفتوح المصدر، أو أدخل خط الويب في أداة Malte Ubl عبر الإنترنت. الفارق قبل وبعد الضبط في مواقع راجعتها كان انخفاض CLS من 0.09 إلى 0.001 فقط لأننا استخدمنا size-adjust. صدق أو لا تصدق، هذا الفرق كافٍ ليجعل الموقع يجتاز Core Web Vitals.
لا تُضف preload لكل خط تملكه (ذلك يستهلك عرض النطاق ويضرّ LCP). اكتفِ بالوزن والنمط المستخدمَين في المطوى الأول.
التعامل مع الإعلانات والتضمينات والمحتوى المُحقَن
إعلانات AdSense وشرائط الكوكيز وواجهات الدردشة هي الجزء الأصعب في تحسين CLS، لأن حجم المحتوى غالبًا لا يكون معروفًا مسبقًا. الاستراتيجية العامة: احجز مساحة تكفي لأكبر احتمال، وإن كان أصغر اقبل الفراغ، لأن الفراغ لا يُسبب انزياحًا لكن التمدد يُسبّبه.
/* حاوية إعلان responsive بأبعاد محجوزة */
.ad-slot {
min-height: 280px; /* ارتفاع أكبر creative متوقع */
min-width: 300px;
display: flex;
align-items: center;
justify-content: center;
background: #f5f5f5; /* لتوضيح الفراغ للمصمم فقط */
contain: layout paint; /* عزل تأثير الرسم داخل الحاوية */
}
/* لشرائط الكوكيز والتنبيهات: استخدم position: fixed */
.cookie-banner {
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 999;
/* لا يؤثر على تخطيط بقية الصفحة → لا CLS */
}
القاعدة الذهبية للتنبيهات: أي عنصر يظهر فوق المحتوى (وليس ضمنه) يجب أن يكون position: fixed أو position: absolute. هذا يخرجه من التدفّق الطبيعي فلا يُزيح شيئًا. أما للـiframe embeds مثل يوتيوب وتويتر، فاحجز نسبة الأبعاد بنفس تقنية aspect-ratio السابقة.
للمحتوى الديناميكي الذي يظهر بعد تفاعل المستخدم (نقرة على "المزيد")، لديك ٥٠٠ ملّي ثانية من نافذة استثناء الإدخال؛ إن ظهر المحتوى داخل هذه النافذة فلا يُحسب على CLS. استفد من هذا في القوائم القابلة للطي.
View Transitions و content-visibility: أدوات 2026
ظهرت في السنتين الماضيتين أدوات جديدة تُعيد تعريف طريقة إدارة الانزياحات. أولها View Transitions API المدعوم في Chromium 111+ وSafari 18. بدلًا من انزياح خشن حين يتغيّر DOM، يلتقط المتصفح لقطتين قبل وبعد ويُشغّل انتقالًا سلسًا لا يُحسب ضمن CLS لأنه يستخدم مركّب GPU:
// انتقال سلس عند تحديث قائمة المنتجات
function updateProductList(newItems) {
if (!document.startViewTransition) {
// fallback للمتصفحات القديمة
renderItems(newItems);
return;
}
document.startViewTransition(() => {
renderItems(newItems);
});
}
/* CSS لتخصيص الانتقال */
::view-transition-old(root),
::view-transition-new(root) {
animation-duration: 250ms;
animation-timing-function: ease-out;
}
الأداة الثانية هي خاصية content-visibility: auto. تخبر المتصفح: "هذا القسم خارج الشاشة، لا ترسمه ولا تحسب تخطيطه حتى يقترب من إطار العرض". هذا يُقلّل عمل التخطيط الأولي بشكل كبير، لكن انتبه: يجب أن تُقرن الخاصية بـcontain-intrinsic-size لتحجز مساحة تقريبية للقسم، وإلا سيحدث انزياح كبير عند تقريب المستخدم للقسم:
.article-section {
content-visibility: auto;
contain-intrinsic-size: auto 500px; /* ارتفاع متوقع للقسم */
}
الأداة الثالثة: bfcache (Back/Forward Cache). عند العودة عبر زر الرجوع، يستعيد المتصفح الصفحة من ذاكرة كاملة بدون إعادة تنفيذ JavaScript، فلا انزياحات جديدة. تأكّد أن صفحتك مؤهّلة عبر عدم استخدام unload handler وعدم إرسال Cache-Control: no-store. لتحسين تجربة التنقل بشكل عام، راجع دليل Speculation Rules API للتنقل الفوري.
كيف تقيس CLS في المختبر والحقل؟
Lighthouse يعطيك رقم CLS معملي يعتمد على تشغيل واحد لصفحة ثابتة، وهو مفيد للاكتشاف لكنه لا يعكس الواقع. المستخدم الحقيقي يمرّر ويتفاعل ويرى الإعلانات تُحمَّل بترتيبات مختلفة. اعتمد دائمًا على قياس ميداني عبر PerformanceObserver وأرسل النتيجة إلى نظام تحليلات:
// قياس CLS بمنطق نوافذ الجلسة (كما تفعله CrUX)
let clsValue = 0;
let clsEntries = [];
let sessionValue = 0;
let sessionEntries = [];
const observer = new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
if (entry.hadRecentInput) continue; // استثناء التفاعلات
const firstSessionEntry = sessionEntries[0];
const lastSessionEntry = sessionEntries[sessionEntries.length - 1];
// إن كانت الفجوة أقل من ثانية والنافذة أقل من ٥ ثوانٍ
if (
sessionValue &&
entry.startTime - lastSessionEntry.startTime < 1000 &&
entry.startTime - firstSessionEntry.startTime < 5000
) {
sessionValue += entry.value;
sessionEntries.push(entry);
} else {
sessionValue = entry.value;
sessionEntries = [entry];
}
// احتفظ بأكبر نافذة
if (sessionValue > clsValue) {
clsValue = sessionValue;
clsEntries = sessionEntries;
}
}
});
observer.observe({ type: 'layout-shift', buffered: true });
// أرسل النتيجة عند إغلاق الصفحة
addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
navigator.sendBeacon('/analytics/cls', JSON.stringify({
cls: clsValue,
// اعرف مصادر الانزياح لتصحيحها
sources: clsEntries.flatMap(e =>
e.sources.map(s => ({
node: s.node?.tagName,
selector: s.node?.id || s.node?.className
}))
)
}));
}
});
هذا السكربت لا يقيس CLS فحسب، بل يُرسل معه أسماء العناصر التي تسبّبت في الانزياح، وهذه معلومة ذهبية للتصحيح. في Chrome DevTools افتح لسان Performance، فعّل تسجيل "Web Vitals"، وستظهر مستطيلات حمراء فوق كل عنصر انزاح مع سهم يبيّن اتجاه الحركة. راجع أيضًا دليل Google الرسمي لتصحيح انزياحات التخطيط لتقنيات متقدمة.
للمتابعة اليومية، استخدم Chrome UX Report (CrUX): بيانات حقيقية مجمّعة من مستخدمي Chrome لموقعك. تُظهر النسبة المئوية 75 التي تُقيّم عليها. اربط CrUX بـLooker Studio للحصول على لوحة CLS تاريخية. لتصحيح مشكلات الاستجابة المرتبطة، راجع دليل تحسين مقياس INP.
قائمة تدقيق CLS لعام 2026
هذه القائمة أستخدمها في مراجعاتي الشخصية، امسح بها موقعك مرة كل ربع سنة على الأقل:
☐ كل <img> و<video> له سمتا width وheight بقيم رقمية.
☐ كل حاوية بأبعاد غير معروفة تستخدم aspect-ratio أو min-height.
☐ خطوط الويب تستخدم font-display: swap أو optional، مع size-adjust على الخط الاحتياطي.
☐ الخطوط الحرجة فوق المطوى مُعلَنة بـrel="preload".
☐ الإعلانات لها حاوية بأبعاد محجوزة وcontain: layout paint.
☐ شرائط الكوكيز والتنبيهات تستخدم position: fixed.
☐ الرسوم المتحرّكة تستخدم transform وopacity فقط، لا top ولا height.
☐ صفحات SPA لديها تنفيذ View Transitions API عند التنقل.
☐ صفحات طويلة تستخدم content-visibility: auto مع contain-intrinsic-size صحيح.
☐ لا يوجد unload handler ولا Cache-Control: no-store يمنع bfcache.
☐ CLS مراقَب حقليًا عبر PerformanceObserver ومُرسَل إلى RUM.
☐ CrUX يُظهر النسبة المئوية 75 من الزيارات دون 0.1 للأجهزة المحمولة تحديدًا.
أخيرًا، تذكّر أن CLS ليس مقياسًا معزولًا. تحسين استقرار التخطيط غالبًا ما يُحسّن أيضًا LCP (لأن حجز المساحة يجعل الصورة الرئيسية تُحمَّل في وضعها النهائي). راجع دليل تحسين مقياس LCP لفهم الصورة الكاملة.
الأسئلة الشائعة
ما درجة CLS الجيدة لموقعي؟
درجة CLS جيدة تعني 0.1 أو أقل عند النسبة المئوية 75 من مستخدميك الحقيقيين على الأجهزة المحمولة وسطح المكتب. أي شيء بين 0.1 و0.25 يحتاج تحسينًا، وأكثر من 0.25 يُعدّ ضعيفًا ويؤثر سلبًا على ترتيبك في نتائج البحث.
هل يؤثر الرول المتحرك (scroll) على CLS؟
لا، حركات المستخدم مثل التمرير والنقر لا تُحسب ضمن انزياح التخطيط. المتصفح يستثني أي انزياح يحدث خلال ٥٠٠ ملّي ثانية من آخر تفاعل مستخدم عبر آلية input exclusion window. لكن انزياحات ناتجة عن التمرير (مثل تحميل صور lazy بلا أبعاد) تُحسب.
لماذا CLS مرتفع في Lighthouse لكنه منخفض في PageSpeed Insights؟
Lighthouse يقيس CLS معمليًا لمدة ٥ ثوانٍ فقط على صفحة ثابتة، بينما PageSpeed Insights يعرض بيانات CrUX الحقلية من مستخدمين حقيقيين على عمر النافذة الكاملة. الاختلاف طبيعي، واعتمد دائمًا على البيانات الحقلية لأنها تعكس تجربة المستخدم الفعلية.
هل يمكنني تجاهل CLS إذا كان موقعي SPA؟
لا. منذ 2021 يُقاس CLS عبر نوافذ الجلسة وليس على طول عمر الصفحة، ما يعني أن SPA لم تعد "معاقَبة" على تنقلاتها. Google يقيس CLS لكل تنقّل داخل SPA بشكل منفصل عبر Soft Navigations API، فيجب تحسينه على كل شاشة.
هل يمنع lazy loading انزياح التخطيط؟
لا يمنعه بل يمكن أن يُسبّبه إن لم تحجز مساحة الصورة مسبقًا. أضف دائمًا سمتَي width وheight على الصور المؤجّلة، أو غلّفها بحاوية ذات aspect-ratio محدّد. عندها يمكن استخدام loading="lazy" بأمان دون التأثير على CLS.
كيف أُصلح CLS الناتج عن إعلانات Google AdSense؟
احجز حاوية بارتفاع ثابت يساوي أطول creative متوقع، طبّق contain: layout paint عليها، واستخدم صيغ AdSense responsive مع تحديد data-ad-slot-height. تجنّب وضع الإعلانات فوق المطوى مباشرة، وضعها بعد أول قسم محتوى ليقلّل تأثير أي انزياح متبقٍ.
دليل عملي لتحسين مقياس INP في 2026: من قياسه بمكتبة web-vitals إلى تقسيم المهام بـ scheduler.yield واستخدام useTransition وWeb Workers لتفاعل تحت 200ms.
دليل عملي لسمة fetchpriority في HTML: كيف تستخدمها لتسريع LCP وتحسين Core Web Vitals في 2026، مع أمثلة كود ودراسات حالة من Google Flights و Etsy و Oodle.