CSS Animations — دليل المبتدئين للتحريك الأنيق
متى تستخدم transition ومتى تحتاج keyframes، وأي الخصائص آمنة للأداء، وكيف تحترم تفضيل تقليل الحركة
الحركة الجيدة لا يلاحظها أحد — يشعر الناس فقط بأن الواجهة «مرتّبة». والحركة السيئة تُلاحَظ فوراً: بطء، اهتزاز، أو انتظار. الفرق بينهما قواعد قليلة يمكن تعلّمها في جلسة واحدة.
transition أم animation؟
القاعدة بسيطة: إن كان هناك حدث يشغّل التغيير (تمرير الفأرة، تركيز، ضغط، إضافة صنف)، فهي transition. وإن كانت الحركة تبدأ وحدها أو تتكرر أو تمر بمراحل، فهي animation مع @keyframes.
/* transition — من حالة إلى حالة */
.btn {
background: #4f46e5;
transition: background-color 200ms ease-out;
}
.btn:hover { background: #4338ca; }
/* animation — حركة مستقلة متكررة */
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.loading { animation: pulse 2s ease-in-out infinite; }
الخصائص الآمنة للأداء
هذه أهم نقطة في المقال. المتصفح يرسم الصفحة على مراحل، وبعض الخصائص تجبره على إعادة حساب التخطيط في كل إطار — أي 60 مرة في الثانية.
حرّك هاتين فقط كلما أمكن: transform وopacity. كلتاهما تُنفَّذ على وحدة معالجة الرسوميات بلا إعادة تخطيط.
وتجنّب تحريك: width، height، top، left، margin، padding. هذه تُعيد حساب تخطيط الصفحة في كل إطار.
/* ❌ يعيد التخطيط في كل إطار */
.card:hover { margin-top: -8px; }
/* ✅ ينفَّذ على GPU */
.card { transition: transform 200ms ease-out; }
.card:hover { transform: translateY(-8px); }
التأثير البصري نفسه، والكلفة مختلفة تماماً — وتظهر بوضوح على الأجهزة المتوسطة.
المدة ومنحنى التسارع
مدد تصلح لمعظم الحالات:
- 100–150 مللي ثانية — تغيّر لون أو ظل عند التمرير.
- 200–300 مللي ثانية — ظهور قائمة أو بطاقة. المدى الأكثر استخداماً.
- 300–500 مللي ثانية — انتقال عنصر كبير أو نافذة.
- أكثر من 500 — يبدأ الشعور بالبطء.
أما المنحنى: استخدم ease-out للعناصر الداخلة (تبدأ سريعاً وتستقر بهدوء، فتبدو سريعة الاستجابة)، وease-in للخارجة. تجنّب linear إلا لحركة مستمرة كمؤشر تحميل دوّار — الحركة الطبيعية لا تسير بسرعة ثابتة.
أمثلة عملية بـ Tailwind
<!-- بطاقة ترتفع قليلاً -->
<div class="transition-transform duration-200 ease-out hover:-translate-y-1">…</div>
<!-- زر يتغير لونه -->
<button class="bg-indigo-600 hover:bg-indigo-700 transition-colors duration-150">…</button>
<!-- هيكل تحميل -->
<div class="animate-pulse bg-slate-200 h-4 rounded"></div>
لاحظ transition-colors وtransition-transform بدل transition-all. الأخيرة تراقب كل خاصية قابلة للتحريك وتنتج أحياناً حركات غير مقصودة عند تغيّر شيء آخر.
الظهور عند التمرير
لا تربط الحركة بحدث التمرير مباشرة — استخدم IntersectionObserver الذي يخبرك حين يدخل العنصر الشاشة، ثم أضف صنفاً:
const io = new IntersectionObserver((entries) => {
entries.forEach((e) => {
if (e.isIntersecting) {
e.target.classList.add("is-visible");
io.unobserve(e.target); // مرة واحدة تكفي
}
});
}, { threshold: 0.15 });
document.querySelectorAll(".reveal").forEach((el) => io.observe(el));
.reveal {
opacity: 0;
transform: translateY(16px);
transition: opacity 400ms ease-out, transform 400ms ease-out;
}
.reveal.is-visible { opacity: 1; transform: none; }
انتبه لأمر مهم: إن كان المحتوى المخفي نصاً أساسياً، فتأكد أنه يظهر حتى لو تعطّل JavaScript، وإلا صار جزء من صفحتك غير مقروء لبعض الزوار.
احترم تفضيل تقليل الحركة
بعض المستخدمين يعانون دواراً من الحركة ويفعّلون خياراً في النظام لتقليلها. تجاهل هذا ليس مسألة ذوق:
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
لاحظ أننا لا نلغي الحركة بل نختصرها إلى ما لا يُدرك، فتبقى الحالة النهائية صحيحة ولا ينكسر أي عنصر يعتمد على انتهاء الحركة.
أخطاء شائعة
- تحريك كل شيء. الحركة تلفت الانتباه؛ إن تحرك كل شيء لم يعد شيء ملفتاً.
- حركة تؤخّر الاستجابة. إن كان الزر يستجيب بعد 400 مللي ثانية، فالواجهة بطيئة مهما كانت الحركة أنيقة.
- حركة تنقل المحتوى. عنصر يزحف ويدفع ما تحته يسبب قفزاً في التخطيط. حرّك بـ
transformفلا يتأثر ما حوله.
خلاصة
حرّك transform وopacity، وابقَ بين 150 و300 مللي ثانية، واستخدم ease-out للداخل، واحترم تفضيل تقليل الحركة. هذه القواعد الأربع تغطي الغالبية العظمى مما تحتاجه.



