تحسين مقياس CLS: دليلك العملي للاستقرار البصري في 2026

دليل عملي لإصلاح مشاكل CLS (انزياح التخطيط التراكمي) مع أمثلة كود جاهزة. يغطي أسباب عدم الاستقرار البصري، إصلاح الصور والإعلانات وخطوط الويب، وتقنيات متقدمة مثل CSS Containment وbfcache.

ما هو مقياس CLS ولماذا هو المقياس الأكثر سوء فهم؟

مقياس Cumulative Layout Shift (CLS) — أو "انزياح التخطيط التراكمي" كما يُترجم أحيانًا — هو أحد مقاييس Core Web Vitals الثلاثة التي تعتمدها Google لتقييم تجربة المستخدم. بينما يقيس LCP سرعة التحميل وINP سرعة الاستجابة، يقيس CLS شيئًا مختلفًا تمامًا: الاستقرار البصري للصفحة.

وهنا يأتي السؤال: لماذا يهم هذا المقياس بالذات؟

تخيّل أنك تقرأ مقالًا على هاتفك، وفجأة يقفز النص كله للأسفل لأن إعلانًا قرر يظهر فوقه. أو — والأسوأ من ذلك — تحاول تضغط على زر "إلغاء"، لكن اللحظة اللي يتحرك فيها إصبعك، ينزاح التخطيط وتلاقي نفسك ضغطت على "تأكيد" بدلًا منه. هذا بالضبط ما يقيسه CLS، وصراحةً هو من أكثر الأشياء إحباطًا اللي ممكن تصادفها على الإنترنت.

والمفارقة؟ CLS هو المقياس اللي يحقق فيه أكبر عدد من المواقع نتائج "جيدة" — حوالي 72% من المواقع عالميًا وفقًا لبيانات Web Almanac 2025. لكن هذا لا يعني أنه بسيط أو مفهوم. بل على العكس تمامًا — هو المقياس الأكثر سوء فهم لأن مشاكله غالبًا لا تظهر في أدوات المختبر (Lab Tools) وتظهر فقط في بيانات المستخدمين الحقيقيين.

عتبات أداء CLS

  • جيد: 0.1 أو أقل — تجربة مستقرة بصريًا والمستخدم لا يلاحظ أي قفزات
  • يحتاج تحسين: بين 0.1 و 0.25 — انزياحات ملحوظة قد تزعج المستخدم
  • ضعيف: أكثر من 0.25 — تجربة مزعجة تؤدي لنقرات خاطئة ومغادرة الزوار

والمعيار هو نفسه المستخدم في LCP وINP: يجب أن يحقق 75% على الأقل من زيارات صفحتك نتيجة "جيد" حتى تجتاز تقييم Core Web Vitals. يعني لو 25% من زوارك يعانون من انزياحات، فأنت عندك مشكلة حتى لو الأغلبية تجربتهم ممتازة.

كيف يُحسب مقياس CLS بالضبط؟

هنا نقطة مهمة يغفلها كثير من المطورين. CLS ليس رقمًا بسيطًا — بل يُحسب من خلال صيغة تجمع بين عاملين لكل انزياح:

درجة الانزياح = نسبة التأثير × نسبة المسافة

  • نسبة التأثير (Impact Fraction): النسبة المئوية من منطقة العرض (Viewport) التي تأثرت بالانزياح. إذا انزاح عنصر يشغل 50% من الشاشة، فإن نسبة التأثير هي 0.5.
  • نسبة المسافة (Distance Fraction): أكبر مسافة انتقلها أي عنصر غير مستقر مقسومة على البعد الأكبر لمنطقة العرض. إذا تحرك عنصر بمقدار 25% من ارتفاع الشاشة، فإن نسبة المسافة هي 0.25.

مثال عملي: لو ظهر إعلان يشغل 30% من الشاشة ودفع المحتوى لأسفل بمقدار 20% من ارتفاع الشاشة، فإن درجة الانزياح هي: 0.30 × 0.20 = 0.06. رقم يبدو صغيرًا، لكن تراكم عدة انزياحات مثل هذا يوصلك بسرعة للمنطقة الحمراء.

لكن — وهذا الجزء مهم جدًا — CLS لا يجمع كل الانزياحات معًا ببساطة. بل يستخدم آلية "نوافذ الجلسة" (Session Windows): يُجمّع الانزياحات المتتالية التي تحدث خلال 5 ثوانٍ كحد أقصى مع فاصل لا يتجاوز ثانية واحدة بين كل انزياحين، ثم يُبلغ عن أكبر نافذة جلسة كقيمة CLS النهائية.

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

وهنا الفخ اللي يقع فيه الكثيرون.

أدوات المختبر مثل Lighthouse تقيس CLS فقط أثناء التحميل الأولي للصفحة. لكن في الواقع، كثير من الانزياحات تحدث بعد التحميل — عند التمرير، أو عند تحميل محتوى كسول (lazy-loaded)، أو عند تفاعل المستخدم مع عناصر ديناميكية. لذلك، بيانات CrUX (Chrome User Experience Report) هي المصدر الأدق لأنها تقيس طوال دورة حياة الصفحة بالكامل. من تجربتي الشخصية، رأيت مواقع تحصل على درجة CLS ممتازة في Lighthouse لكن نتائجها كارثية في بيانات الميدان.

الأسباب السبعة الأكثر شيوعًا لمشاكل CLS

بعد تحليل عدد كبير من المواقع، هذه هي الأسباب الأكثر تكرارًا لمشاكل الاستقرار البصري — مرتبة من الأكثر شيوعًا للأقل. خلينا نمر عليها واحدة واحدة:

1. صور وفيديوهات بدون أبعاد محددة

هذا السبب الأول بلا منازع. وفقًا للإحصائيات، 66% من صفحات الموبايل تحتوي على صورة واحدة على الأقل بدون أبعاد محددة. وعندما لا يعرف المتصفح حجم الصورة مسبقًا، يخصص لها مساحة صفرية ثم لما تتحمّل الصورة يدفع كل المحتوى أسفلها. مشكلة بسيطة والحل بسيط — لكن للأسف كثير من المطورين يتجاهلونها.

2. الإعلانات والعناصر المضمنة (Embeds)

الإعلانات هي — بصراحة — أكبر مصدر إحباط فيما يتعلق بـ CLS. شبكات الإعلانات تحقن محتوى ديناميكي بأحجام غير معروفة مسبقًا، وتفعل ذلك بعد تحميل الصفحة. والنتيجة؟ قفزات مفاجئة في التخطيط تخلي المستخدم يحس إنه فقد مكانه في الصفحة.

3. خطوط الويب (Web Fonts)

خطوط الويب قاتل صامت لـ CLS، وهالشي ما يعرفه كثير من المطورين. فقط 11% من المواقع تستخدم التحميل المسبق (preload) لخطوط الويب، مما يعني أن الغالبية العظمى معرضة لانزياحات عند تبديل الخط. المشكلة تحدث لأن المتصفح يحسب المساحة بناءً على الخط الاحتياطي، ثم يعيد الحساب عند تحميل الخط الحقيقي — والفرق بينهما (حتى لو كان بكسلات قليلة) يسبب الانزياح.

4. المحتوى الديناميكي المحقون فوق المحتوى الحالي

شريط إشعارات يظهر فجأة أعلى الصفحة، لافتة كوكيز تدفع المحتوى لأسفل، أو محتوى يُحمّل عبر AJAX ويُدرج فوق ما يقرأه المستخدم حاليًا — كل هذه أمثلة كلاسيكية. والمشكلة إن المطور عادةً ما يلاحظها لأنه يعرف إن المحتوى رايح يظهر، بينما المستخدم يُفاجأ.

5. الحركات والانتقالات غير المُحسّنة

استخدام خصائص CSS مثل height أو width أو top أو margin في الحركات يتسبب في إعادة حساب التخطيط مع كل إطار. وفقًا للبيانات، 39% من صفحات الموبايل تستخدم حركات غير مُركّبة (non-composited animations) تساهم في CLS. رقم كبير صراحةً.

6. تطبيقات الصفحة الواحدة (SPA)

في تطبيقات SPA، التنقل بين الصفحات يحدث على جانب العميل. إذا استغرقت الانتقالات أكثر من 500 مللي ثانية أو تم تحميل المحتوى بشكل غير متزامن بعد التنقل، فإن الانزياحات الناتجة تُحسب ضمن CLS. وهالشي يصير كثير في تطبيقات React وVue اللي تعتمد على جلب البيانات بعد التنقل.

7. عناصر الكاروسيل (Carousels) التي تتحرك تلقائيًا

عدد مفاجئ من عناصر الكاروسيل يستخدم حركات غير مُركّبة، ويمكن يتسبب في انزياحات تخطيط لا نهائية إذا كان يتحرك تلقائيًا بدون تفاعل من المستخدم. نصيحتي: لو تقدر تتجنب الكاروسيل التلقائي، تجنبه.

تشخيص مشاكل CLS: كيف تعرف أين المشكلة بالضبط؟

قبل ما نبدأ بالإصلاح، لازم نشخّص المشكلة أولًا. وهنا ثلاث أدوات أساسية أنصح فيها:

استخدام Performance API في المتصفح

يمكنك رصد كل انزياح تخطيط يحدث في الصفحة برمجيًا باستخدام PerformanceObserver:

// رصد انزياحات التخطيط وتحديد العناصر المسببة
new PerformanceObserver((entryList) => {
  for (const entry of entryList.getEntries()) {
    // تجاهل الانزياحات الناتجة عن تفاعل المستخدم
    if (!entry.hadRecentInput) {
      console.log("Layout Shift Score:", entry.value.toFixed(4));
      console.log("Elements that shifted:");

      for (const source of entry.sources) {
        console.log("  Element:", source.node);
        console.log("  Previous position:", source.previousRect);
        console.log("  Current position:", source.currentRect);
      }
    }
  }
}).observe({ type: "layout-shift", buffered: true });

استخدام مكتبة web-vitals

للحصول على قياس دقيق يطابق ما تسجله Google، استخدم مكتبة web-vitals الرسمية مع خاصية attribution اللي تحدد لك العنصر المسبب بالضبط:

import { onCLS } from "web-vitals/attribution";

onCLS((metric) => {
  console.log("CLS Score:", metric.value);
  console.log("Largest shift source:",
    metric.attribution.largestShiftSource);
  console.log("Largest shift element:",
    metric.attribution.largestShiftTarget);
  console.log("Largest shift time:",
    metric.attribution.largestShiftTime);

  // إرسال البيانات لنظام التحليلات
  sendToAnalytics({
    name: "CLS",
    value: metric.value,
    element: metric.attribution.largestShiftTarget,
    time: metric.attribution.largestShiftTime
  });
});

Chrome DevTools — لوحة Performance

افتح لوحة Performance في Chrome DevTools وسجّل تحميل الصفحة مع تفعيل خيار Web Vitals. ستظهر الانزياحات كعلامات حمراء أو برتقالية في قسم Experience. انقر على أي انزياح لترى العنصر المسبب بالضبط، ومقدار الانزياح، والموقع السابق والحالي للعنصر.

نصيحة متقدمة (وهذي من أهم النصائح في المقال): قارن بين قيمة CLS في P75 وP95 في لوحة CrUX Dashboard. إذا كان P75 هو 0.05 (جيد) لكن P95 هو 0.40 (ضعيف)، فهذا يعني أن 5% من المستخدمين — غالبًا على أجهزة قديمة أو اتصالات بطيئة — يعانون من تجربة سيئة جدًا. ولا تستهين بهذي النسبة.

إصلاح السبب الأول: تحديد أبعاد الصور والفيديوهات

الحل الأبسط والأكثر فعالية — وأنا أعتبره أول شي لازم تسويه: أضف دائمًا سمات width وheight لكل صورة وفيديو وiframe في HTML. المتصفحات الحديثة تستخدم هذه السمات لحساب نسبة العرض إلى الارتفاع (aspect ratio) قبل تحميل المورد، وتحجز المساحة المناسبة تلقائيًا.

<!-- سيء: بدون أبعاد — سيسبب انزياح تخطيط -->
<img src="/images/photo.webp" alt="وصف الصورة">

<!-- جيد: مع أبعاد — المتصفح يحجز المساحة مسبقًا -->
<img src="/images/photo.webp"
     alt="وصف الصورة"
     width="800"
     height="450">

<!-- أفضل: مع aspect-ratio في CSS للصور المتجاوبة -->
<style>
  .responsive-img {
    width: 100%;
    height: auto;
    aspect-ratio: 16 / 9;
  }
</style>
<img src="/images/photo.webp"
     alt="وصف الصورة"
     class="responsive-img"
     width="800"
     height="450">

ملاحظة مهمة: حتى لو كانت صورك متجاوبة (responsive) ولا تستخدم أبعادًا ثابتة في CSS، أضف سمات width وheight في HTML على أي حال. المتصفح يستخدمها فقط لحساب نسبة العرض إلى الارتفاع، مش لتحديد الحجم الفعلي.

إصلاح السبب الثاني: حجز مساحة للإعلانات والعناصر المضمنة

الإعلانات تحدٍ حقيقي لأن حجمها غالبًا غير معروف مسبقًا. لكن خلني أشارك معك بعض الاستراتيجيات اللي أثبتت فعاليتها:

/* حجز مساحة ثابتة لحاوية الإعلان */
.ad-container {
  min-height: 250px;   /* ارتفاع أدنى متوقع */
  width: 100%;
  background-color: #f5f5f5;
  display: flex;
  align-items: center;
  justify-content: center;
  /* CSS Containment لمنع تأثير الإعلان على بقية التخطيط */
  contain: layout size style;
}

.ad-container::before {
  content: "مساحة إعلانية";
  color: #ccc;
  font-size: 0.875rem;
}

/* للفيديوهات المضمنة — حجز مساحة بنسبة 16:9 */
.video-embed-container {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  background-color: #000;
}

.video-embed-container iframe {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}

نصيحة مهمة: إذا كان الإعلان لا يُملأ دائمًا (يعني أحيانًا ما يكون في إعلان للعرض)، استخدم خاصية min-height بدلًا من height ثابت، واستخدم JavaScript لطي الحاوية إذا لم يُملأ الإعلان — لكن بعد انتهاء فترة التحميل فقط:

// طي حاوية الإعلان فقط إذا لم يُملأ بعد فترة كافية
const adContainer = document.querySelector(".ad-container");
const AD_TIMEOUT = 3000; // 3 ثوانٍ

setTimeout(() => {
  if (!adContainer.querySelector("iframe, img, ins")) {
    // لا يوجد إعلان — طي الحاوية بانتقال سلس
    adContainer.style.transition = "min-height 0.3s ease";
    adContainer.style.minHeight = "0";
    adContainer.style.overflow = "hidden";
  }
}, AD_TIMEOUT);

إصلاح السبب الثالث: تحسين تحميل خطوط الويب

خطوط الويب تسبب نوعين من المشاكل: FOUT (وميض النص غير المنسّق) وFOIT (وميض النص غير المرئي). كلاهما يسبب انزياحات تخطيط. والحل المثالي في 2026 يتكون من ثلاث طبقات — وصراحةً لو طبقت الطبقة الثانية بالذات، راح تلاحظ فرق كبير.

الطبقة 1: التحميل المسبق للخطوط الحرجة

<head>
  <!-- التحميل المسبق للخط الأساسي -->
  <link rel="preload"
        href="/fonts/cairo-regular.woff2"
        as="font"
        type="font/woff2"
        crossorigin>
</head>

الطبقة 2: مطابقة مقاييس الخط الاحتياطي

هذه التقنية هي الأقوى والأقل استخدامًا (وهذا شي يحيرني بصراحة). باستخدام واصفات size-adjust وascent-override وdescent-override وline-gap-override في تعريف @font-face، تقدر تخلي الخط الاحتياطي يشغل نفس المساحة تقريبًا اللي يشغلها خط الويب — مما يلغي الانزياح عمليًا:

/* تعريف خط احتياطي بمقاييس مطابقة لخط Cairo */
@font-face {
  font-family: "Cairo Fallback";
  src: local("Arial");
  size-adjust: 97.5%;
  ascent-override: 102%;
  descent-override: 35%;
  line-gap-override: 0%;
}

/* تعريف خط الويب الأساسي */
@font-face {
  font-family: "Cairo";
  src: url("/fonts/cairo-regular.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

/* الاستخدام: الخط الاحتياطي المطابق أولًا */
body {
  font-family: "Cairo", "Cairo Fallback", sans-serif;
}

الطبقة 3: استخدام font-display: optional للخطوط غير الحرجة

لخطوط العناوين الثانوية أو الخطوط الزخرفية، استخدم font-display: optional بدلًا من swap. هذه القيمة تعني أن المتصفح يستخدم الخط فقط إذا كان متوفرًا فورًا — وإلا يستخدم الخط الاحتياطي ولا يبدّل أبدًا. صفر انزياح. مضمون:

@font-face {
  font-family: "Decorative Font";
  src: url("/fonts/decorative.woff2") format("woff2");
  font-display: optional;
}

إصلاح السبب الرابع: المحتوى الديناميكي

القاعدة الذهبية هنا بسيطة: لا تحقن محتوى أبدًا فوق المحتوى الحالي إلا إذا كان ذلك استجابة لتفاعل مباشر من المستخدم. بدلًا من ذلك:

<!-- سيء: شريط إشعارات يدفع المحتوى لأسفل -->
<div class="notification-bar">
  عرض خاص! خصم 50% على جميع المنتجات
</div>
<main>...</main>

<!-- جيد: شريط إشعارات يطفو فوق المحتوى -->
<style>
  .notification-bar {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    z-index: 100;
    /* يطفو فوق المحتوى بدون دفعه */
    transform: translateY(-100%);
    transition: transform 0.3s ease;
  }
  .notification-bar.visible {
    transform: translateY(0);
  }
</style>

أما لافتات الكوكيز، فالحل الأفضل هو وضعها أسفل الصفحة (وليس أعلاها) أو استخدام position: fixed لجعلها تطفو فوق المحتوى:

.cookie-banner {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 1000;
  /* لا تسبب أي انزياح لأنها تطفو فوق المحتوى */
}

استخدام Skeleton Screens للمحتوى غير المتزامن

عند تحميل محتوى عبر AJAX أو API، استخدم هياكل عظمية (Skeleton Screens) تحجز المساحة مسبقًا. لكن انتبه لنقطة مهمة: يجب أن يطابق ارتفاع الهيكل العظمي ارتفاع المحتوى النهائي، وإلا ستحصل على انزياح بأي حال (وهذا خطأ شائع أشوفه كثير):

<!-- هيكل عظمي لبطاقة منتج -->
<div class="product-card-skeleton">
  <div class="skeleton-image" style="aspect-ratio: 4/3;"></div>
  <div class="skeleton-title" style="height: 24px; margin: 12px 0 8px;"></div>
  <div class="skeleton-price" style="height: 20px; width: 60%;"></div>
</div>

<style>
  .product-card-skeleton [class^="skeleton-"] {
    background: linear-gradient(
      90deg,
      #e0e0e0 25%,
      #f0f0f0 50%,
      #e0e0e0 75%
    );
    background-size: 200% 100%;
    animation: shimmer 1.5s infinite;
    border-radius: 4px;
  }

  @keyframes shimmer {
    0% { background-position: 200% 0; }
    100% { background-position: -200% 0; }
  }
</style>

إصلاح السبب الخامس: استخدام حركات CSS المُركّبة

كل حركة (animation) تستخدم خصائص تؤثر على التخطيط — مثل width أو height أو top أو margin أو padding — تتسبب في إعادة حساب التخطيط مع كل إطار وتُعدّ انزياحًا. الحل؟ استخدم خصائص مُركّبة (composited) فقط:

/* سيء: حركة تسبب إعادة حساب التخطيط */
.slide-down {
  animation: badSlide 0.3s ease;
}
@keyframes badSlide {
  from { height: 0; margin-top: 0; }
  to { height: 200px; margin-top: 20px; }
}

/* جيد: حركة مُركّبة لا تؤثر على التخطيط */
.slide-down {
  animation: goodSlide 0.3s ease;
}
@keyframes goodSlide {
  from {
    transform: scaleY(0);
    opacity: 0;
  }
  to {
    transform: scaleY(1);
    opacity: 1;
  }
}

/* القاعدة: استخدم فقط هذه الخصائص في الحركات */
/* transform — لا يسبب إعادة تخطيط */
/* opacity — لا يسبب إعادة تخطيط */
/* filter — لا يسبب إعادة تخطيط */

تقنيات متقدمة لعام 2026: CSS Containment

خلينا نتكلم عن شي متقدم شوية. خاصية contain في CSS هي أداة قوية تخبر المتصفح أن عنصرًا معينًا ومحتوياته مستقلة عن بقية المستند. وهذا يعني أن أي تغيير داخل هذا العنصر لن يؤثر على التخطيط خارجه.

لماذا هذا مهم في 2026؟

المواقع اللي تحتوي على شجرة DOM عميقة (أكثر من 20 مستوى) تعاني من ظاهرة "الانزياحات المتتالية" (Cascading Shifts): تغيير بمقدار 1 بكسل في المستوى 3 من شجرة DOM يمكن يتضخم إلى انزياح 20 بكسل في المستوى 15 بسبب كيفية حساب Flexbox وGrid لـ "المساحة المتاحة". وخاصية contain تقطع هذا التسلسل.

/* احتواء أساسي: منع التأثير على التخطيط الخارجي */
.widget-container {
  contain: layout;
}

/* احتواء كامل: تخطيط + حجم + نمط */
.ad-slot,
.third-party-embed,
.dynamic-content-area {
  contain: layout size style;
}

/* content-visibility: تحسين أداء العرض للمحتوى خارج الشاشة */
.below-fold-section {
  content-visibility: auto;
  contain-intrinsic-size: 0 500px; /* حجم تقديري */
}

خاصية content-visibility: auto هي امتداد ذكي لـ CSS Containment. تخبر المتصفح: "لا تقم بعرض هذا القسم حتى يقترب من منطقة العرض." وهذا لا يحسّن CLS فقط بل يحسّن أداء العرض بشكل عام. لكن تأكد من تحديد contain-intrinsic-size حتى يحجز المتصفح المساحة الصحيحة ولا يتسبب في انزياح عند العرض.

تقنية Back/Forward Cache لتحسين CLS

ذاكرة التخزين المؤقت للخلف/للأمام (bfcache) تحفظ الصفحة كاملة في ذاكرة المتصفح عندما ينتقل المستخدم بعيدًا عنها. وعندما يعود باستخدام زر الرجوع، تُستعاد الصفحة فورًا كما كانت — بدون أي انزياحات ناتجة عن إعادة التحميل. وهذي ميزة كثير من المطورين يتجاهلونها.

كيف تتأكد من أهلية صفحتك لـ bfcache؟

// اختبار أهلية الصفحة لـ bfcache
// افتح Chrome DevTools > Application > Back/forward cache
// واضغط "Test back/forward cache"

// أشياء تمنع bfcache — تجنّبها:
// 1. استخدام unload event
window.addEventListener("unload", () => {}); // يمنع bfcache!

// استخدم pagehide بدلًا منه:
window.addEventListener("pagehide", (event) => {
  if (event.persisted) {
    // الصفحة دخلت bfcache
    console.log("Page entering bfcache");
  }
});

// 2. استعادة الحالة عند العودة من bfcache
window.addEventListener("pageshow", (event) => {
  if (event.persisted) {
    // الصفحة عادت من bfcache — حدّث المحتوى الحساس للوقت
    refreshTimeSensitiveContent();
  }
});

الأسباب الشائعة اللي تمنع bfcache:

  • استخدام حدث unload (استبدله بـ pagehide)
  • اتصالات WebSocket مفتوحة (أغلقها في pagehide وأعد فتحها في pageshow)
  • استخدام Cache-Control: no-store (استخدم no-cache بدلًا منه حيث أمكن)

التأثير التجاري: لماذا يستحق CLS اهتمامك؟

طيب، كل هالكلام التقني حلو، لكن السؤال المهم: هل فعلًا يستاهل الجهد؟

الأرقام تتحدث عن نفسها. منصة Rakuten 24 اليابانية للتجارة الإلكترونية أجرت دراسة تفصيلية عن تأثير تحسين CLS على مقاييس الأعمال، وكانت النتائج مذهلة صراحةً:

  • زيادة 53.37% في الإيرادات لكل زائر
  • زيادة 33.13% في معدل التحويل
  • انخفاض 15.20% في معدل الارتداد

أرقام مثل هذي تخليك تعيد التفكير في أولوياتك.

وفي 2026، أصبح تأثير CLS على الترتيب أكبر من أي وقت مضى. خوارزمية Google تعطي وزنًا متزايدًا لما تسميه "عدالة التجربة" (Experience Equity) — يعني أن الموقع لازم يوفر تجربة مستقرة ومستجيبة عبر جميع أنواع الأجهزة، وليس فقط الأجهزة المتطورة.

كما أن Google أصبحت أكثر حساسية تجاه "النقرات الغاضبة" (Rage Clicks) — تلك النقرات المتكررة السريعة اللي تدل على إحباط المستخدم. والانزياحات البصرية هي السبب الرئيسي وراءها.

قائمة مراجعة شاملة لتحسين CLS

قبل ما أختم، إليك قائمة مراجعة عملية يمكنك تطبيقها على أي موقع. أنصحك تحفظها وترجع لها كل ما اشتغلت على تحسين الأداء:

  1. الصور والوسائط: تحقق من أن كل صورة وiframe وفيديو يحتوي على سمات width وheight
  2. الإعلانات: أضف min-height وخاصية contain: layout size لكل حاوية إعلان
  3. الخطوط: استخدم preload للخطوط الحرجة واستخدم واصفات مطابقة المقاييس للخط الاحتياطي
  4. المحتوى الديناميكي: لا تحقن محتوى فوق المحتوى الحالي — استخدم position: fixed أو أضف المحتوى أسفل العناصر الموجودة
  5. الحركات: استخدم فقط transform وopacity في الحركات
  6. CSS Containment: أضف contain: layout للعناصر المستقلة والمضمنة من طرف ثالث
  7. bfcache: تأكد من أهلية صفحاتك لذاكرة التخزين المؤقت للخلف/للأمام
  8. Skeleton Screens: استخدم هياكل عظمية تطابق ارتفاع المحتوى النهائي للعناصر المحملة بشكل غير متزامن
  9. لافتة الكوكيز: اجعلها ثابتة أسفل الصفحة باستخدام position: fixed
  10. الكاروسيل: استخدم حركات مُركّبة فقط وتجنب التحريك التلقائي بدون تفاعل المستخدم

الأسئلة الشائعة

ما الفرق بين CLS في أدوات المختبر وبيانات الميدان؟

أدوات المختبر مثل Lighthouse تقيس CLS فقط خلال التحميل الأولي للصفحة، بينما بيانات الميدان (CrUX وSearch Console) تقيس CLS طوال دورة حياة الصفحة بالكامل — بما في ذلك الانزياحات اللي تحدث عند التمرير والتفاعل والتنقل. لذلك قد ترى نتيجة CLS ممتازة في Lighthouse لكن سيئة في Search Console. الحل هو الاعتماد على بيانات الميدان كمصدر أساسي واستخدام مكتبة web-vitals مع نظام RUM لرصد المشاكل الحقيقية.

هل تُحسب الانزياحات الناتجة عن تفاعل المستخدم ضمن CLS؟

لا. Google تستثني الانزياحات اللي تحدث خلال 500 مللي ثانية من تفاعل المستخدم (نقر، ضغط على مفتاح، إلخ). لكن انتبه: إذا تسبب التفاعل في طلب شبكة وعاد المحتوى بعد أكثر من 500 مللي ثانية، فإن الانزياح الناتج سيُحسب ضمن CLS. لذلك احرص على حجز المساحة فورًا عند التفاعل وليس عند وصول البيانات.

كيف أتعامل مع الإعلانات التي لا أعرف حجمها مسبقًا؟

استخدم مزيجًا من ثلاث تقنيات: أولًا، حدد min-height بناءً على أكثر أحجام الإعلانات شيوعًا في موقعك. ثانيًا، أضف contain: layout size لحاوية الإعلان لمنع تأثيره على بقية التخطيط. ثالثًا، إذا لم يُملأ الإعلان، أطوِ الحاوية بانتقال سلس بعد فترة انتظار كافية (3 ثوانٍ مثلًا).

هل خاصية content-visibility: auto تسبب مشاكل CLS؟

ممكن تتسبب في مشاكل CLS إذا لم تُستخدم بشكل صحيح. عند استخدام content-visibility: auto، لازم دائمًا تحدد contain-intrinsic-size لإخبار المتصفح بالحجم التقديري للعنصر. بدون ذلك، يفترض المتصفح أن ارتفاع العنصر صفر حتى يدخل منطقة العرض، مما يسبب انزياحًا كبيرًا عند العرض الفعلي.

كيف أتعامل مع انزياحات CLS في تطبيقات الصفحة الواحدة (SPA)؟

في تطبيقات SPA، التنقل بين "الصفحات" يحدث على جانب العميل بدون إعادة تحميل كاملة. لتجنب CLS: استخدم هياكل عظمية (Skeleton Screens) تحجز مساحة المحتوى القادم فورًا عند بدء التنقل، واحرص على أن الانتقالات تكون أقل من 500 مللي ثانية، واستخدم startViewTransition API (المتوفرة في Chrome) لإنشاء انتقالات سلسة لا تتسبب في انزياحات.

عن الكاتب Editorial Team

Our team of expert writers and editors.