الوحدة 3 · متجاوب ومتين
خطوط ومسافات مرنة
17 دقراءة 3 دبيئة تجريب الكود
الدرس في لمحة
ابنِ خطوطًا ومسافات مرنة باستخدام clamp()، واحسب الميل ونقطة التقاطع من مقاسين في التصميم، وأبقِ النص قابلًا لتغيير الحجم للمستخدمين الذين يغيّرون حجم الخط.
لا فيديو هنا، بل الشيء الحقيقي: درس مكتوب وأمثلة قابلة للتشغيل واختبار وتدريب.
عنوان الواجهة الرئيسية في Waypoint، «Two days of front-end, in Lisbon»، حجمه 48px في تصميم الحاسوب المحمول و28px في تصميم الهاتف. كان في التطبيق الأول ثلاث media queries، وكان العنوان يقفز بين ثلاثة أحجام كلما غيّرت حجم النافذة. لا أحد يغيّر حجم النافذة للتسلية، لكن الناس يديرون الأجهزة اللوحية، ويقسمون الشاشات، ويفتحون الصفحة في ألواح جانبية ضيّقة، وكل قفزة بين هذه الأحجام بدت عرضية.
الخطوط المرنة (fluid type) تستبدل القفزات بخط مستقيم بين التصميمين.
القيمة المرنة خط مستقيم
لديك نقطتان من التصميم: عند viewport بعرض 20rem (320px) يكون العنوان 1.75rem؛ وعند 60rem (960px) يكون 3rem. بينهما تريده أن يكبر بالتناسب؛ وخارجهما يجب أن يتوقّف. هذه هي clamp() بقيمة مفضّلة خطية:
.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. من السهل أن تخطئ في هذا الحساب يدويًا، لذا إليك دالة تستطيع تشغيلها:
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:
.slot { container-type: inline-size; }
.session-card h3 {
font-size: clamp(1rem, 0.5rem + 2.5cqi, 1.75rem);
}مسافات مرنة
الخطوط التي تتدرّج بينما يبقى الـ padding ثابتًا تبدو خاطئة عند الطرفين: مزدحمة على الشاشات الكبيرة، ومتباعدة على الصغيرة. عرّف بضعة tokens للمسافات بالتقنية نفسها واستخدمها في كل مكان:
: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 لكل منها.