العناوين الثابتة هي نمط شائع لواجهة المستخدم، ولكن غالبًا ما تحتاج إلى تغيير مظهرها عندما تصبح "ثابتة" للحفاظ على سهولة القراءة أو توفير المساحة. تتطلّب الحلول التقليدية مستمعي التمرير في JavaScript، ما قد يؤدي إلى حدوث مشاكل في الأداء. تتيح لك CSS الحديثة التعامل مع ذلك بشكلٍ أصلي باستخدام طلبات البحث عن حالة الانتقال.
خطوات التنفيذ
1. إنشاء الحاوية الثابتة
أنت بحاجة إلى حاوية تعمل كعنصر ثابت وحاوية لطلب البحث عن حالة الانتقال.
<!-- Wrap in a section to define the containment area for the sticky element -->
<div class="section">
<div class="sticky-container">
<div class="sticky-header">
Section Header
</div>
</div>
<div class="content">
<!-- Content goes here -->
</div>
</div>
2. تطبيق CSS للسلوك الثابت ونوع الحاوية
اضبط السمة container-type: scroll-state على العنصر position: sticky، وليس على العنصر الرئيسي القابل للتمرير. يجب دائمًا تحديد container-name، لأنّ طلبات البحث هذه قد تكون متداخلة وقد تتداخل الحاويات غير المسمّاة. يمكنك أيضًا دمجها مع طلبات البحث عن الحجم، مثل container-type: scroll-state inline-size.
.sticky-container {
position: sticky;
top: 0;
/* MANDATORY: Always specify a container name to avoid collisions if CQs are nested */
container-type: scroll-state;
container-name: section-header;
z-index: 10;
}
.sticky-header {
/* Base styles for the header */
background: #f0f0f0;
padding: 20px;
transition: background-color 0.3s, box-shadow 0.3s;
}
ملاحظة مهمة: يتم الاستعلام عن حالة الانتقال من خلال العناصر الفرعية لحاوية حالة الانتقال. لن تنطبق الأنماط في طلب البحث @container على .sticky-container نفسه، بل على عناصره الثانوية (مثل .sticky-header). لا يمكنك تصميم عنصر الحاوية نفسه باستخدام طلب البحث الخاص بحالة الانتقال.
3- استهداف حالة التوقّف
استخدِم طلب البحث @container scroll-state(...) لتطبيق الأنماط عندما يكون العنوان ثابتًا.
/* Apply styles when the named container is stuck at the top */
@container section-header scroll-state(stuck: top) {
.sticky-header {
background: #0056b3;
color: white;
box-shadow: 0 4px 6px rgb(0 0 0 / 0.15);
}
}
ملاحظة: يمكنك أيضًا استخدام سمات منطقية مثل stuck: inset-block-start أو stuck: inset-inline-start لتوفير دعم أفضل للتدويل (i18n) والتنسيقات من اليمين إلى اليسار (RTL) من خلال الاستعلام عن الحواف المرتبطة بالتدفق بدلاً من الحواف المادية.
ملاحظات حول التغييرات في السمات
إذا لم يتم إيقاف ميزة "تثبيت موضع التمرير"، قد يؤدي تغيير السمات التي تؤثر في التنسيق (الارتفاع، والحشو، وحجم الخط) عند تثبيت العنصر إلى حدوث وميض مرئي. يؤدي تثبيت موضع الانتقال في المحتوى المضمّن أسفل العنصر الثابت إلى تعديل إزاحة الانتقال للتعويض عن تغيير التنسيق، ما يؤدي إلى دفع العنصر مرة أخرى خارج موضعه الثابت ويؤدي إلى حدوث تذبذب.
لتجنُّب ذلك، أوقِف ميزة "تثبيت موضع الانتقال" في العنصر الرئيسي للعنصر الثابت:
.section {
overflow-anchor: none;
}
طبِّق السمة overflow-anchor: none على العنصر الرئيسي المباشر للعنصر position: sticky، وليس على :root أو body، لأنّ ذلك سيؤدي إلى إيقاف ميزة "تثبيت موضع التمرير" للصفحة بأكملها. بعد تنفيذ ذلك، يمكنك تغيير أي سمة في الحالة الثابتة بحرية، بما في ذلك سمات نموذج الصندوق وعمليات التحويل.
لا تعتمد على overflow-anchor: none للعناصر الثابتة في أسفل الشاشة. لا يعوّض تثبيت موضع الانتقال إلا عن متغيّرات التصميم فوق موضع الانتقال الحالي، لذا ليس له أي تأثير في العناصر الثابتة في الأسفل. تجنَّب تغيير سمات نموذج الصندوق في حالة التثبيت للعناوين المثبّتة في الأسفل.
استراتيجيات احتياطية
تُعدّ طلبات البحث عن حالة الانتقال تحسينًا تدريجيًا. في المتصفّحات التي لا تتيح استخدام container-type: scroll-state، سيظل العنوان ثابتًا في الأعلى (بسبب position: sticky)، ولكن لن يتغيّر شكله. هذه هي الطريقة المقترَحة لمعظم حالات الاستخدام.
إذا لم يكن التثبيت ضروريًا، ولكن position: sticky بدون نمط مختلف سيؤدي إلى تجربة أسوأ من عدم التثبيت على الإطلاق، يمكنك تضمين position: sticky والبيان ذي الصلة في طلب بحث @supports أيضًا:
.sticky-container {
@supports (container-type: scroll-state) {
position: sticky;
top: 0;
container-type: scroll-state;
container-name: section-header;
z-index: 10;
}
}
ملاحظة: إذا كان النمط "الثابت" يتطلّب لون خلفية مختلفًا لتسهيل القراءة، ننصحك بإعداد التنسيق بحيث يعمل النمط التلقائي في كل مكان (التحسين التدريجي). إذا كان عليك استخدام عنصر احتياطي، يمكنك حظر سلوك position: sticky نفسه داخل طلب بحث @supports (container-type: scroll-state).
إذا كان التحوّل المرئي ضروريًا للغاية للتصميم (على سبيل المثال، تؤدي الحالة الثابتة إلى ظهور خلفية أو ضغط لا يمكن قراءة المحتوى بدونه)، يمكنك تنفيذ إجراء احتياطي قوي في JavaScript باستخدام IntersectionObserver. يجب تكرار أنماط CSS ضمن فئة .is-stuck. يتحقّق الرمز polyfill العام التالي من getComputedStyle للانتقال إلى الأعلى والعثور على حاوية الانتقال الصحيحة (التي يتم ضبطها تلقائيًا على إطار العرض)، بما يتطابق مع سلوك @container scroll-state(stuck: top):
function getScrollParent(node) {
if (node == null || node === document.body || node === document.documentElement) {
return null; // default to viewport
}
if (node.scrollHeight > node.clientHeight || node.scrollWidth > node.clientWidth) {
const overflow = getComputedStyle(node).overflow;
if (overflow !== 'visible' && overflow !== 'clip') {
return node;
}
}
return getScrollParent(node.parentNode);
}
document.querySelectorAll('.sticky-container').forEach(container => {
const root = getScrollParent(container);
const topOffset = parseFloat(getComputedStyle(container).top) || 0;
const observer = new IntersectionObserver(
([e]) => {
// Toggle the fallback class on the sticky header container
e.target.classList.toggle('is-stuck', e.intersectionRatio < 1);
},
{
root: root,
threshold: [1],
rootMargin: `-${topOffset + 1}px 0px 0px 0px`
}
);
observer.observe(container);
});
ملاحظة: يمكن أيضًا استخدام نمط IntersectionObserver العام هذا كـ polyfill لطلب البحث scroll-state(scrollable).