الـ headless مابيجيبش animation أصلًا
Base UI و Radix و React Aria مابيجيبوش أي animation خالص. كل اللي بيعملوه إنهم يقولولك الـ component في أنهي حالة: بيفتح، مفتوح، بيقفل. وانت تحرّك زي ما انت عايز، بـ CSS أو Motion أو أي حاجة.
المشكلة الحقيقية: الـ exit animation
في React، لو الـ component اتشال من الـ DOM فورًا، مفيش وقت يعمل fade out.
كل مكتبة بتعرّفك الحالة إزاي
| المكتبة | الـ attributes | للـ JS animation (Motion) |
|---|---|---|
| Base UI | data-open / data-closed، و data-starting-style / data-ending-style | keepMounted و render prop |
| Radix | data-state="open" / "closed" | forceMount |
| React Aria | data-entering / data-exiting | render props isEntering / isExiting |
Modal بـ CSS بس (Base UI)
.dialog-popup { transition: opacity var(--motion-duration) var(--motion-ease), transform var(--motion-duration) var(--motion-ease);}/* أول frame وهو داخل + وهو خارج = نفس الشكل المخفي */.dialog-popup[data-starting-style],.dialog-popup[data-ending-style] { opacity: 0; transform: scale(0.96);}
Drawer مع RTL
نفس الفكرة، بس بدل scale بنحرّك من الجنب، ونعكس الاتجاه في العربي:
.drawer-popup[data-starting-style],.drawer-popup[data-ending-style] { transform: translateX(100%);}[dir="rtl"] .drawer-popup[data-starting-style],[dir="rtl"] .drawer-popup[data-ending-style] { transform: translateX(-100%);}
الـ Select والـ dropdowns
الـ popup بياخد data-side (top أو bottom)، و Base UI بتديك --transform-origin، فالـ scale بيطلع من ناحية الـ trigger بالظبط:
.select-popup { transform-origin: var(--transform-origin); transition: opacity 150ms, transform 150ms;}.select-popup[data-starting-style],.select-popup[data-ending-style] { opacity: 0; transform: scale(0.95);}
الـ Toast
Base UI فيها Toast headless كمان، وبتديك CSS variables للـ swipe والـ stacking، وانت تكتب الحركة.
خلّي الـ lib مرنة للي بيستخدمها
١. Motion tokens كـ CSS variables
:root { --motion-duration-fast: 120ms; --motion-duration: 200ms; --motion-ease-out: cubic-bezier(0.22, 1, 0.36, 1);}
أي مشروع يغيّر السرعة من غير ما يلمس الكود.
٢. سيب الـ data attributes ظاهرة
ماتخبيهاش ورا classes بتاعتك، كده المستخدم يعمل override للحركة بـ CSS عادي.
٣. ادعم reduced motion من جوه الـ lib
@media (prefers-reduced-motion: reduce) { :root { --motion-duration: 0ms; --motion-duration-fast: 0ms; }}
٤. الـ default يبقى CSS transitions
أخف ومن غير dependency. ولو حد عايز spring أو gestures (زي سحب الـ drawer)، يستخدم Motion عن طريق render prop. ماتحطش Motion جوه الـ lib نفسها، عشان ماتجبرش كل اللي بيستخدمها ياخدها.
// المستخدم اللي عايز Motion يجيبها بنفسه<Dialog.Popup keepMounted render={(props, state) => ( <motion.div {...props} animate={{ opacity: state.open ? 1 : 0 }} /> )}/>
خلّصت الفصل لما
١) ليه الـ fade out صعب في React من غير مساعدة؟
٢) ليه منحطش Motion جوه الـ lib؟
render prop.٣) إزاي المستخدم يبطّأ كل الحركات في مشروعه؟
--motion-duration في الـ :root بتاعه، من غير ما يلمس كود الـ lib.