هل سئمت من اختفاء الروابط الداخلية خلف الترويسات الثابتة؟ اكتشف خاصية CSS scroll-margin-top، الحل العصري والنظيف لإزاحة مثالية عند التنقل.
إتقان التنقل عبر الروابط الداخلية: نظرة معمقة على هوامش التمرير في CSS
في عالم تصميم الويب الحديث، يعد إنشاء تجربة مستخدم سلسة وبديهية أمرًا بالغ الأهمية. أحد أكثر أنماط واجهة المستخدم شيوعًا التي نراها اليوم هو الترويسة اللاصقة أو الثابتة. فهي تبقي على التنقل الأساسي، والعلامة التجارية، وأهم الدعوات لاتخاذ إجراء متاحة باستمرار أثناء تمرير المستخدم لأسفل الصفحة. ورغم فائدتها الكبيرة، إلا أن هذا النمط يطرح مشكلة كلاسيكية ومحبطة: الروابط الداخلية المحجوبة.
لا شك أنك واجهت هذا الموقف. تنقر على رابط في جدول المحتويات، فيقفز المتصفح بشكل صحيح إلى القسم المقابل، ولكن عنوان القسم يختفي بدقة خلف شريط التنقل اللاصق. يفقد المستخدم السياق، ويشعر بالارتباك، وتتحطم مؤقتًا التجربة المصقولة التي عملت بجد لإنشائها. لعقود من الزمن، حارب المطورون هذه المشكلة بمجموعة متنوعة من الحيل الذكية، ولكن غير المثالية، التي تتضمن الحشو (padding)، والعناصر الزائفة (pseudo-elements)، أو جافا سكريبت.
لحسن الحظ، انتهى عصر الحيل. قدمت مجموعة عمل CSS حلاً أنيقًا وقويًا ومصممًا خصيصًا لهذه المشكلة بالذات: خاصية scroll-margin. هذا المقال هو دليل شامل لفهم وإتقان هوامش التمرير في CSS، لتحويل التنقل في موقعك من مصدر للإحباط إلى نقطة للمتعة.
المشكلة الكلاسيكية: هدف الرابط الداخلي المحجوب
قبل أن نحتفل بالحل، دعونا نحلل المشكلة بالكامل. تنشأ المشكلة من تعارض بسيط بين ميزتين أساسيتين في الويب: معرفات الأجزاء (الروابط الداخلية) والتمركز الثابت (fixed positioning).
إليك السيناريو المعتاد:
- الهيكل: لديك صفحة طويلة التمرير ذات أقسام مميزة. كل قسم رئيسي له عنوان بسمة `id` فريدة، مثل `
من نحن
`. - التنقل: في أعلى الصفحة، لديك قائمة تنقل. يمكن أن تكون جدول محتويات أو قائمة التنقل الرئيسية للموقع. تحتوي على روابط داخلية تشير إلى معرفات تلك الأقسام، مثل `تعرف على شركتنا`.
- العنصر اللاصق: لديك عنصر ترويسة مصمم بـ `position: sticky; top: 0;` أو `position: fixed; top: 0;`. هذا العنصر له ارتفاع محدد، على سبيل المثال، 80 بكسل.
- التفاعل: ينقر المستخدم على رابط "تعرف على شركتنا".
- سلوك المتصفح: السلوك الافتراضي للمتصفح هو تمرير الصفحة بحيث تتم محاذاة الحافة العلوية للعنصر المستهدف (العنصر `
` ذو المعرف `id="about-us"`) تمامًا مع الحافة العلوية لإطار العرض (viewport).
- التعارض: نظرًا لأن ترويستك اللاصقة التي يبلغ ارتفاعها 80 بكسل تشغل الجزء العلوي من إطار العرض، فإنها الآن تغطي عنصر `
` الذي قام المتصفح للتو بالتمرير إليه. يرى المستخدم المحتوى *أسفل* العنوان، ولكن ليس العنوان نفسه.
هذا ليس خطأً برمجيًا؛ إنه مجرد نتيجة منطقية لكيفية تصميم هذه الأنظمة لتعمل بشكل مستقل. آلية التمرير لا تعرف بطبيعتها عن العنصر ذي الموضع الثابت الموجود كطبقة فوق إطار العرض. هذا التعارض البسيط أدى إلى سنوات من الحلول الإبداعية.
الحيل القديمة: رحلة في ذاكرة الماضي
لتقدير أناقة `scroll-margin` حقًا، من المفيد فهم "الطرق القديمة" التي كنا نستخدمها لحل هذه المشكلة. لا تزال هذه الطرق موجودة في عدد لا يحصى من قواعد الأكواد عبر الويب، والتعرف عليها مفيد لأي مطور.
الحيلة رقم 1: خدعة الحشو والهامش السلبي
كان هذا أحد أقدم الحلول وأكثرها شيوعًا باستخدام CSS فقط. الفكرة هي إضافة حشو (padding) إلى الجزء العلوي من العنصر المستهدف لإنشاء مساحة، ثم استخدام هامش سلبي لسحب محتوى العنصر مرة أخرى إلى موضعه المرئي الأصلي.
مثال الكود:
CSS
.sticky-header { height: 80px; position: sticky; top: 0; }
h2[id] {
padding-top: 80px; /* إنشاء مساحة تساوي ارتفاع الترويسة */
margin-top: -80px; /* سحب محتوى العنصر مرة أخرى لأعلى */
}
لماذا تعتبر حيلة:
- تغير نموذج الصندوق (Box Model): هذا يتلاعب مباشرة بتخطيط العنصر بطريقة غير بديهية. يمكن أن يتداخل الحشو الإضافي مع ألوان الخلفية والحدود والأنماط الأخرى المطبقة على العنصر.
- هشة: إنها تخلق اقترانًا وثيقًا بين ارتفاع الترويسة وتصميم العنصر المستهدف. إذا قرر المصمم تغيير ارتفاع الترويسة، يجب على المطور أن يتذكر العثور على قاعدة الحشو/الهامش هذه وتحديثها في كل مكان تُستخدم فيه.
- غير دلالية: يوجد الحشو والهامش لغرض ميكانيكي بحت يتعلق بالتمرير، وليس لأي سبب حقيقي يتعلق بالتخطيط أو التصميم، مما يجعل الكود أصعب في الفهم.
الحيلة رقم 2: خدعة العنصر الزائف (Pseudo-element)
نهج أكثر تطورًا قليلاً باستخدام CSS فقط يتضمن استخدام عنصر زائف (`::before`) على الهدف. يتم وضع العنصر الزائف فوق العنصر الفعلي ويعمل كهدف تمرير غير مرئي.
مثال الكود:
CSS
h2[id] {
position: relative;
}
h2[id]::before {
content: "";
display: block;
height: 90px; /* ارتفاع الترويسة + بعض المساحة للتنفس */
margin-top: -90px;
visibility: hidden;
}
لماذا تعتبر حيلة:
- أكثر تعقيدًا: هذا ذكي، لكنه يضيف تعقيدًا وهو أقل وضوحًا للمطورين غير المعتادين على هذا النمط.
- يستهلك العنصر الزائف: يستخدم العنصر الزائف `::before`، والذي قد يكون مطلوبًا لأغراض زخرفية أو وظيفية أخرى على نفس العنصر.
- لا تزال حيلة: على الرغم من أنها تتجنب العبث بنموذج الصندوق المباشر للعنصر المستهدف، إلا أنها لا تزال حلاً بديلاً يستخدم خصائص CSS لشيء آخر غير الغرض المقصود منها.
الحيلة رقم 3: تدخل جافا سكريبت
للحصول على تحكم مطلق، لجأ العديد من المطورين إلى جافا سكريبت. يقوم السكريبت باعتراض حدث النقر على جميع الروابط الداخلية، ومنع القفزة الافتراضية للمتصفح، وحساب ارتفاع الترويسة، ثم تمرير الصفحة يدويًا إلى الموضع الصحيح.
مثال الكود (مفاهيمي):
JavaScript
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function (e) {
e.preventDefault();
const headerHeight = document.querySelector('.sticky-header').offsetHeight;
const targetElement = document.querySelector(this.getAttribute('href'));
if (targetElement) {
const elementPosition = targetElement.getBoundingClientRect().top;
const offsetPosition = elementPosition + window.pageYOffset - headerHeight;
window.scrollTo({
top: offsetPosition,
behavior: 'smooth'
});
}
});
});
لماذا تعتبر حيلة:
- مبالغة: تستخدم لغة برمجة قوية لحل مشكلة تتعلق أساسًا بالتخطيط والعرض.
- تكلفة أداء: على الرغم من أنها غالبًا ما تكون ضئيلة، إلا أنها تضيف عبء تنفيذ جافا سكريبت إلى الصفحة.
- الهشاشة: يمكن أن يتعطل السكريبت إذا تغيرت أسماء الفئات (classes). قد لا يأخذ في الاعتبار الترويسات التي يتغير ارتفاعها ديناميكيًا (على سبيل المثال، عند تغيير حجم النافذة) بدون كود إضافي وأكثر تعقيدًا.
- مخاوف تتعلق بإمكانية الوصول: إذا لم يتم تنفيذها بعناية، يمكن أن تتداخل مع السلوك المتوقع للمتصفح لأدوات إمكانية الوصول والتنقل باستخدام لوحة المفاتيح. كما أنها تفشل تمامًا إذا تم تعطيل جافا سكريبت أو فشل تحميلها.
الحل الحديث: تقديم خاصية `scroll-margin`
هنا يأتي دور `scroll-margin`. تم تصميم خاصية CSS هذه (ومتغيراتها الطويلة) خصيصًا لهذا النوع من المشاكل. تسمح لك بتحديد هامش خارجي حول عنصر يُستخدم لضبط منطقة انجذاب التمرير.
فكر فيها كمنطقة عازلة غير مرئية. عندما يُطلب من المتصفح التمرير إلى عنصر (عبر رابط داخلي، على سبيل المثال)، فإنه لا يقوم بمحاذاة مربع حدود العنصر (border-box) مع حافة إطار العرض. بدلاً من ذلك، يقوم بمحاذاة منطقة `scroll-margin`. هذا يعني أن العنصر الفعلي يتم دفعه لأسفل، بعيدًا عن الترويسة اللاصقة، دون التأثير على تخطيطه بأي شكل من الأشكال.
نجم العرض: `scroll-margin-top`
لمشكلة الترويسة اللاصقة لدينا، الخاصية الأكثر مباشرة وفائدة هي `scroll-margin-top`. تحدد الإزاحة خصيصًا للحافة العلوية للعنصر.
دعنا نعيد صياغة سيناريونا السابق باستخدام هذا الحل الحديث والأنيق. لا مزيد من الهوامش السلبية، لا عناصر زائفة، لا جافا سكريبت.
مثال الكود:
HTML
<header class="site-header">... تنقلاتك ...</header>
<main>
<h2 id="section-one">القسم الأول</h2>
<p>محتوى القسم الأول...</p>
<h2 id="section-two">القسم الثاني</h2>
<p>محتوى القسم الثاني...</p>
</main>
CSS
.site-header {
position: sticky;
top: 0;
height: 80px;
background-color: white;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
/* السطر السحري! */
h2[id] {
scroll-margin-top: 90px; /* ارتفاع الترويسة (80px) + 10px مساحة للتنفس */
}
هذا كل شيء. إنه سطر واحد من كود CSS نظيف، وتصريحي، ويوثق نفسه بنفسه. عندما ينقر المستخدم على رابط إلى `#section-one`، يقوم المتصفح بالتمرير حتى تصل النقطة التي تبعد 90 بكسل *فوق* العنصر `
` إلى قمة إطار العرض. هذا يترك العنوان مرئيًا تمامًا أسفل ترويستك التي يبلغ ارتفاعها 80 بكسل، مع 10 بكسلات مريحة من المساحة الإضافية.
الفوائد واضحة على الفور:
- فصل الاهتمامات (Separation of Concerns): يتم تحديد سلوك التمرير حيث ينتمي - في CSS - دون الاعتماد على جافا سكريبت. لا يتأثر تخطيط العنصر على الإطلاق.
- البساطة والقابلية للقراءة: خاصية `scroll-margin-top` تصف تمامًا ما تفعله. أي مطور يقرأ هذا الكود سيفهم الغرض منه على الفور.
- المتانة: إنها الطريقة الأصلية للمنصة للتعامل مع المشكلة، مما يجعلها أكثر كفاءة وموثوقية من أي حل برمجي.
- قابلية الصيانة: إدارتها أسهل بكثير من الحيل القديمة. يمكننا حتى تحسينها بشكل أكبر باستخدام خصائص CSS المخصصة (Custom Properties)، والتي سنتناولها بعد قليل.
نظرة أعمق على خصائص `scroll-margin`
بينما `scroll-margin-top` هي البطل الأكثر شيوعًا لمشكلة الترويسة اللاصقة، فإن عائلة `scroll-margin` أكثر تنوعًا من ذلك. إنها تعكس خاصية `margin` المألوفة في هيكلها.
الخصائص الطويلة والمختصرة
تمامًا مثل `margin`، يمكنك تعيين الخصائص بشكل فردي أو باستخدام صيغة مختصرة:
scroll-margin-top
scroll-margin-right
scroll-margin-bottom
scroll-margin-left
والخاصية المختصرة `scroll-margin`، التي تتبع نفس صيغة القيم من واحدة إلى أربع قيم مثل `margin`:
CSS
.target-element {
/* top | right | bottom | left */
scroll-margin: 90px 20px 20px 20px;
/* مكافئ لـ: */
scroll-margin-top: 90px;
scroll-margin-right: 20px;
scroll-margin-bottom: 20px;
scroll-margin-left: 20px;
}
هذه الخصائص الأخرى مفيدة بشكل خاص في واجهات التمرير الأكثر تقدمًا، مثل عروض الشرائح التي تستخدم انجذاب التمرير لملء الصفحة (full-page scroll-snapping carousels)، حيث قد ترغب في التأكد من أن العنصر الذي تم التمرير إليه لا يكون ملاصقًا تمامًا لحواف حاويته.
التفكير عالميًا: الخصائص المنطقية (Logical Properties)
لكتابة كود CSS جاهز للعالمية حقًا، من الأفضل استخدام الخصائص المنطقية بدلاً من الخصائص المادية حيثما أمكن ذلك. تعتمد الخصائص المنطقية على تدفق النص (`start` و `end`) بدلاً من الاتجاهات المادية (`top`، `left`، `right`، `bottom`). هذا يضمن أن تخطيطك يتكيف بشكل صحيح مع أوضاع الكتابة المختلفة، مثل اللغات من اليمين إلى اليسار (RTL) مثل العربية أو العبرية، أو حتى أوضاع الكتابة العمودية.
عائلة `scroll-margin` لديها مجموعة كاملة من الخصائص المنطقية:
scroll-margin-block-start
: تقابل `scroll-margin-top` في وضع الكتابة الأفقي القياسي من الأعلى إلى الأسفل.scroll-margin-block-end
: تقابل `scroll-margin-bottom`.scroll-margin-inline-start
: تقابل `scroll-margin-left` في سياق من اليسار إلى اليمين.scroll-margin-inline-end
: تقابل `scroll-margin-right` في سياق من اليسار إلى اليمين.
بالنسبة لمثال الترويسة اللاصقة لدينا، فإن استخدام الخاصية المنطقية هو أكثر قوة ومستقبلية:
CSS
h2[id] {
/* هذه هي الطريقة الحديثة والمفضلة */
scroll-margin-block-start: 90px;
}
هذا التغيير البسيط يجعل سلوك التمرير لديك صحيحًا تلقائيًا، بغض النظر عن لغة المستند واتجاه النص. إنها تفصيلة صغيرة تظهر الالتزام بالبناء لجمهور عالمي.
الدمج مع التمرير السلس لتجربة مستخدم مصقولة
تعمل خاصية `scroll-margin` بشكل جميل جنبًا إلى جنب مع خاصية CSS حديثة أخرى: `scroll-behavior`. من خلال تعيين `scroll-behavior: smooth;` على العنصر الجذر (root)، فإنك تخبر المتصفح بتحريك قفزات الروابط الداخلية بدلاً من الانتقال الفوري إليها.
عندما تجمع بين الاثنين، تحصل على تجربة مستخدم احترافية ومصقولة ببضعة أسطر فقط من CSS:
CSS
html {
scroll-behavior: smooth;
}
.site-header {
position: sticky;
top: 0;
height: 80px;
}
[id] {
/* تطبيقها على أي عنصر له ID لجعله هدف تمرير محتمل */
scroll-margin-top: 90px;
}
مع هذا الإعداد، يؤدي النقر فوق رابط داخلي إلى تشغيل تمرير سلس ينتهي بوضع العنصر المستهدف بشكل مثالي ومرئي أسفل الترويسة اللاصقة. لا حاجة لمكتبة جافا سكريبت.
اعتبارات عملية وحالات خاصة
بينما `scroll-margin` قوية، إليك بعض الاعتبارات الواقعية لجعل تنفيذك أكثر قوة.
إدارة ارتفاعات الترويسة الديناميكية باستخدام خصائص CSS المخصصة (Custom Properties)
إن كتابة قيم بكسل ثابتة مثل `80px` هو مصدر شائع لمشاكل الصيانة. ماذا يحدث إذا تغير ارتفاع الترويسة بأحجام شاشات مختلفة؟ أو إذا تمت إضافة لافتة فوقها؟ ستحتاج إلى تحديث قيمة الارتفاع وقيمة `scroll-margin-top` في أماكن متعددة.
الحل هو استخدام خصائص CSS المخصصة (المتغيرات). من خلال تحديد ارتفاع الترويسة كمتغير، يمكننا الرجوع إليه في كل من نمط الترويسة وهامش تمرير الهدف.
CSS
:root {
--header-height: 80px;
--scroll-padding: 1rem; /* استخدام وحدة نسبية للمسافات */
}
/* ارتفاع ترويسة متجاوب */
@media (max-width: 768px) {
:root {
--header-height: 60px;
}
}
.site-header {
position: sticky;
top: 0;
height: var(--header-height);
}
[id] {
scroll-margin-top: calc(var(--header-height) + var(--scroll-padding));
}
هذا النهج قوي بشكل لا يصدق. الآن، إذا احتجت في أي وقت إلى تغيير ارتفاع الترويسة، فما عليك سوى تحديث متغير `--header-height` في مكان واحد. سيتم تحديث `scroll-margin-top` تلقائيًا، حتى استجابةً لاستعلامات الوسائط (media queries). هذا هو مثال لكتابة كود CSS قابل للصيانة ويتبع مبدأ (لا تكرر نفسك - DRY).
دعم المتصفحات
أفضل خبر عن `scroll-margin` هو أن وقتها قد حان. اعتبارًا من اليوم، هي مدعومة في جميع المتصفحات الحديثة دائمة التحديث، بما في ذلك Chrome و Firefox و Safari و Edge. هذا يعني أنه بالنسبة للغالبية العظمى من المشاريع التي تستهدف جمهورًا عالميًا، يمكنك استخدام هذه الخاصية بثقة.
بالنسبة للمشاريع التي تتطلب دعمًا للمتصفحات القديمة جدًا (مثل Internet Explorer 11)، لن تعمل `scroll-margin`. في مثل هذه الحالات، قد تحتاج إلى استخدام إحدى الحيل القديمة كحل بديل. يمكنك استخدام استعلام CSS `@supports` لتطبيق الخاصية الحديثة للمتصفحات القادرة والحيلة للآخرين:
CSS
/* الحيلة القديمة للمتصفحات القديمة */
[id] {
padding-top: 90px;
margin-top: -90px;
}
/* الخاصية الحديثة للمتصفحات المدعومة */
@supports (scroll-margin-top: 1px) {
[id] {
/* أولاً، قم بإلغاء الحيلة القديمة */
padding-top: 0;
margin-top: 0;
/* ثم، قم بتطبيق الحل الأفضل */
scroll-margin-top: 90px;
}
}
ومع ذلك، نظرًا لتراجع المتصفحات القديمة، غالبًا ما يكون من العملي أكثر البناء باستخدام الخصائص الحديثة أولاً والنظر في الحلول البديلة فقط عندما تتطلبها قيود المشروع بشكل صريح.
مكاسب في إمكانية الوصول
استخدام `scroll-margin` ليس مجرد راحة للمطورين؛ إنه فوز كبير لإمكانية الوصول. عندما يتنقل المستخدمون في الصفحة باستخدام لوحة المفاتيح (على سبيل المثال، عن طريق التنقل بين الروابط باستخدام مفتاح Tab والضغط على Enter على رابط داخلي)، يتم تشغيل تمرير المتصفح. من خلال ضمان عدم حجب العنوان المستهدف، فإنك توفر سياقًا حاسمًا لهؤلاء المستخدمين.
وبالمثل، عندما يقوم مستخدم قارئ الشاشة بتنشيط رابط داخلي، فإن الموقع المرئي للتركيز يتطابق مع ما يتم الإعلان عنه، مما يقلل من الارتباك المحتمل للمستخدمين ذوي الرؤية الجزئية. إنه يدعم مبدأ أن جميع العناصر التفاعلية والإجراءات الناتجة عنها يجب أن تكون واضحة ومدركة لجميع المستخدمين.
الخاتمة: تبنَّى المعيار الحديث
مشكلة إخفاء الروابط الداخلية بواسطة الترويسات اللاصقة هي من بقايا زمن كانت فيه CSS تفتقر إلى الأدوات المحددة لمعالجتها. لقد طورنا حيلًا ذكية بدافع الضرورة، لكن تلك الحلول البديلة جاءت بتكاليف في قابلية الصيانة والتعقيد والأداء.
مع خاصية `scroll-margin`، لدينا الآن مواطن من الدرجة الأولى في لغة CSS مصمم لحل هذه المشكلة بشكل نظيف وفعال. من خلال اعتمادها، فأنت لا تكتب فقط كودًا أفضل؛ بل تبني تجربة أفضل وأكثر قابلية للتنبؤ وأكثر سهولة في الوصول لمستخدميك.
أهم النقاط التي يجب أن تتذكرها:
- استخدم `scroll-margin-top` (أو `scroll-margin-block-start`) على العناصر المستهدفة لإنشاء إزاحة عند التمرير.
- اجمعها مع خصائص CSS المخصصة لإنشاء مصدر وحيد للحقيقة لارتفاع ترويستك اللاصقة، مما يجعل الكود الخاص بك قويًا وقابلًا للصيانة.
- أضف `scroll-behavior: smooth;` إلى عنصر `html` لإضفاء لمسة احترافية ومصقولة.
- توقف عن استخدام حيل الحشو، والعناصر الزائفة، أو جافا سكريبت لهذه المهمة. تبنَّى الحل الحديث والمصمم لهذا الغرض الذي توفره منصة الويب.
في المرة القادمة التي تبني فيها صفحة ذات ترويسة لاصقة وجدول محتويات، لديك الأداة النهائية لهذه المهمة. انطلق وأنشئ تجارب تنقل سلسة وخالية من الإحباط.