EduFlow
إتقان CSS الحديثة
⁦0%⁩

الوحدة 3 · متجاوب ومتين

خطوط ومسافات مرنة

17 دقراءة 3 دبيئة تجريب الكود

الدرس في لمحة

ابنِ خطوطًا ومسافات مرنة باستخدام clamp()، واحسب الميل ونقطة التقاطع من مقاسين في التصميم، وأبقِ النص قابلًا لتغيير الحجم للمستخدمين الذين يغيّرون حجم الخط.

  1. 1القيمة المرنة خط مستقيم
  2. 2لماذا تهمّ الـ rem
  3. 3الـ viewport أم الحاوية؟
  4. 4مسافات مرنة
  5. 5دورك الآن

لا فيديو هنا، بل الشيء الحقيقي: درس مكتوب وأمثلة قابلة للتشغيل واختبار وتدريب.

عنوان الواجهة الرئيسية في Waypoint، «Two days of front-end, in Lisbon»، حجمه 48px في تصميم الحاسوب المحمول و28px في تصميم الهاتف. كان في التطبيق الأول ثلاث media queries، وكان العنوان يقفز بين ثلاثة أحجام كلما غيّرت حجم النافذة. لا أحد يغيّر حجم النافذة للتسلية، لكن الناس يديرون الأجهزة اللوحية، ويقسمون الشاشات، ويفتحون الصفحة في ألواح جانبية ضيّقة، وكل قفزة بين هذه الأحجام بدت عرضية.

الخطوط المرنة (fluid type) تستبدل القفزات بخط مستقيم بين التصميمين.

القيمة المرنة خط مستقيم

لديك نقطتان من التصميم: عند viewport بعرض 20rem (320px) يكون العنوان 1.75rem؛ وعند 60rem (960px) يكون 3rem. بينهما تريده أن يكبر بالتناسب؛ وخارجهما يجب أن يتوقّف. هذه هي clamp() بقيمة مفضّلة خطية:

type.css
.hero h1 {
  font-size: clamp(1.75rem, 1.125rem + 3.125vw, 3rem);
}

القيمة الوسطى خط مستقيم، intercept + slope × width. الميل هو مقدار تغيّر الحجم لكل وحدة من العرض: (3 − 1.75) ÷ (60 − 20) = 0.03125، وهي بوحدة الـ viewport 3.125vw. ونقطة التقاطع هي حيث يقطع ذلك الخط العرض صفر: 1.75 − 0.03125 × 20 = 1.125rem. من السهل أن تخطئ في هذا الحساب يدويًا، لذا إليك دالة تستطيع تشغيلها:

JavaScript
function fluid(minSize, maxSize, minWidth, maxWidth, unit = 'vw') {
  // sizes and widths in rem
  const slope = (maxSize - minSize) / (maxWidth - minWidth);
  const intercept = minSize - slope * minWidth;
  const round = (n) => Math.round(n * 1000) / 1000;
  return `clamp(${minSize}rem, ${round(intercept)}rem + ${round(slope * 100)}${unit}, ${maxSize}rem)`;
}

console.log(fluid(1.75, 3, 20, 60));          // hero heading
console.log(fluid(1, 1.75, 20, 50, 'cqi'));   // card title, container-based

جرّب أزواجًا أخرى. مقاسان من التصميم يدخلان، وclamp() واحدة تخرج.

لماذا تهمّ الـ rem

تستطيع أن تكتب font-size: 5vw وتسمّيها خطًا مرنًا. لا تفعل. وحدات الـ viewport لا تعرف شيئًا عن تفضيلات الخط لدى المستخدم: من يضبط حجم النص الافتراضي في متصفّحه على 20px يحصل على الـ 5vw نفسها التي يحصل عليها الجميع. ومعيار Resize Text في WCAG يشترط أن يتمكّن النص من التكبير حتى 200% دون فقدان المحتوى، والخطوط التي تتجاهل إعدادات المستخدم تخذل أكثر الناس حاجةً إليها.

إدراج حدّ من rem يُبقي المستخدم داخل المعادلة. مع 1.125rem + 3.125vw يكبر جزء الـ rem حين يرفع المستخدم حجمه الافتراضي، وتتحرّك حدود الـ rem معه.

وهناك حافة رياضية يجب الانتباه إليها: إن كانت النسبة بين MAX وMIN كبيرة والميل حادًّا، فقد يقلّص التكبير المدى الفعلي كثيرًا حتى لا ينتج عن تكبير 200% نصٌّ بحجم 200%. أبقِ الحدّ الأقصى في حدود 2.5 ضعف الحدّ الأدنى للنصوص والعناوين، وستكون في أمان.

الـ viewport أم الحاوية؟

vw هي الصواب للخطوط المرتبطة بالصفحة: عنوان الواجهة الرئيسية وعنوان الصفحة. أما الخطوط داخل الـ component فاستخدم لها وحدات الحاوية. عنوان بطاقة الجلسة في الشريط الجانبي بعرض 280px وفي العمود الرئيسي بعرض 640px يجب أن يختلفا، وvw لا تستطيع التمييز بينهما. ومع الخانة كحاوية حجم (من الدرس السابق)، تعمل المعادلة نفسها مع cqi:

CSS
.slot { container-type: inline-size; }

.session-card h3 {
  font-size: clamp(1rem, 0.5rem + 2.5cqi, 1.75rem);
}

مسافات مرنة

الخطوط التي تتدرّج بينما يبقى الـ padding ثابتًا تبدو خاطئة عند الطرفين: مزدحمة على الشاشات الكبيرة، ومتباعدة على الصغيرة. عرّف بضعة tokens للمسافات بالتقنية نفسها واستخدمها في كل مكان:

tokens.css
:root {
  --space-s: clamp(0.5rem, 0.4rem + 0.5vw, 0.75rem);
  --space-m: clamp(1rem, 0.75rem + 1.25vw, 1.5rem);
  --space-l: clamp(1.5rem, 1rem + 2.5vw, 3rem);
}

.session-card { padding: var(--space-m); }
.schedule     { gap: var(--space-l); }

ثلاث درجات أو أربع تكفي. أكثر من ذلك ولن يتذكّر أحد أيّها يستخدم.

دورك الآن

عنوان البطاقة في التمرين يستخدم 5cqi، فيكبر بلا حدود ويتجاهل حجم خط المستخدم. استبدلها بـ clamp() تصيب 16px في خانة بعرض 320px و28px في خانة بعرض 800px، بحدود من rem. تغيّر الفحوصات عرض الخانة، بل وحجم خط الجذر أيضًا. وبعدها ستجعل الصفحة نفسها تعمل بالعربية، من اليمين إلى اليسار، باستخدام الخصائص المنطقية.

أهم النقاط

  • القيمة المرنة خط مستقيم بين نقطتين: clamp(MIN, intercept + slope × width, MAX)، حيث الميل (slope) هو تغيّر الحجم مقسومًا على تغيّر العرض.
  • ضع rem في نقطة التقاطع (intercept) وفي الحدود ليظل النص يستجيب لحجم الخط الافتراضي لدى المستخدم وللتكبير؛ الأحجام المبنية على الـ viewport وحده لا تفعل ذلك.
  • استخدم vw للخطوط على مستوى الصفحة مثل عنوان الواجهة الرئيسية، وcqi للخطوط داخل الـ components التي تعيش في حاويات مختلفة العرض.
  • tokens المسافات المرنة مثل --space-m تُبقي الـ padding والفجوات متناسبة مع الخطوط دون breakpoint لكل منها.

قراءات إضافية

تحقّق من فهمك

3 أسئلة

السؤال 1 من 3

يجب أن يكون عنوانٌ 24px حين يكون عرض حاويته 400px، و40px عند 1200px، وأن يكبر خطيًا بينهما. أيّ قيمة مفضّلة تحقّق ذلك؟

السؤال 2 من 3

لماذا تُعدّ font-size: 4vw على نصوص المحتوى مشكلة في إمكانية الوصول؟

السؤال 3 من 3

تظهر بطاقة جلسة في شريط جانبي عرضه 280px وفي عمود رئيسي عرضه 700px في الصفحة نفسها. أيّ وحدة يجب أن يستخدمها عنوانها المرن؟

تدريب

بيئة تجريب الكود

عنوان بطاقة مرن

عنوان البطاقة يستخدم font-size: 5cqi، فيكبر بلا نهاية ويتجاهل حجم خط المستخدم. استبدله بـ clamp() بحيث يكون العنوان:

  • 16px (1rem) حين يكون عرض الخانة 320px،
  • 28px (1.75rem) حين يكون عرض الخانة 800px،
  • خطيًا بينهما، ومحدودًا خارج هذا المدى.

اكتب الحدود بـ rem: الفحص الأخير يضبط حجم خط الجذر على 20px، ويجب أن تتدرّج الحدود معه (إلى 20px و35px). الخانة حاوية حجم أصلًا، لذا تقيسها cqi.

<div class="slot">
  <article class="session-card">
    <h3>Fluid Type Without Breakpoints</h3>
    <p>One clamp() instead of three media queries.</p>
  </article>
</div>جارٍ تحميل المحرّر…

Ctrl/⌘ + Enter للتشغيل · يعمل في بيئة معزولة داخل متصفّحك. لا شيء يغادر جهازك.

المعاينة

الـ console

(لا مخرجات)

الفحوص

  • 16px في خانة بعرض 320px
  • 28px في خانة بعرض 800px
  • 22px في المنتصف، عند 560px
  • يبقى عند 16px تحت المدى وعند 28px فوقه
  • الحدود تتبع حجم خط المستخدم (rem لا px)