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

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

الـ Transitions والـ Keyframes وتقليل الحركة

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

الدرس في لمحة

أضف transitions وحركات keyframes تبدو مقصودة، وحرّك العناصر عند ظهورها من display: none باستخدام @starting-style، واحترم prefers-reduced-motion دون أن تكسر الواجهة.

  1. 1الـ transitions: بين حالتين
  2. 2الـ keyframes: حركة متعدّدة الخطوات ومتكرّرة
  3. 3حركات الدخول بـ @starting-style
  4. 4احترم تقليل الحركة
  5. 5دورك الآن

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

يطلب موجز التصميم في Waypoint ثلاث لمسات من الحركة: بطاقات الجلسات ترتفع قليلًا عند المرور فوقها، والجلسة الجارية الآن لها نقطة «Live» نابضة، وإشعار منبثق (toast) ينزلق إلى الداخل حين تحفظ جلسة. الثلاث صغيرة. والثلاث يمكن أن تسوء بطرق تجعل الصفحة تبدو رخيصة، أو تُشعر بعض الناس بتعب جسدي حقيقي.

يغطّي هذا الدرس الأدوات (الـ transitions والـ keyframes و@starting-style) والمسؤولية (prefers-reduced-motion).

الـ transitions: بين حالتين

الـ transition (الانتقال) يحرّك خاصية حين تتغيّر قيمتها المحسوبة، لأيّ سبب: مرور المؤشّر، أو تبديل class، أو media query. أنت تختار الخصائص والمدة ومنحنى التسارع (easing):

session-card.css
.session-card {
  transition:
    translate 200ms ease-out,
    box-shadow 200ms ease-out;

  &:hover,
  &:focus-within {
    translate: 0 -2px;
    box-shadow: 0 6px 16px rgb(15 23 42 / 0.12);
  }
}

ثلاثة اختيارات تؤدّي العمل هنا. خصائص محدّدة: ترتفع البطاقة ويكبر ظلّها، ولا يتحرّك شيء آخر. مدة قصيرة: من 150 إلى 250ms تبدو مستجيبة لتغييرات الواجهة الصغيرة؛ وما يزيد على 400ms يبدو متثاقلًا حين يكون المستخدم في انتظاره. التسارع: ease-out تبدأ سريعة ثم تستقرّ، وهذا يناسب ما يستجيب للمستخدم.

وخصائص التحويل المنفردة (translate وscale وrotate) تستحق أن تفضّلها على transform: يمكن تحريك كلٍّ منها وتجاوزه باستقلال، فلا يمسح ارتفاع المرور قيمة scale ضُبطت في مكان آخر.

الـ keyframes: حركة متعدّدة الخطوات ومتكرّرة

حين يكون للحركة أكثر من خطوتين، أو تتكرّر من تلقاء نفسها، تحتاج إلى @keyframes:

CSS
@keyframes pulse {
  0%   { scale: 1;   opacity: 1; }
  70%  { scale: 2.2; opacity: 0; }
  100% { scale: 2.2; opacity: 0; }
}

.live-dot::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: currentColor;
  animation: pulse 1.6s ease-out infinite;
}

هالة النقطة تكبر وتتلاشى، ثم تتوقّف برهة (الثبات من 70% إلى 100%) قبل أن تتكرّر. وتحريك scale وopacity مقصود: هاتان الخاصيتان تستطيعان العمل على الـ compositor دون إعادة تشغيل التخطيط، وهذا ما يشرحه آخر درس في هذه الدورة.

حركات الدخول بـ @starting-style

الإشعار المنبثق للحفظ يكون display: none إلى أن يُظهره class. لا تستطيع الـ transitions أن تعمل عند ظهوره الأول، لأن العنصر المرسوم حديثًا ليس له نمط سابق يبدأ منه. و@starting-style توفّر هذا النمط:

CSS
.toast {
  opacity: 1;
  translate: 0 0;
  transition:
    opacity 200ms ease-out,
    translate 200ms ease-out,
    display 200ms allow-discrete;
}

@starting-style {
  .toast {
    opacity: 0;
    translate: 0 8px;
  }
}

.toast[hidden] {
  display: none;
  opacity: 0;
}

حين تُزال السمة hidden، يبدأ المتصفّح الإشعار من قيم @starting-style وينتقل إلى القيم العادية. وtransition-behavior: allow-discrete (المكتوبة هنا allow-discrete في الاختصار) تتيح لـ display أن تنقلب في اللحظة المناسبة عند الخروج، فيكون التلاشي ظاهرًا قبل أن يختفي العنصر. @starting-style وtransition-behavior ضمن Baseline 2024، لذا تعمل حركة الدخول في كل المتصفّحات الحالية. أما تحريك display نفسها عند الخروج فليس ضمن Baseline بعد (يدعمه Chrome وSafari دون Firefox)، فيختفي الإشعار هناك فورًا بدل أن يتلاشى؛ وفي المتصفّحات الأقدم يظهر ببساطة دون حركة. وكلاهما بديل احتياطي جيد تمامًا.

احترم تقليل الحركة

بعض الناس يصابون بالدوار أو الغثيان أو التشتّت من الحركة على الشاشة، خصوصًا الحركة عبر مسافات طويلة، والـ parallax، والتكبير والتصغير. لأنظمة التشغيل إعداد «تقليل الحركة»، وتقرؤه CSS بـ prefers-reduced-motion.

النمط الأمتن يجعل الحركة اختيارية:

CSS
@media (prefers-reduced-motion: no-preference) {
  .session-card {
    transition: translate 200ms ease-out, box-shadow 200ms ease-out;
  }
  .live-dot::after {
    animation: pulse 1.6s ease-out infinite;
  }
}

المستخدمون الذين لم يطلبوا تقليل الحركة يحصلون على كل شيء. ومن طلبوه يحصلون على بطاقة ثابتة ونقطة ثابتة، وتظل الواجهة تعمل، لأن أيّ معلومة لا تعتمد على الحركة.

وسترى أيضًا شبكة أمان عامة تضبط مدة كل حركة وكل transition على ما يقارب الصفر داخل @media (prefers-reduced-motion: reduce). إنها حاجز احتياطي معقول للأدوات الخارجية (widgets)، لكنها كاستراتيجية وحيدة فظّة: قد تربك السكربتات التي تعتمد على توقيت الحركة (وهي تستخدم مدة صغيرة غير صفرية تحديدًا لأن transition بمدة 0s لا يُطلق transitionend أبدًا)، وتزيل التلاشيات غير المؤذية مع الحركة المؤذية.

دورك الآن

بطاقة التمرين فيها مرور فوري، ونقطة ثابتة، ولا تعامل مع تقليل الحركة. أضف transition للمرور على خصائص محدّدة، ونبضًا متكرّرًا على النقطة، وغلّف الحركة الزخرفية لتعمل فقط عند المستخدمين الذين لم يطلبوا تقليلها. وبعدها ستحرّك الانتقال بين حالات الصفحة كاملة باستخدام الـ view transitions.

أهم النقاط

  • اذكر الخصائص التي تحرّكها بالتحديد (transition: translate 200ms ease-out) بدل all، حتى لا تتحرّك تغييرات الثيم وخصائص التخطيط بالصدفة.
  • استخدم الـ transitions للتغيّر بين حالتين، و@keyframes للحركة متعدّدة الخطوات أو المتكرّرة.
  • @starting-style تحدّد القيم التي ينطلق منها العنصر في الـ transition حين يظهر أول مرة، ما يجعل حركات الدخول من display: none ممكنة في CSS.
  • غلّف الحركة غير الضرورية في @media (prefers-reduced-motion: no-preference)، فيحصل من طلبوا حركة أقل على واجهة هادئة افتراضيًا.

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

تحقّق من فهمك

3 أسئلة

السؤال 1 من 3

لبطاقة transition: all 300ms. حين يتحوّل المستخدم إلى الوضع الداكن، تتلاشى ألوان الصفحة كلها ببطء. ما أنظف إصلاح؟

السؤال 2 من 3

يظهر إشعار منبثق (toast) بالتبديل من display: none إلى display: block، والـ transition الخاص بـ opacity لا يعمل أبدًا. لماذا؟

السؤال 3 من 3

أيّ نهج يخدم على أفضل وجه المستخدمين الذين فعّلوا «تقليل الحركة» في نظام التشغيل؟

تدريب

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

حركة بأدب

أضف الحركة إلى البطاقة، بلطف:

  1. أضف transition لارتفاع المرور: حرّك translate (أو transform) و**box-shadow** فقط، ولا تستخدم all أبدًا، بمدد بين 100ms و400ms.
  2. أنشئ حركة @keyframes تجعل هالة .live-dot::after تنبض إلى الأبد، باستخدام خصائص رخيصة فقط (scale وopacity وtransform وtranslate وrotate).
  3. اجعل الـ transition والنبض يحترمان prefers-reduced-motion (يُنصح بالنمط الاختياري مع no-preference).
<article class="session-card">
  <p class="status"><span class="live-dot" aria-hidden="true"></span> Live now</p>
  <h3>Motion With Manners</h3>
  <p>Transitions, keyframes and reduced motion.</p>
</article>جارٍ تحميل المحرّر…

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

المعاينة

الـ console

(لا مخرجات)

الفحوص

  • المرور يحرّك translate وbox-shadow، لا all
  • مدد الـ transition بين 100ms و400ms
  • هالة نقطة Live تنبض إلى الأبد
  • الـ keyframes لا تحرّك إلا خصائص رخيصة
  • الحركة تحترم prefers-reduced-motion