في جملة واحدة
الـ Font fallback metrics هي شوية قوى خارقة في الـ CSS بتعمل stretch (مط) أو squash (ضغط) لخط النظام (system font) بشكل اصطناعي عشان ياخد نفس المساحة الحسابية بالظبط اللي بياخدها الـ custom web font بتاعك، وده بيمنع الـ layout بتاع الصفحة إنه يتشقلب أو يتحرك بعنف لما الخط بتاعك يحمّل في النهاية.
المشكلة اللي بيحلها
عشان تفهم المشكلة، لازم في الأول تفهم أقدم حرب في تاريخ الـ typography على الويب: هنعرض إيه للمستخدم وإحنا مستنيين ملف خط (custom web font) مساحته 150KB يحمّل على كونكشن 3G بعافية شوية؟
تاريخياً، المتصفحات (browsers) كانت بتاخد واحد من طريقين، والاتنين كانوا أسوأ من بعض. الأول كان الـ FOIT (Flash of Invisible Text)، وهنا المتصفح كان بكل بساطة بيخفي الكلام لحد ما الخط يحمّل. المستخدمين كانوا بيفضلوا مبحلقين في شاشة فاضية ويسألوا نفسهم هو الموقع ده بايظ ولا إيه. التاني كان الـ FOUT (Flash of Unstyled Text)، وهنا المتصفح كان بيرندر الكلام فوراً بخط من خطوط النظام العادية (زي Arial أو Times New Roman)، وبعدين يبدله بسرعة بخطك المخصوص الرهيب في نفس الملي ثانية اللي بيوصل فيها.
مجتمع مطوري الويب اتفقوا بشكل كبير إن الـ FOUT أحسن عشان الـ accessibility (إمكانية الوصول) والـ perceived performance (الأداء الملحوظ). في النهاية، إنك تقرا كلام شكله وحش أحسن من إنك متقراش حاجة خالص. وإحنا اعتمدنا ده بشكل رسمي عن طريق الـ CSS rule اللي اسمها font-display: swap.
بس font-display: swap ولّدت وحش جديد: الـ Cumulative Layout Shift (CLS).
كل خط له هيكل عظمي مميز. حرف الـ "x" في خط Roboto أعرض فيزيائياً من الـ "x" في Arial. ارتفاع الحروف الكابيتال بيختلف. المسافات (spacing) اللي مبنية جوه ملفات الخطوط مختلفة تماماً. لما المتصفح بيبدل الـ Arial ويحط مكانه الـ Roboto، فجأة الكلام بياخد مساحة أفقية ورأسية مختلفة. البراجرافات بتعمل reflow. والكلام بينزل على سطر جديد.
لو ده حصل والمستخدم أصلاً بيقرا أو كان خلاص هيدوس على زرار، الصفحة كلها بتنط كأن زلزال ضرب الـ DOM. الزراير بتتحرك من تحت مؤشر الماوس بتاع المستخدم. الصور بتنزل لتحت في الصفحة. جوجل قدمت مقاييس الـ Core Web Vitals عشان تقيس الإحباط ده بالذات، وبقت بتعاقب الـ SEO بتاع المواقع اللي عندها CLS عالي عقاب شديد. كنا محتاجين طريقة تخلي خط النظام المؤقت والوحش يقلّد البصمة الفيزيائية بتاعة الخط المخصوص الحلو بالمللي.
إزاي بتشتغل تحت الكبوت
عشان نوقف الـ layout shift، إحنا مابنغيرش الـ custom web font بتاعنا. بدل كده، بنخلق نسخة "فرانكنشتاين" من خط النظام المحلي (local system font) باستخدام قاعدة الـ @font-face. وبنستخدم CSS descriptors متخصصة عشان نعمل override (تجاوز) للهندسة الداخلية بتاعة خط النظام.
تشريح الـ Font Box
عشان تتلاعب بخط، لازم تفهم المتصفح إزاي بيحسب البوكس (box) اللي حوالين سطر الكلام. الموضوع مش مجرد font-size. هو بيعتمد على تلات مقاييس داخلية (internal metrics) مزروعة جوه ملف الخط نفسه:
- الـ Ascent: المسافة من الـ baseline المخفي (الخط اللي الحروف بتقعد عليه) لحد قمة أطول الحروف (زي قمة الـ 'h' أو الـ 'T').
- الـ Descent: المسافة من الـ baseline لحد قاع الحروف النازلة لتحت (زي ديل الـ 'y' أو 'g' أو 'p').
- الـ Line Gap: المساحة الفاضية الموصى بيها بين الـ descent بتاع سطر والـ ascent بتاع السطر اللي بعده (وغالباً بتتسمى leading).
في الـ CSS، الارتفاع الكلي لسطر الكلام بيكون تقريباً الـ ascent + descent + line gap. لو الخط بتاعك وخط النظام عندهم قيم مختلفة للتلات مقاييس دول، الـ layout بتاعك هينط بشكل رأسي. ولو الحروف عرضها مختلف، الـ layout بتاعك هينط بشكل أفقي.
دخول الـ Overrides
الـ CSS Fonts Module Level 4 قدمت أربعة descriptors سحرية نقدر نطبقها جوه بلوك الـ @font-face. الـ descriptors دي بتعترض قراية المتصفح للمقاييس الداخلية لملف الخط وبتعملها overwrite في ساعتها (on the fly).
size-adjust: ده بيشتغل كأنه مُضاعِف (multiplier) للخط كله. لو الخط المخصوص بتاعك أعرض بشكل عام من Arial، ممكن تخلي الـsize-adjust: 110%على Arial عشان تعمله stretch أفقياً.ascent-override: بيعمل overwrite لمقياس الـ ascent الداخلي للخط.descent-override: بيعمل overwrite لمقياس الـ descent الداخلي للخط.line-gap-override: بيعمل overwrite لمقياس الـ line gap الداخلي للخط.
تجميع الحسبة
التريكة هنا إن size-adjust بيكبر أو يصغر كل حاجة—بما فيها العرض الأفقي، الـ ascent، والـ descent. فبالتالي، لو كبرت خط النظام بنسبة 110% عشان تخلي عرض السطور يطابق الـ web font بتاعك، إنت كده بدون قصد خليت الارتفاع الرأسي لخط النظام أطول بنسبة 110%، وده هيعمل vertical layout shift!
عشان نحل ده، الـ overrides دي (ascent-override، descent-override، line-gap-override) بتتحسب كنسب مئوية من قيمة الـ size-adjust، مش من الـ font size الأصلي. إنت بتستخدم size-adjust عشان تطابق العرض الأفقي بالظبط، وبعدين بتستخدم الـ override metrics دي عشان تضغط (squish) الارتفاع الرأسي وترجعه ينزل لتحت (أو تسحبه لفوق) عشان يطابق الـ bounding box الخاص بالخط المخصوص بتاعك بالمللي.
ده شكل الكود النهائي. لاحظ إزاي إحنا بنعمل font family جديدة وتخيلية خالص اسمها Roboto-Fallback بتغلف خط Arial المحلي بـ overrides دقيقة رياضياً:
/* 1. Load your actual web font */
@font-face {
font-family: 'Roboto';
src: url('/fonts/roboto.woff2') format('woff2');
font-display: swap;
}
/* 2. Create the Frankenstein fallback */
@font-face {
font-family: 'Roboto-Fallback';
src: local('Arial');
/* Stretch Arial horizontally to match Roboto */
size-adjust: 104.5%;
/* Squish Arial's vertical metrics to match Roboto */
ascent-override: 89.92%;
descent-override: 23.44%;
line-gap-override: 0%;
}
/* 3. Use them together in your CSS */
body {
font-family: 'Roboto', 'Roboto-Fallback', sans-serif;
}
لما الصفحة بتحمل، المتصفح بيرسم Roboto-Fallback فوراً. ده مجرد Arial محلي، بس ممطوط أفقياً ومضغوط رأسياً عشان يطابق البصمة الحسابية لخط Roboto بالظبط. ولما ملف Roboto الحقيقي بيخلص تحميل، الـ swap (التبديل) بيحصل، وتصميم الحروف بيتغير، لكن الـ layout مبيتحركش ولا بيكسل واحد.
قصص من أرض الواقع
فخ زرار الـ "Cancel"
منصة e-commerce كبيرة كانت بتعاني من الـ cart abandonment (سلة مشتريات مهجورة) وتيكتات دعم فني من عملاء غضبانين. المستخدمين كانوا بيشتكوا إنهم بيفضوا سلة المشتريات بتاعتهم بالغلط. فريق الهندسة سجلوا جلسات المستخدمين (user sessions) ولقوا المتهم. الموقع كان بيحمل خط للبراند تقيل جداً. المستخدم كان بيشوف زرار "Checkout" (الدفع) جنب زرار "Empty Cart" (تفريغ السلة). وفي اللحظة اللي الماوس بتاعهم بينزل يدوس على "Checkout"، خط البراند كان بيحمل. خط البراند كان أعرض بكتير من خط النظام الافتراضي. الكلام عمل reflow، وزق زرار الـ "Checkout" لليمين، وحط زرار الـ "Empty Cart" بالظبط تحت كليك الماوس بتاع المستخدم وهو نازل. تطبيق الـ size-adjust والـ metric overrides ثبّت الزراير في مكانها وقت تبديل الخط (font swap)، وده قلل معدل الإلغاء بالخطأ فوراً.
زلزال المقالات الإخبارية
بلوج تقني مشهور عملت redesign ضخم، وكان من ضمنه خط serif حديث وشكله حلو للقراية الطويلة. عشان يدوّا الأولوية للسرعة، استخدموا font-display: swap. بس القراء اللي الكونكشن بتاعهم بطيء عانوا من ظاهرة مزعجة: كانوا بيحملوا المقالة، وينزلوا تلات براجرافات، ويبدأوا قراية. بعد ثانيتين، خط الـ serif بيحمل. ولأن خط الـ serif كان الـ ascent والـ descent بتاعه أكبر من خط النظام الافتراضي، كل سطر كلام كبر بجزء من المليمتر. وعلى مدار تلات براجرافات، الزيادة دي اتراكمت وعملت vertical shift (إزاحة رأسية) ضخمة، رمت الجملة اللي المستخدم كان بيقراها بره الشاشة تماماً. بإضافة ascent-override و descent-override لخط الـ fallback المحلي، الكلام اتبدل في مكانه بالظبط، وخلى عين القارئ في المكان اللي المفروض تكون فيه.
صدمة سكور الـ Lighthouse
مطور frontend قضى تلات أسابيع بيعمل optimization لـ web application عشان الـ Core Web Vitals بتاعة جوجل. ضغطوا الصور بصيغة WebP، وعملوا defer للـ JavaScript اللي مش ضروري (non-critical)، وعملوا minify للـ CSS. شغلوا Google Lighthouse وكانوا مستنيين بكل ثقة سكور 100 بيرفكت. بدل كده، جابوا 75. الريبورت طلّع عقوبة Cumulative Layout Shift ضخمة. الـ shift ده كله كان سببه عنوان <H1> واحد نزل من سطر لسطرين لما الـ custom web font حمّل. بدل ما المطور يعمل redesign للـ header أو يعتمد على الـ invisible text، طلّع fallback metrics دقيقة لخط العنوان. الـ layout shift نزل للصفر، سكور الـ Lighthouse ضرب الـ 100، والمطور أخيراً عرف ينام.
غلطات شائعة وفخاخ
- تخمين النسب المئوية: مينفعش تجيب الأرقام دي بالويم. الحسابات محتاجة تستخرج بيانات الـ Unique Units Per Em (UPM) بالظبط، والـ ascent، والـ descent، والـ line gap مباشرة من الـ binary tables اللي جوه ملفات الـ web font والـ system font، وتعملهم cross-multiplying. دايماً استخدم programmatic generator عشان تظبط الحسبة صح.
- تطبيق الـ overrides على الـ web font: من الفخاخ الكلاسيكية إنك تضيف
size-adjustمباشرة في الـ@font-facedeclaration بتاع الـ custom web font. ده مش هيعمل أي حاجة. الـ overrides لازم تتطبق على@font-facedeclaration منفصل تماماً بيحملlocal()system font، واللي إنت بعد كده بتحطه رقم اتنين في الـfont-familystack بتاعك. - اختيار fallbacks عكس بعض في الهيكل: الـ Font metrics متقدرش تعمل معجزات. لو الخط بتاعك ultra-condensed sans-serif (مضغوط جداً وبدون حواف)، واستخدمت خط serif عريض ومسحوب كـ fallback محلي، حسبة الـ
size-adjustهتخلي خط الـ serif شكله مشوه تماماً ومش مقروء وإنت مستني التبديل. دايماً اختار خط نظام محلي (local system font) يكون هيكله (skeletal structure) قريب تقريباً من الـ web font بتاعك. - نسيان الـ
line-gap-override: المطورين غالباً بيركزوا بس على العرض والطول وبيفترضوا إنهم يخلوا الـ line gap علىnormalأو يشيلوه خالص، وده مش صح. لو المتصفح قرر يضيف default leading لخط الـ fallback ومكنش بيطابق الـ internal leading بتاع الـ web font بالمللي، الـ vertical rhythm بتاعك هيبوظ. دايماً اكتبه وحدده صراحة (explicitly).
ليه لازم يكون على الرادار بتاعك
لازم تفكر في الـ font fallback metrics في اللحظة اللي تقرر فيها تستخدم custom web font في أي بروجكت بيهتم بالـ user experience والـ SEO.
تاريخياً، الـ typography على الويب كان عبارة عن تنازلات (compromise) بين الشكل الجمالي (استخدام خطوط حلوة) والأداء (تجنب الـ layouts اللي بتنط). مع ظهور الـ CSS font metrics، التنازل ده مات وانتهى. في أي وقت بتعمل فيه audit للـ Core Web Vitals بتاعة موقع، أو بتعاني مع سكور CLS عالي، أو بتبني reusable UI components مينفعش تتهز على الشاشة، الـ font fallback overrides هي الحل الهندسي القاطع. هي بتحول الـ web fonts من عبء في التحميل لانتقال سلس ومحدش يحس بيه.
اتعمق أكتر
- MDN Web Docs: size-adjust - الدوكيومنتيشن الرسمية من موزيلا عن إزاي الـ size-adjust descriptor بيعمل scale للـ glyph outlines والـ metrics.
- MDN Web Docs: ascent-override - تعمق أكتر في إزاي تعمل override لمقياس الـ ascent بتاع الخط.
- web.dev: Improved font fallbacks - جايد ممتاز من جوجل عن استخدام الـ CSS descriptors دي بالذات عشان تصلح الـ Cumulative Layout Shift.
- W3C CSS Fonts Module Level 4 - المواصفات الرسمية (official specification) اللي بتشرح بالتفصيل الحسابات الدقيقة وقواعد الـ rendering للمتصفح الخاصة بالـ font metric overrides.