المشكلة التي تحلّها
بنيت قائمة عناصر وأردت أن تظهر كل واحدة بعد التي قبلها بفارق زمني بسيط — حركة متدرّجة (Staggered) تعطي إحساسًا احترافيًا بدل ظهور الكل دفعة واحدة. المشكلة أن CSS لم يكن يعرف "أنا العنصر الثالث من بين سبعة" — فكان الحل التقليدي أحد أمرين:
- جافاسكربت يحسب فهرس كل عنصر بنفسه ويكتب
animation-delayكخاصية مضمّنة (inline style) لكل واحد. - سلسلة
:nth-child()يدوية تكرّر نفس القاعدة بقيمة تأخير مختلفة لكل رقم — تعمل، لكنها تنكسر أول ما يتغيّر عدد العناصر، وتحتاج تعديلًا يدويًا في كل مرة.
sibling-index() وsibling-count() تحلّان المشكلة من داخل CSS نفسه: تعطيك ترتيب العنصر بين إخوته وعددهم الكلي كقيمتين حيّتين تستخدمهما في أي حساب calc().
الصياغة
li {
animation: fade-in 0.4s ease both;
animation-delay: calc(sibling-index() * 100ms);
}
sibling-index(): تُرجع رقم ترتيب العنصر الحالي بين إخوته المباشرين (أبناء نفس الأب)، بادئة من 1 — تمامًا كـ:nth-child()، لا من صفر.sibling-count(): تُرجع العدد الكلي لإخوة العنصر (بما فيه هو نفسه) — أي عدد أبناء الأب مجتمعين.
كلتاهما بلا وسائط (parentheses فارغة)، وتُعاد حساب قيمتهما تلقائيًا كلما تغيّر عدد العناصر — أضف عنصرًا سابعًا للقائمة، وكل شيء يُعاد ترقيمه وحده بلا لمس أي سطر CSS.
مثال: قائمة تظهر عناصرها بالتتابع
@keyframes fade-up {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
.list li {
animation: fade-up 0.35s ease both;
animation-delay: calc((sibling-index() - 1) * 80ms);
}
هنا طرحنا 1 من sibling-index() ليبدأ أول عنصر بتأخير صفري (بدل 80ms) — نفس منطق التحويل من ترقيم يبدأ من 1 إلى تأخير يبدأ من 0 الذي كنت تكتبه يدويًا سابقًا لكل عنصر على حدة.
دمج الدالتين: تدرّج بالاتجاه المعاكس
sibling-count() تفتح إمكانية حسابات أعقد — كعكس اتجاه التدرّج فيبدأ آخر عنصر أولًا:
.list li {
animation-delay: calc((sibling-count() - sibling-index()) * 80ms);
}
العنصر الأخير (الذي sibling-index() له يساوي sibling-count()) يحصل على تأخير صفري ويظهر أولًا، بينما الأول يحصل على أطول تأخير — بلا أي معرفة مسبقة بعدد العناصر وقت كتابة القاعدة.
لماذا هذا أفضل من سلسلة :nth-child()
/* الأسلوب التقليدي — يحتاج سطرًا لكل رقم، وينكسر بعد آخر رقم مكتوب */
li:nth-child(1) { animation-delay: 0ms; }
li:nth-child(2) { animation-delay: 100ms; }
li:nth-child(3) { animation-delay: 200ms; }
/* ... ماذا لو أصبحت القائمة 12 عنصرًا؟ */
مقابل سطر واحد ثابت لا يهتم بعدد العناصر:
li { animation-delay: calc((sibling-index() - 1) * 100ms); }
سلسلة :nth-child() | sibling-index() | |
|---|---|---|
| يحتاج معرفة العدد مسبقًا | نعم — سطر لكل رقم | لا |
| يتكيّف مع عدد متغيّر | لا، ينكسر بصمت | نعم تلقائيًا |
| صيغة الحساب | متكررة لكل رقم | معادلة واحدة عامة |
دعم المتصفحات لا يزال جزئيًا
هذه دوال حديثة جدًا: مدعومة في Chrome وEdge (بدءًا من الإصدار 138) وSafari (بدءًا من 26.2)، لكن Firefox لم يدعمها بعد حتى وقت كتابة هذا الدرس — لذا لا تُحسب بعد ضمن "Baseline" المتاحة على كل المتصفحات دفعة واحدة.
🔑 لمشروع يستهدف Firefox أيضًا، لفّ استخدامها بـ
@supports (animation-delay: sibling-index())ووفّر تأخيرًا ثابتًا أو بلا تأخير كبديل احتياطي — بدل الاعتماد عليها وحدها.
li { animation-delay: 0ms; } /* بديل احتياطي لكل المتصفحات */
@supports (animation-delay: sibling-index()) {
li { animation-delay: calc((sibling-index() - 1) * 80ms); }
}
الأخطاء الشائعة
- ❌ توقّع أن
sibling-index()تبدأ من صفر → تبدأ من 1 مثل:nth-child()؛ اطرح 1 إن أردت تأخيرًا صفريًا للعنصر الأول. - ❌ استخدامها بلا بديل احتياطي في مشروع يدعم Firefox → التأخيرات كلها تُهمَل بصمت (القيمة غير المفهومة تُتجاهل) فتظهر كل العناصر دفعة واحدة بلا حركة متدرّجة.
- ❌ الخلط بينها وبين
:nth-child()→:nth-child()محدّد يُطابق عنصرًا بعينه، بينماsibling-index()/sibling-count()دالتا قيمة تُستخدمان داخل أي خاصية تقبل رقمًا عبرcalc()، ليس فقط للتحديد.
خلاصة
sibling-index() وsibling-count() يعطيان CSS وعيًا بترتيب العنصر بين إخوته وعددهم الكلي، فتحوّلان حركات القوائم المتدرّجة من كود جافاسكربت أو سلاسل :nth-child() هشّة إلى معادلة calc() واحدة تتكيّف تلقائيًا مع أي عدد عناصر. لكن ضع لها بديلًا احتياطيًا عبر @supports حتى تصل لدعم Firefox.