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

الوحدة 4 · الحركة والمعمارية

الحركات المرتبطة بالتمرير

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

الدرس في لمحة

قُد حركات CSS بموضع التمرير باستخدام خطوط زمن scroll() وview()، واختر نطاقات الحركة، وأطلقها كتحسين تدريجي ريثما يكتمل دعم Firefox.

  1. 1نوعان من خطوط الزمن
  2. 2الظهور التدريجي مع النطاقات
  3. 3أطلقها كتحسين
  4. 4دورك الآن

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

جدول Waypoint طويل: يومان، وأربعة مسارات، وستون جلسة. يريد الفريق لمستين صغيرتين: شريط تقدّم رفيع تحت الرأس يبيّن إلى أين وصلت في الجدول، وبطاقات متحدّثين تنساب إلى مجال الرؤية حين تصل إليها بالتمرير. الطريقة القديمة كانت مستمعًا لحدث scroll يقرأ scrollY، ويجري بعض الحسابات، ويكتب الأنماط، ستين مرة في الثانية، على الـ main thread، متنافسًا مع كل ما عداه.

الحركات المرتبطة بالتمرير (scroll-driven animations) تترك هذا العمل لـ CSS. تكتب حركة @keyframes عادية وتستبدل ساعتها: بدل الزمن، يأتي تقدّمها من التمرير.

نوعان من خطوط الزمن

خط زمن تقدّم التمرير، scroll()، يمضي من 0% إلى 100% بينما تُمرَّر حاوية التمرير من الأعلى إلى الأسفل. scroll(root) هي المستند؛ وscroll(nearest)، وهي الافتراضية، أقرب سلف قابل للتمرير.

خط زمن تقدّم الظهور، view()، يتتبّع عنصرًا واحدًا وهو يعبر المنطقة المرئية من حاوية التمرير (الـ scrollport): من لحظة ظهور حافته من الأسفل إلى لحظة مغادرته من الأعلى.

progress.css
@keyframes grow {
  from { scale: 0 1; }
  to   { scale: 1 1; }
}

.progress {
  position: fixed;
  inset: 0 0 auto;
  height: 3px;
  background: var(--track-design);
  transform-origin: 0 50%;
  animation: grow linear both;
  animation-timeline: scroll(root);
}

في أعلى الصفحة يكون حجم الشريط صفرًا؛ وفي أسفلها يمتدّ بالعرض الكامل؛ وفي أيّ موضع بينهما يكون متناسبًا تمامًا. وlinear مهمة هنا: مع ease الافتراضية سيندفع الشريط في المنتصف ويزحف عند الطرفين. وboth تُبقي أول keyframe وآخرها مطبّقين خارج النطاق.

الظهور التدريجي مع النطاقات

لبطاقات المتحدّثين، خط زمن الظهور هو الساعة المناسبة، وanimation-range تختار أيّ جزء من مسار البطاقة يشغّل الحركة:

speakers.css
@keyframes reveal {
  from { opacity: 0; translate: 0 24px; }
  to   { opacity: 1; translate: 0 0; }
}

.speaker {
  animation: reveal linear both;
  animation-timeline: view();
  animation-range: entry 0% cover 30%;
}
مستطيل طويل يمثّل الـ scrollport. تظهر بطاقة في ثلاثة مواضع: تدخل للتوّ عند الحافة السفلية، ثم داخله بالكامل، ثم تغادر عند الحافة العلوية. تحدّد التسميات نطاق entry عند الحافة السفلية، ونطاق contain وهي داخله بالكامل، ونطاق exit عند الحافة العلوية؛ ويمتدّ cover من أول دخول إلى آخر خروج. scrollport بطاقة بطاقة بطاقة entry contain exit cover التمرير إلى الأسفل
خط زمن الظهور يتتبّع العنصر عبر الـ scrollport، والنطاقات المسمّاة تحدّد أجزاء ذلك المسار

النطاقات المسمّاة تصف مسار العنصر: entry وهو يعبر الحافة السفلية، وcontain وهو داخل المنطقة بالكامل (أو يغطّيها بالكامل إن كان أطول منها)، وexit وهو يعبر الحافة العلوية، وcover للرحلة كلها من أول بكسل يدخل إلى آخر بكسل يخرج. وentry 0% cover 30% تعني «ابدأ حين تبدأ البطاقة بالدخول، وانتهِ حين تقطع 30% من رحلتها الكاملة»، فتكون كل بطاقة ظاهرة بالكامل قبل وقت كافٍ من وصول المستخدم إليها.

أطلقها كتحسين

هذه صورة الدعم في أكتوبر 2026: الحركات المرتبطة بالتمرير موجودة في Chromium منذ الإصدار 115 (2023) وفي Safari منذ الإصدار 26 (سبتمبر 2025). ولدى Firefox تطبيق خلف إعداد تجريبي (flag)، وهي من مجالات التركيز في Interop 2026، لكن نسخة Firefox المستقرة لا تدعمها بعد، لذا فالميزة ليست ضمن Baseline.

وهذا يحدّد طريقة كتابتك لها. الـ keyframes الخاصة بالظهور تبدأ من opacity: 0، وفي متصفّح يفهم animation-timeline لا مشكلة في ذلك. أما في متصفّح لا يفهمها، فإن animation: reveal linear both بلا خط زمن حركة زمنية بمدة صفر تنتهي عند آخر keyframe، فتظل البطاقات ظاهرة. لكن لا تعتمد على هذه الدقيقة الخفيّة؛ اجعل النيّة صريحة:

CSS
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .speaker {
      animation: reveal linear both;
      animation-timeline: view();
      animation-range: entry 0% cover 30%;
    }
  }
}

المتصفّحات غير الداعمة ومستخدمو تقليل الحركة يحصلون على الشبكة العادية الظاهرة بالكامل. والمتصفّحات الداعمة تحصل على الظهور التدريجي.

وتستطيع أيضًا تسمية خطوط الزمن (view-timeline-name: --speaker) ليتبعها عنصر آخر، واستخدام timeline-scope لجعل الاسم مرئيًا في مستوى أعلى من الشجرة. الجأ إلى ذلك حين يجب أن يقود موضع تمرير عنصر ما عنصرًا آخر؛ أما للتأثيرات المكتفية بذاتها، فتكفي scroll() وview() بلا أسماء.

دورك الآن

في التمرين أضف شريط تقدّم القراءة والظهور التدريجي للمتحدّثين، بالترتيب الصحيح، ومحميّين بـ @supports. تقرأ الفحوصات خطوط الزمن والنطاقات المحسوبة. وبعدها ستتراجع خطوة عن التأثيرات المنفردة لتنظّم ملف الأنماط كله بحيث يستطيع فريق أن يعمل عليه.

أهم النقاط

  • animation-timeline: scroll() تربط تقدّم الحركة بمقدار ما مُرِّرت حاوية التمرير؛ وview() تربطه بمرور عنصر عبر المنطقة المرئية.
  • animation-range تختار أيّ جزء من خط الزمن يشغّل الحركة، مثل entry 0% cover 30% لظهور تدريجي ينتهي بعد قليل من ظهور العنصر.
  • الاختصار animation يعيد ضبط animation-timeline، لذا أعلن خط الزمن بعد الاختصار.
  • الحركات المرتبطة بالتمرير تعمل في Chromium وSafari 26 لكن ليس في نسخة Firefox المستقرة بعد، لذا غلّفها في @supports وتأكّد من أن المحتوى ظاهر بالكامل دونها.

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

تحقّق من فهمك

3 أسئلة

السؤال 1 من 3

شريط التقدّم لا يتحرّك أبدًا. لماذا؟

CSS
.progress {
  animation-timeline: scroll(root);
  animation: grow linear both;
}
السؤال 2 من 3

يجب أن تظهر بطاقة متحدّث تدريجيًا وهي تدخل الـ viewport، وأن تكون ظاهرة بالكامل حين يظهر 30% منها. أيّ التصريحات تحقّق ذلك؟

السؤال 3 من 3

في 2026، يريد زميلك إخفاء شبكة المتحدّثين حتى تُظهر الحركات المرتبطة بالتمرير كل بطاقة تدريجيًا. ما الخطر الرئيسي؟

تدريب

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

تقدّم وظهور تدريجي مرتبطان بالتمرير

الـ keyframes grow وreveal جاهزة. اربطها بالتمرير:

  1. أصلح شريط التقدّم ليعمل grow على خط زمن تمرير المستند، بتوقيت linear وبنمط تعبئة يُبقي الـ keyframes مطبّقة (both).
  2. اجعل كل .speaker تشغّل reveal على خط زمن ظهورها، من بداية نطاق entry إلى 30% من نطاق cover.
  3. ضع القواعد المرتبطة بالتمرير داخل @supports (animation-timeline: view()) لتعرض المتصفّحات غير الداعمة الصفحة العادية. (ويُنصح بتغليفها أيضًا في prefers-reduced-motion: no-preference.)
<div class="progress" aria-hidden="true"></div>
<h2>Speakers</h2>
<section class="speakers">
  <article class="speaker">Ana Ortiz</article>
  <article class="speaker">Kenji Watanabe</article>
  <article class="speaker">Layla Haddad</article>
  <article class="speaker">Tom Becker</article>
  <article class="speaker">Priya Nair</article>
  <article class="speaker">Marcus Lee</article>
</section>جارٍ تحميل المحرّر…

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

المعاينة

الـ console

(لا مخرجات)

الفحوص

  • حركة الشريط تعمل على خط زمن scroll()
  • الشريط يستخدم توقيتًا linear ويُبقي الـ keyframes مطبّقة
  • كل بطاقة متحدّث تشغّل reveal على خط زمن view()
  • الظهور التدريجي يمتدّ من entry إلى cover 30%
  • القواعد داخل @supports (animation-timeline: …)