الوحدة 4 · الحركة والمعمارية
الـ Transitions والـ Keyframes وتقليل الحركة
18 دقراءة 4 دبيئة تجريب الكود
الدرس في لمحة
أضف transitions وحركات keyframes تبدو مقصودة، وحرّك العناصر عند ظهورها من display: none باستخدام @starting-style، واحترم prefers-reduced-motion دون أن تكسر الواجهة.
- 1الـ transitions: بين حالتين
- 2الـ keyframes: حركة متعدّدة الخطوات ومتكرّرة
- 3حركات الدخول بـ @starting-style
- 4احترم تقليل الحركة
- 5دورك الآن
لا فيديو هنا، بل الشيء الحقيقي: درس مكتوب وأمثلة قابلة للتشغيل واختبار وتدريب.
يطلب موجز التصميم في Waypoint ثلاث لمسات من الحركة: بطاقات الجلسات ترتفع قليلًا عند المرور فوقها، والجلسة الجارية الآن لها نقطة «Live» نابضة، وإشعار منبثق (toast) ينزلق إلى الداخل حين تحفظ جلسة. الثلاث صغيرة. والثلاث يمكن أن تسوء بطرق تجعل الصفحة تبدو رخيصة، أو تُشعر بعض الناس بتعب جسدي حقيقي.
يغطّي هذا الدرس الأدوات (الـ transitions والـ keyframes و@starting-style) والمسؤولية (prefers-reduced-motion).
الـ transitions: بين حالتين
الـ transition (الانتقال) يحرّك خاصية حين تتغيّر قيمتها المحسوبة، لأيّ سبب: مرور المؤشّر، أو تبديل class، أو media query. أنت تختار الخصائص والمدة ومنحنى التسارع (easing):
.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:
@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 توفّر هذا النمط:
.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.
النمط الأمتن يجعل الحركة اختيارية:
@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)، فيحصل من طلبوا حركة أقل على واجهة هادئة افتراضيًا.