البرمجة مبتدئ 7 دقيقة

كيفية تحميل الصور بشكل كسول لتسريع تحميل الصفحة

يؤجل التحميل الكسول (Lazy Loading) الصور الخارجة عن الشاشة حتى يقترب المستخدم منها بالتمرير، مما يقلل حجم الصفحة الأولي ووقت التفاعل. يدعم المتصفح هذا بشكل أصلي الآن — لا حاجة لمكتبة في معظم الحالات. لكن ثمة محاذير حقيقية: تحميل صورة hero الرئيسية بشكل كسول سيضر بمعدلات Core Web Vitals. يغطي هذا الدليل متى تطبّقه ومتى لا، وكيف تبني بديل IntersectionObserver للمتصفحات القديمة.

الخطوات

  1. 1

    أضف loading="lazy" للصور خارج مرأى الشاشة

    هذا هو الحل الأبسط والأكثر فاعلية. أي صورة غير مرئية عند التحميل الأولي يجب أن تحمل loading="lazy". مدعوم في جميع المتصفحات الحديثة. لا JavaScript، لا مكتبات — خاصية واحدة فقط.

    html
    <!-- صورة منتج أسفل الطيّة: حمّلها بشكل كسول -->
    <img
      src="product-photo.jpg"
      alt="مكتب خشبي بإعداد نظيف"
      width="800"
      height="600"
      loading="lazy"
    >
  2. 2

    لا تُحمّل صور أعلى الصفحة بشكل كسول

    صور الـ hero، والشعار، وكل ما هو مرئي دون تمرير يجب ألا يُحمَّل بشكل كسول. التحميل الكسول يؤخر اكتشاف المتصفح للصورة ويضر مباشرة بمقياس Largest Contentful Paint (LCP). لأهم صورة فوق الطيّة، أضف fetchpriority="high" أيضاً.

    html
    <!-- Hero: eager (الافتراضي) + أولوية عالية -->
    <img
      src="hero.jpg"
      alt="لقطة hero للمنتج"
      width="1200"
      height="675"
      fetchpriority="high"
    >
    
    <!-- أول صورة مرئية في قائمة: eager، لا حاجة لـ fetchpriority -->
    <img
      src="first-card.jpg"
      alt="أول بطاقة"
      width="400"
      height="300"
    >
  3. 3

    حدد العرض والارتفاع دائماً

    بدون أبعاد صريحة لا يستطيع المتصفح حجز مساحة للصورة أثناء تحميلها. النتيجة هي Cumulative Layout Shift (CLS) — ينتقل المحتوى عند وصول الصورة. width و height لا يفرضان حجماً بصرياً ثابتاً؛ CSS لا تزال تتحكم في ذلك. إنها فقط تعطي المتصفح نسبة الأبعاد التي يحتاجها لحجز المساحة مسبقاً.

    html
    <!-- خاطئ: لا أبعاد = انزياح التخطيط -->
    <img src="photo.jpg" alt="..." loading="lazy">
    
    <!-- صحيح: نسبة الأبعاد محددة، لا انزياح -->
    <img
      src="photo.jpg"
      alt="..."
      width="800"
      height="533"
      loading="lazy"
    >
  4. 4

    استخدم srcset للصور المتجاوبة

    اعرض الدقة المناسبة لكل حجم شاشة. يختار المتصفح الأنسب من قائمة srcset. ادمج هذا مع loading="lazy" — يعملان باستقلالية تامة.

    html
    <img
      src="photo-800.jpg"
      srcset="photo-400.jpg 400w,
              photo-800.jpg 800w,
              photo-1200.jpg 1200w"
      sizes="(max-width: 600px) 100vw,
             (max-width: 900px) 50vw,
             800px"
      alt="صورة متجاوبة"
      width="800"
      height="533"
      loading="lazy"
    >
  5. 5

    استخدم <picture> للتبديل بين صيغ الصور

    اعرض WebP للمتصفحات التي تدعمه وارجع إلى JPEG لبقيتها. يختار <picture> أول <source> يستطيع المتصفح التعامل معه. loading="lazy" يذهب على عنصر <img> الاحتياطي — وليس على عناصر <source>.

    html
    <picture>
      <source
        type="image/webp"
        srcset="photo-400.webp 400w, photo-800.webp 800w"
        sizes="(max-width: 600px) 100vw, 800px"
      >
      <source
        type="image/jpeg"
        srcset="photo-400.jpg 400w, photo-800.jpg 800w"
        sizes="(max-width: 600px) 100vw, 800px"
      >
      <img
        src="photo-800.jpg"
        alt="صورة المنتج"
        width="800"
        height="533"
        loading="lazy"
      >
    </picture>
  6. 6

    بناء بديل IntersectionObserver

    للمتصفحات التي لا تدعم loading="lazy" (بشكل رئيسي Safari قبل iOS 15.4 وإصدارات Chrome القديمة)، استخدم IntersectionObserver. خزّن الرابط الحقيقي في data-src وبادله إلى src عندما تدخل الصورة نطاق الرؤية.

    javascript
    if ('loading' in HTMLImageElement.prototype) {
      // التحميل الكسول الأصلي مدعوم — لا شيء نفعله
    } else {
      // بديل: IntersectionObserver
      const images = document.querySelectorAll('img[data-src]');
    
      const observer = new IntersectionObserver((entries) => {
        entries.forEach(entry => {
          if (!entry.isIntersecting) return;
          const img = entry.target;
          img.src = img.dataset.src;
          if (img.dataset.srcset) img.srcset = img.dataset.srcset;
          img.removeAttribute('data-src');
          observer.unobserve(img);
        });
      }, { rootMargin: '200px' });  // حمّل 200px قبل الدخول للرؤية
    
      images.forEach(img => observer.observe(img));
    }
  7. 7

    ضع رابط الصورة في data-src للصور الاحتياطية

    عند تفعيل البديل، يخزّن HTML للصور الكسولة الرابط في data-src بدلاً من src. استخدم placeholder شفاف 1×1 في src حتى يظل العنصر صالحاً. المتصفحات الحديثة تستخدم خاصية loading ولا تقرأ data-src أبداً.

    html
    <!-- يعمل مع التحميل الكسول الأصلي والبديل JS -->
    <img
      src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
      data-src="actual-photo.jpg"
      data-srcset="actual-photo-400.jpg 400w, actual-photo-800.jpg 800w"
      alt="صورة المنتج"
      width="800"
      height="533"
      loading="lazy"
    >

نصائح ومحاذير

  • أول صورة في قائمة متكررة (بطاقات، منتجات) تكون غالباً فوق الطيّة على الشاشات الكبيرة — تحقق قبل إضافة التحميل الكسول.
  • <code>loading="lazy"</code> يعمل أيضاً على عناصر <code>&lt;iframe&gt;</code> وليس فقط الصور. مفيد لتأجيل تحميل الخرائط أو مقاطع الفيديو المضمّنة.
  • استخدم <code>rootMargin</code> بقيمة 200–400px في IntersectionObserver حتى تبدأ الصور بالتحميل قبل وصولها لنطاق الرؤية، متجنباً ظهورها الفجائي المرئي.
  • شغّل Google Lighthouse أو WebPageTest بعد إضافة التحميل الكسول. تحقق أن LCP لم يتدهور — خطأ شائع هو تحميل صورة LCP بشكل كسول.
  • لصور الخلفية في CSS (وليس <code>&lt;img&gt;</code>)، لا يوجد تحميل كسول أصلي. استخدم IntersectionObserver لإضافة/إزالة class يضبط <code>background-image</code>.

خاتمة

في معظم المشاريع، إضافة loading="lazy" لجميع الصور أسفل الطيّة وتحديد width/height لكل صورة يكفي لتحقيق تحسن ملموس في الأداء. يستحق إضافة بديل IntersectionObserver إذا كنت بحاجة لدعم المتصفحات القديمة، لكن الخاصية الأصلية تغطي الغالبية العظمى من حركة المرور الحقيقية. القاعدة الأهم: لا تُحمّل صورة LCP بشكل كسول أبداً.

#Performance #HTML #SEO
العودة إلى جميع الأدلة

هل تحتاج مساعدة في مشروعك؟

احجز استشارة مجانية لمدة 30 دقيقة لمناقشة تحدياتك التقنية واستكشاف الحلول معًا.