قد يتجاوز السطر النصي ذو الأحجام المختلطة قيم line-height لكلا المقطعين، لأن محاذاة خط الأساس تجمع امتداديهما العلوي والسفلي. يوضح هذا الدليل كيفية تشخيص هذا الارتفاع الإضافي وضبط صف من تسميتين في متصفح سطح مكتب على Mac.

لماذا لا يحدد المقطع الأطول ارتفاع السطر؟

تأمل ترويسة صفحة مجردة تحتوي على Label A كبيرة بجوار Label B صغيرة. ارتفاع السطر لكلا المقطعين هو line-height: 40px، ومع ذلك يزيد قياس الصف الحاوي على 40 بكسل CSS. وتنتج هذه النتيجة عن الطريقة التي تحاذي بها التخطيطات المضمّنة الصناديق قبل حساب السطر المحيط بها.

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

خط الأساس هو مرجع محاذاة النص. وليس نقطة المنتصف العمودية لمساهمة ارتفاع السطر. إن منح نصوص بأحجام مختلفة ارتفاعات سطر متساوية لا يجعل المسافتين أعلى خط الأساس وأسفله متساويتين في كلا النصين. القيمة الابتدائية لـ vertical-align هي baseline. MDN: vertical-align

أبقِ ثلاثة قياسات منفصلة: مقاييس الصعود والهبوط للخط، والحدود المرئية للحروف المعنية، والمساحة التي تساهم في تخطيط السطر. لا ينبغي اعتبار خلفية المقطع الملونة أو مستطيل تحديده قياسًا مباشرًا لصندوق السطر المحيط؛ إذ تميّز CSS بين هندسة رسم العناصر المضمّنة وحسابات ارتفاع السطر. W3C: المسافة البادئة ونصفها

كيف يمكن لمساهمتين ارتفاع كل منهما 40px أن تنتجا سطرًا بارتفاع 48.4px؟

للنص العادي المحاذي إلى خط الأساس ذي ارتفاع السطر الصريح، يكون الحساب المفيد كالتالي: اطرح مجموع صعود الخط وهبوطه من ارتفاع السطر، ثم وزّع المساحة المتبقية بالتساوي أعلى الخط وأسفله. تُسمى هذه المساحة الإضافية المسافة البادئة، ويُسمى كل نصف منها نصف المسافة البادئة. تصف مسودة العمل CSS Inline Layout Level 3 هذا الحساب وتسمح بالمسافة البادئة السالبة. ولا تثبت حالة المسودة دعم المتصفحات لكل خاصية أحدث تتضمنها. W3C: حساب حدود التخطيط

المدخلات التالية توضيحية، وليست قياسات لخط Arial أو لأي خط على Mac. افترض دعامة للعنصر الأب تطابق مساهمتها المقطع الصغير، من دون أي محتوى مضمّن آخر أو إزاحات عمودية.

المساهمة صعود الخط هبوط الخط ارتفاع السطر نصف المسافة البادئة أعلى خط الأساس أسفل خط الأساس
المقطع الكبير 32px 8px 40px 0px 32px 8px
المقطع الصغير 9.6px 2.4px 40px 14px 23.6px 16.4px
دعامة العنصر الأب 9.6px 2.4px 40px 14px 23.6px 16.4px

في هذا المثال المحاذي إلى خط الأساس، خذ أقصى امتداد لكل جانب على حدة: max(32, 23.6, 23.6) + max(8, 16.4, 16.4) = 48.4px. تبلغ كل مساهمة 40px، لكن المقطع الكبير يحدد الحد العلوي، فيما يحدد المقطع الصغير والعنصر الأب الحد السفلي. هذه النتيجة المحسوبة مستمدة من حساب التخطيط الموثق، وليست قياسًا متوقعًا للمتصفح. W3C: نموذج التخطيط المضمّن

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

كيف يمكنك إعادة إنتاج المشكلة وفحصها على Mac؟

استخدم Chrome على Mac لتنفيذ هذه الخطوات. افتح علامة تبويب جديدة، وانتقل إلى about:blank، ثم اضغط Command + Option + J لفتح Console. توثّق Chrome هذا الاختصار لتشغيل JavaScript. Chrome DevTools: نظرة عامة

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

(async () => {
  document.getElementById('line-box-demo')?.remove();
  const host = document.createElement('div');
  host.id = 'line-box-demo';
  document.body.append(host);
  const root = host.attachShadow({ mode: 'open' });
  const style = document.createElement('style');
  style.textContent = `
    :host { display: block; padding: 24px; color: #222; background: #fff; }
    .label { margin: 20px 0 6px; font: 14px Arial, sans-serif; }
    .row { font: 12px/40px Arial, sans-serif; white-space: nowrap;
      margin: 0; padding: 0; border: 0; background: #e8eef4; }
    .large { font-size: 40px; line-height: 40px; }
    .small { font-size: 12px; line-height: 40px; }
    .trim-span .small, .trim-both .small { line-height: 12px; }
    .trim-both { font-size: 40px; }
  `;
  root.append(style);
  const cases = [
    ['', 'Shared 40px line-height'],
    ['trim-span', 'Smaller span only'],
    ['trim-both', 'Parent and smaller span']
  ];
  const rows = cases.map(([className, label]) => {
    const caption = document.createElement('p');
    caption.className = 'label';
    caption.textContent = label;
    const row = document.createElement('div');
    row.className = `row ${className}`;
    for (const [size, text] of [['large', 'Label A'], ['small', ' Label B']]) {
      const span = document.createElement('span');
      span.className = size;
      span.textContent = text;
      row.append(span);
    }
    root.append(caption, row);
    return { label, row };
  });
  await document.fonts.ready;
  console.table(rows.map(({ label, row }) => ({
    case: label,
    parentLineHeight: getComputedStyle(row).lineHeight,
    largeLineHeight: getComputedStyle(row.children[0]).lineHeight,
    smallLineHeight: getComputedStyle(row.children[1]).lineHeight,
    rowHeight: row.getBoundingClientRect().height
  })));
})();

ينتظر البرنامج document.fonts.ready، الذي يُحل بعد اكتمال تحميل الخطوط في المستند وأعمال التخطيط المرتبطة بها. إذا أضفت لاحقًا خطًا آخر أو غيّرت المحتوى، فأعِد القياس بعد استقرار تلك التغييرات. MDN: FontFaceSet.ready

اقرأ الصفوف الثلاثة بوصفها مقارنة مضبوطة. يحدد الصف الأول ارتفاع سطر قدره 40px للمقطعين وللعنصر الأب. ويقلل الصف الثاني ارتفاع سطر المقطع الصغير وحده إلى 12px. أما الصف الثالث فيغيّر أيضًا حجم خط العنصر الأب إلى 40px، بحيث تطابق دعامته حجم خط النص الكبير وارتفاع سطره. إذا ظل الصف الثاني طويلًا بينما تقلص الثالث، فهذه المقارنة تحدد مساهمة العنصر الأب.

استخدم نتائج متصفحك بدلًا من توقع قيمة 48.4px التوضيحية. يقيس البرنامج كل صف باستخدام getBoundingClientRect()، الذي يعيد مستطيل إحاطة العنصر بما يشمل الحشو والحدود. لا حشو أو حدود لهذه الصفوف، وهي تحتوي على سطر واحد غير ملتف وارتفاعها تلقائي؛ لذا تصلح ارتفاعاتها بدائل مفيدة لارتفاعات صناديق السطر. أما قياس مقطع باستخدام واجهة البرمجة نفسها، فيقيس مستطيل ذلك المقطع ولا يكشف مباشرةً صندوق السطر المحيط. MDN: getBoundingClientRect()

ما الذي ينبغي فحصه في تخطيط موجود؟

انقر على النص المعني مع الضغط على زر التحكم واختر Inspect. في Elements، افحص كلا المقطعين والعنصر الحاوي لهما. استخدم Styles لتتبّع التصريحات، وComputed للاطلاع على القيم المطبقة؛ فعّل Show All عند الحاجة لفحص الخصائص الموروثة. توثّق Chrome هذه العروض ومرشحات الخصائص فيها. Chrome DevTools: مرجع ميزات CSS

سجّل font-family وfont-size وline-height وdisplay وvertical-align لكل مشارك. ابدأ بالعنصر الأب بدلًا من الاقتصار على المقطعين. ثم اتبع هذا التسلسل لعزل السبب:

  1. تحقق من نموذج التخطيط. حدّد ما إذا كانت العناصر الفرعية تشارك في التدفق المضمّن العادي أم أنها عناصر Flex أو Grid.
  2. تحقق من وجود سطر واحد. استخدم مساحة اختبار عريضة وتسميات اصطناعية قصيرة كي لا يربك التفاف النص القياس.
  3. تحقق من المحاذاة. أزل مؤقتًا إزاحات خط الأساس أو المحاذاة العمودية غير الافتراضية قبل تطبيق الحساب المبسط أعلاه.
  4. غيّر مساهمًا واحدًا. قلّل ارتفاع سطر المقطع الصغير وقِس، ثم اختبر مساهمة خط العنصر الأب وارتفاع سطره كلًّا على حدة.
  5. أعِد الظروف الواقعية. أعد فحص الخط المقصود والعرض المتاح والنص بعد تحديد السبب.

تحقق أيضًا من الوراثة. فالقيمة بلا وحدة line-height: 1.2 تمرر معاملًا يطبقه كل عنصر فرعي على حجم خطه الخاص. أما قيمة الطول مثل 40px فتمرر طولًا ثابتًا ما لم تُستبدل؛ وتُحسب النسب المئوية نسبةً إلى حجم خط العنصر الذي يصرّح بها. وقد تمنح هذه الخيارات النص الصغير مسافة بادئة واسعة على نحو غير متوقع. تعتمد normal على الخط والمتصفح، لذا لا تصلح افتراضًا رقميًا ثابتًا. MDN: قيم line-height والوراثة

أي تعديل يلائم النتيجة المقصودة؟

اختر التعديل وفقًا لما تمثله التسميتان. فللترويسة المدمجة المكوّنة من تسميات منفصلة متطلبات تختلف عن الكلمات المضمّنة في فقرة قابلة للالتفاف.

النتيجة المقصودة التعديل المراد اختباره ما ينبغي التحقق منه
الإبقاء على خط أساس نصي مشترك امنح المقطع الصغير ارتفاع سطر مناسبًا، ووائم مقاييس خط العنصر الأب مع النص الرئيسي ألا توفر دعامة العنصر الأب امتدادًا سفليًا غير مرغوب فيه
محاذاة أعلى التسميات المنفصلة اختبر vertical-align: top على كلا المقطعين المضمّنين أن تلائم علاقة خط الأساس المتغيرة التصميم
توسيط التسميات المنفصلة داخل صف استخدم حاوية Flex مع align-items: center أن تظل طريقة العرض عند تضييق المساحة والتفاف العناصر ملائمتين للتخطيط
الحفاظ على تدفق فقرة متصل أبقِ النص مضمّنًا واستخدم قيم ارتفاع سطر موروثة ومدروسة أن تستوعب جميع الأسطر الناتجة النصوص ذات الأحجام المختلطة

تحاذي vertical-align: top العنصر وعناصره الفرعية مع أعلى السطر. أما middle فله تعريف مختلف: يحاذي منتصف العنصر مع خط أساس العنصر الأب مضافًا إليه نصف ارتفاع حرف x فيه. لذلك لا يعني التوسيط الهندسي داخل الصف كله، كما أن تغيير المحاذاة لا يضمن ارتفاعًا نهائيًا بعينه. MDN: قيم vertical-align

بالنسبة إلى تسميتين مستقلتين، يوفر Flexbox محاذاة صريحة للمحور العرضي عبر align-items. تعبّر المحاذاة إلى الوسط والمحاذاة إلى خط الأساس عن خيارين تصميميين مختلفين. ويؤدي الانتقال إلى Flexbox إلى تغيير سياق التخطيط؛ لذا استخدمه عندما تكون التسميتان عنصرين منفصلين، وقيّم النتيجة عند عروض أضيق. MDN: align-items

قد يسمح ارتفاع السطر الأصغر أيضًا برسم النص خارج مساهمته في التخطيط. افحص الأسطر المجاورة بعد تقليل التباعد؛ فالصندوق المدمج المقاس وحده لا يثبت ملاءمة النص من دون تداخل. W3C: المسافة البادئة ونصفها

هل يمكن لقياسات Canvas أن تحل محل تخطيط السطر في المتصفح؟

تجيب قياسات Canvas عن سؤال ذي صلة. يعرّف WHATWG measureText() ويميّز بين مقاييس الصعود والهبوط للخط والحدود الفعلية لنص بعينه. تفيد هذه القياسات في الرسم على Canvas، لكن قياس حدود الحبر لا يشمل دعامة العنصر الأب في DOM المحيط، والمحاذاة المضمّنة، ومساهمات ارتفاع السطر. فلا تستخدمه وحده للتنبؤ بارتفاع هذا الصف. WHATWG: TextMetrics

حافظ على حدود تشكيل النص عند التجربة. تفرض CSS استمرار التشكيل عبر الحدود المضمّنة عندما لا يكون هناك تغيير فعلي في التنسيق، في حين تفرض حدود معينة، ومنها المحاذاة العمودية غير الابتدائية، قطع التشكيل. تجنّب إحاطة كل حرف على حدة لأغراض القياس؛ فقد يؤدي تقسيم العناقيد الكتابية أو الحروف المركبة إلى نتائج غير متسقة. أبقِ التسميات الكاملة أو المقاطع النصية ذات المعنى سليمة. W3C: معالجة النص وتشكيله عبر حدود العناصر

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

المصادر