تحسين صور المنتج بـ AVIF وsrcset بدون تغيير التصميم
مستوى القارئ: متوسط
لو صفحة المنتج عندك جميلة بس أول صورة بتظهر بعد 4 ثواني، المقال ده هيخليك تنزل الـ LCP غالبًا للنصف بدون ما تغيّر التصميم.
المشكلة باختصار
اللي بيحصل فعلاً إن فريق التصميم يسلّم صورة hero مقاس 2400px، والموقع يرسلها لكل المستخدمين كما هي. مستخدم الموبايل يشوف نفس الملف الكبير، حتى لو الشاشة محتاجة عرض 390px فقط. الطريقة دي بتفشل في أول زيارة لأن المتصفح يدفع وقتًا كبيرًا في تنزيل الصورة وفك ضغطها قبل ظهور أهم عنصر في الصفحة.
الافتراض إن عندك صفحة منتج أو landing page فيها صورة رئيسية واحدة، وحجم الزيارات أقل من 200K زيارة يوميًا. المثال التالي مبني على صورة JPEG حجمها 920KB. بعد التحويل إلى WebP وAVIF مع أحجام متعددة، نزل النقل الفعلي على موبايل متوسط من 920KB إلى 280KB، وانخفض LCP من 4.1 ثانية إلى 1.6 ثانية في اختبار Lighthouse محلي على شبكة Fast 3G.
مثال بسيط قبل التعريف العلمي
ركز في المثال ده. عندك نفس صورة الحذاء في صفحة منتج. لو المستخدم فاتح من لابتوب، محتاج نسخة 1200px. لو فاتح من موبايل، غالبًا 640px تكفي. بدل ما تبعت نفس الصورة الكبيرة للجميع، بتجهّز 3 أحجام و3 صيغ. المتصفح يختار الأنسب حسب الشاشة والدعم.
التعريف الأدق: تحسين الصور هنا معناه تقليل البايتات المطلوبة لرسم أكبر عنصر مرئي في أول viewport، مع الحفاظ على جودة مقبولة. AVIF وWebP صيغ حديثة تضغط أفضل من JPEG غالبًا، ووسم picture يسمح بتقديم fallback لو المتصفح لا يدعم AVIF. مصدر MDN يوضح أن AVIF وWebP من الصيغ المناسبة للأداء، وشرح web.dev يوضح نمط picture مع AVIF ثم WebP ثم JPEG.
الخطوات العملية بسكربت sharp
أفضل طريقة هنا إنك تعمل التحويل في build step، مش وقت الطلب. كده بتكسب سرعة ثابتة وتخسر شوية وقت في الـ build ومساحة تخزين إضافية. ثبّت sharp وجهّز مجلد للصور الناتجة:
npm install sharp
mkdir -p public/generated-imagesبعد كده استخدم السكربت ده. بيطلع AVIF وWebP وJPEG بثلاثة أحجام من صورة واحدة:
// scripts/build-product-images.mjs
import sharp from 'sharp';
import { mkdir } from 'node:fs/promises';
const input = 'assets/product-hero.jpg';
const outputDir = 'public/generated-images';
const widths = [640, 960, 1200];
const formats = [
{ ext: 'avif', options: { quality: 50 } },
{ ext: 'webp', options: { quality: 72 } },
{ ext: 'jpg', options: { quality: 78, mozjpeg: true } },
];
await mkdir(outputDir, { recursive: true });
for (const width of widths) {
for (const format of formats) {
await sharp(input)
.resize({ width, withoutEnlargement: true })
.toFormat(format.ext === 'jpg' ? 'jpeg' : format.ext, format.options)
.toFile(`${outputDir}/product-hero-${width}.${format.ext}`);
}
}
شغّله في الـ build:
node scripts/build-product-images.mjs
ls -lh public/generated-imagesاستخدم picture وsrcset صح
التحويل وحده مش كفاية. لو HTML لسه بيشير لصورة JPEG واحدة، المتصفح مش هيستفيد. استخدم picture بالترتيب ده: AVIF أولًا، WebP ثانيًا، JPEG fallback.
<picture>
<source
type="image/avif"
srcset="/generated-images/product-hero-640.avif 640w,
/generated-images/product-hero-960.avif 960w,
/generated-images/product-hero-1200.avif 1200w"
sizes="(max-width: 768px) 100vw, 720px" />
<source
type="image/webp"
srcset="/generated-images/product-hero-640.webp 640w,
/generated-images/product-hero-960.webp 960w,
/generated-images/product-hero-1200.webp 1200w"
sizes="(max-width: 768px) 100vw, 720px" />
<img
src="/generated-images/product-hero-960.jpg"
srcset="/generated-images/product-hero-640.jpg 640w,
/generated-images/product-hero-960.jpg 960w,
/generated-images/product-hero-1200.jpg 1200w"
sizes="(max-width: 768px) 100vw, 720px"
width="1200"
height="800"
alt="حذاء رياضي أسود مع تفاصيل المنتج"
fetchpriority="high" />
</picture>بالظبط، fetchpriority="high" ينفع لصورة الـ hero فقط. لا تحطه على كل الصور. المكسب إن المتصفح يعطي الصورة المهمة أولوية أعلى. الخسارة إنك لو استخدمته على صور كثيرة هتزاحم CSS وJS المهمين.
القياس قبل وبعد
ممنوع تقول إن الصفحة اتحسنت لمجرد إن الملفات صغرت. قِس 3 أشياء: حجم الصورة المنقولة من Network tab، قيمة LCP من Lighthouse، ووقت تحميل الصورة من WebPageTest أو Chrome DevTools. في سيناريو متجر عنده 50K زائر يوميًا، تقليل 640KB من أول صورة يعني حوالي 32GB نقل أقل يوميًا لو كل زيارة فتحت صفحة المنتج مرة واحدة. الرقم تقديري، لكنه كفاية يوضح تأثير التحسين على التكلفة وتجربة الموبايل.
# قياس سريع بحجم الملفات الناتجة
ls -lh public/generated-images/product-hero-*
# Lighthouse من الطرفية لو chrome متاح
npx lighthouse https://example.com/product/shoe \
--only-categories=performance \
--throttling-method=simulate \
--output=json \
--output-path=./lighthouse-product.jsonالـ trade-off هنا واضح: هتكسب LCP أفضل ونقل أقل، مقابل pipeline صور أكثر تعقيدًا ومساحة تخزين أكبر. لو عندك 1000 صورة وكل صورة لها 9 نسخ، أنت بتدير 9000 ملف. الحل مش مجاني.
متى لا تستخدم هذه الطريقة
لا تستخدمها لو الصور أصلًا صغيرة تحت 80KB، أو لو عندك CDN بيعمل image transformation تلقائيًا بجودة جيدة، أو لو المشكلة الحقيقية في JavaScript blocking وليس الصورة. كمان لا تبدأ بـ AVIF لو جمهورك داخل من متصفحات قديمة جدًا بدون fallback. في الحالة دي WebP مع JPEG fallback أهدأ.
مصادر اعتمدت عليها
- MDN: Image file type and format guide
- web.dev: Image performance ووسم picture
- web.dev: شرح AVIF للويب
- Sharp documentation
الخطوة التالية
الخطوة التالية: اختار أثقل صورة hero عندك، شغّل عليها سكربت sharp بثلاثة أحجام، وبعدها قارن LCP قبل وبعد. لو LCP ما اتحركش على الأقل 20%، يبقى bottleneck غالبًا مش الصورة.