المستوى المطلوب: متوسط — يفترض إنك تعرف أساسيات CSS وHTTP وLighthouse، ومسموح ما تكونش لمست خطوط الويب من قبل.
لو الـ HTML بتاعك بيوصل في 200ms لكن النصوص بتقفز مكانها لما الخط العربي يخلّص تحميل، Cumulative Layout Shift عندك بيتجاوز 0.1 بسهولة. الحل مش تغيير الخط، الحل ضبط طريقة تحميله بثلاث طبقات بسيطة.
تحميل خطوط الويب بدون CLS
المشكلة باختصار
متوسط حجم خط عربي WOFF2 كامل بين 180KB و 420KB. لما المتصفح يلاقي قاعدة @font-face بدون font-display، بيخفي النص لحد 3 ثواني انتظارًا للخط (Flash of Invisible Text — FOIT). لو الخط نزل بعد كده وأبعاد الحرف اختلفت عن خط النظام، الصفحة بتعيد ترتيب نفسها. النتيجة: CLS بيوصل 0.24 على الموبايل و LCP بيتأخر بمقدار زمن تحميل الخط.
مثال للمبتدئين قبل التعريف العلمي
تخيّل إنك بتطبخ، وفيه وصفتين على ورقتين. الورقة الأولى مكتوبة بخط مكتنز عرضه واسع، التانية بخط أرفع. لو لصقتهم جنب بعض، السطور هتتحرك وأنت بتقرأ لما الخطوط تختلف. ده اللي بيحصل بالظبط لما خط الـ fallback (Tahoma غالبًا) يكون أعرض أو أضيق من الخط النهائي (مثلًا Cairo). كل حرف ياخد عرض مختلف، والصفحة كلها تترتب من جديد لما الخط الحقيقي يوصل.
التعريف العلمي
Cumulative Layout Shift هو مقياس مجموع الحركة الرأسية والأفقية للعناصر المرئية أثناء تحميل الصفحة. الصيغة: impact_fraction × distance_fraction لكل عنصر تحرّك. الانتقال من خط نظام لخط ويب بأبعاد مختلفة بيدخل في الحسبة لو حصل بعد أول رسم للصفحة (FCP). جوجل بتعتبر CLS أقل من 0.1 جيد، فوق 0.25 سيء.
الحل: ثلاث طبقات يجب تطبيقها معًا
الطبقة الأولى — font-display
بتحدد سلوك المتصفح أثناء انتظار الخط. القيم المهمة:
swap: يعرض fallback فورًا، يستبدله لما الخط يجهز. CLS مرتفع لو الأبعاد مختلفة، لكن النص يبان من أول لحظة.optional: يستخدم fallback لو الخط ما وصلش في 100ms. صفر CLS لكن أحيانًا الخط الجديد ما يظهرش أصلًا في الزيارة الأولى.fallback: حل وسط — انتظار 100ms ثم fallback، ومهلة 3 ثواني للاستبدال.
@font-face {
font-family: 'Cairo';
src: url('/fonts/cairo-regular-ar.woff2') format('woff2');
font-weight: 400;
font-display: swap;
size-adjust: 95%;
ascent-override: 90%;
}الخصائص size-adjust و ascent-override بتقرّب أبعاد الـ fallback من أبعاد الخط النهائي قبل ما يوصل، فالقفزة لما يستبدل تكون شبه صفر.
الطبقة الثانية — subsetting للحروف العربية فقط
الخطوط من Google Fonts بتيجي بـ glyphs لـ Latin Extended وVietnamese وحتى Cyrillic. لو موقعك عربي بحت، الـ subset العربي لوحده بيكون حوالي 30% من الحجم الأصلي.
# استخدم pyftsubset من حزمة fonttools
pip install fonttools brotli
pyftsubset cairo-regular.ttf \
--output-file=cairo-regular-ar.woff2 \
--flavor=woff2 \
--unicodes=U+0600-06FF,U+0750-077F,U+FB50-FDFF,U+FE70-FEFF,U+0020-007F \
--layout-features='*' \
--no-hintingالنتيجة المقاسة على Cairo Regular: من 312KB إلى 96KB بدون فقد أي حرف عربي أو علامة تشكيل.
الطبقة الثالثة — preload
المتصفح بيكتشف الخط بعد ما يحلل الـ CSS، يعني تأخير ممكن يوصل 300ms. preload بيخلّيه يبدأ التحميل من أول HTML.
<link
rel="preload"
href="/fonts/cairo-regular-ar.woff2"
as="font"
type="font/woff2"
crossorigin="anonymous"
/>القياس: قبل وبعد على نفس الموقع
| المقياس | قبل | بعد |
|---|---|---|
| حجم خط Cairo Regular | 312KB | 96KB |
| زمن وصول الخط | 480ms | 80ms |
| CLS | 0.24 | 0.02 |
| LCP | 2.4s | 1.6s |
القياس على Lighthouse 12، شبكة Slow 4G محاكاة، صفحة فعلية بـ 4 خطوط مختلفة (Regular وBold عربي ولاتيني). الأرقام تتغير حسب الجهاز والشبكة، لكن الترتيب النسبي ثابت.
الـ trade-offs الواضحة
الحل ده بيكسبك أداء واضح، لكن بيدفعك ثمن:
- Subsetting: أي حرف خارج الـ unicode range هيظهر بـ fallback. لو حد كتب اسم بالصينية أو الكورية في تعليق، شكل النص هيبان مختلف. حدد الـ ranges بناءً على جمهورك الفعلي.
- Preload: لو حطّيت 4 خطوط في preload، بتؤخر باقي الموارد المهمة. الحد الأقصى المعقول خطّين فقط (Regular + Bold).
- font-display: optional: زائر على شبكة بطيئة ممكن مايشوف الخط أبدًا في الزيارة الأولى. يشوفه من الـ cache في الزيارة التانية.
- size-adjust: محتاج تجربة يدوية لمطابقة الـ metrics. أداة
fontmetrics.ioبتساعد، لكن المعايرة النهائية بصريًا.
متى لا تستخدم هذه الطريقة
تجنّب الـ subsetting لو موقعك متعدد اللغات بشكل ديناميكي (مدونة بترجمات يضيفها مستخدمون). تجنّب font-display: optional لو الخط جزء من الهوية البصرية ولازم يظهر دائمًا (مواقع شركات أو brand pages). تجنّب preload لو الموقع عنده موارد أهم من الخط في الـ critical path (فيديو hero مثلًا)، علشان preload بيتنافس على عرض النطاق.
الخطوة التالية
افتح موقعك في Lighthouse، اعمل Audit للأداء، وشوف قيمة CLS الحالية. لو فوق 0.1، طبّق الطبقات الثلاث بالترتيب: subset أولًا (يقلل الحجم)، بعدين font-display: swap مع size-adjust (يقلل القفزة لو الخط لسه ما وصلش)، أخيرًا preload (يبدأ التحميل أبكر). قارن بعد كل خطوة، ولو CLS لسه فوق 0.1، احتمال يكون السبب صور بدون width و height، مش الخط.
المصادر
- web.dev — Optimize web fonts (CLS):
web.dev/articles/optimize-cls-web-fonts - MDN — font-display CSS descriptor:
developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display - web.dev — size-adjust for matching fallback metrics:
web.dev/articles/css-size-adjust - fonttools subset documentation:
fonttools.readthedocs.io/en/latest/subset - Chrome DevRel — Preload critical assets:
web.dev/articles/preload-critical-assets - Google Fonts Optimization Guide:
developers.google.com/fonts/docs/getting_started