أساسيات React.js

الرسوم المتحركة في React

15 دقيقة الدرس 33 من 40

الرسوم المتحركة في React

تضفي الرسوم المتحركة الحياة على تطبيقات React من خلال توفير تعليقات مرئية وتحسين تجربة المستخدم. تعلم كيفية تنفيذ الرسوم المتحركة باستخدام انتقالات CSS ومكتبة Framer Motion القوية.

انتقالات ورسوم CSS المتحركة

ابدأ بانتقالات CSS الأساسية للرسوم المتحركة البسيطة:

import { useState } from 'react'; import './Button.css'; function AnimatedButton() { const [isHovered, setIsHovered] = useState(false); return ( <button className={`animated-button ${isHovered ? 'hovered' : ''}`} onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => setIsHovered(false)} > مرر فوقي </button> ); }

ملف CSS (Button.css):

.animated-button { padding: 12px 24px; background: #007bff; color: white; border: none; border-radius: 8px; cursor: pointer; transition: all 0.3s ease; transform: scale(1); } .animated-button.hovered { background: #0056b3; transform: scale(1.05); box-shadow: 0 4px 12px rgba(0, 123, 255, 0.3); } /* رسم متحرك للظهور التدريجي */ @keyframes fadeIn { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } } .fade-in { animation: fadeIn 0.5s ease-out; }
ملاحظة: انتقالات CSS رائعة للرسوم المتحركة البسيطة. للرسوم المتحركة الأكثر تعقيدًا مع حالات وتسلسلات متعددة، فكر في استخدام مكتبة مثل Framer Motion.

تثبيت وإعداد Framer Motion

Framer Motion هي مكتبة حركة جاهزة للإنتاج لـ React. قم بتثبيتها باستخدام:

npm install framer-motion

مثال استخدام أساسي:

import { motion } from 'framer-motion'; function FadeInBox() { return ( <motion.div initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.5 }} style={{ width: 200, height: 200, background: '#007bff', borderRadius: 8, }} > أنا أظهر تدريجيًا! </motion.div> ); }
نصيحة: يعمل Framer Motion مع أي عنصر HTML أو SVG. ما عليك سوى استبدال علامة العنصر بـ motion.element (مثل motion.div، motion.button، motion.svg).

رسوم التمرير واللمس والتركيز المتحركة

أضف رسومًا متحركة تفاعلية باستخدام خصائص الإيماءات:

import { motion } from 'framer-motion'; function InteractiveCard() { return ( <motion.div className="card" whileHover={{ scale: 1.05, boxShadow: '0 10px 30px rgba(0, 0, 0, 0.2)', }} whileTap={{ scale: 0.95 }} whileFocus={{ outline: '2px solid #007bff' }} transition={{ type: 'spring', stiffness: 300 }} style={{ width: 300, padding: 20, background: 'white', borderRadius: 8, cursor: 'pointer', }} > <h3>بطاقة تفاعلية</h3> <p>مرر أو انقر أو ركز علي!</p> </motion.div> ); } // زر برسوم إيماءات متعددة function AnimatedButton() { return ( <motion.button whileHover={{ scale: 1.1 }} whileTap={{ scale: 0.9 }} transition={{ type: 'spring', stiffness: 400, damping: 17 }} style={{ padding: '12px 24px', background: '#28a745', color: 'white', border: 'none', borderRadius: 8, cursor: 'pointer', fontSize: 16, }} > انقر علي </motion.button> ); }

AnimatePresence لرسوم الخروج المتحركة

قم بتحريك المكونات عند إزالتها من DOM:

import { useState } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; function NotificationList() { const [notifications, setNotifications] = useState([ { id: 1, message: 'نجح! تم حفظ تغييراتك.' }, { id: 2, message: 'تم استلام رسالة جديدة.' }, ]); const removeNotification = (id) => { setNotifications(notifications.filter((n) => n.id !== id)); }; return ( <div style={{ padding: 20 }}> <AnimatePresence> {notifications.map((notification) => ( <motion.div key={notification.id} initial={{ opacity: 0, x: 100 }} animate={{ opacity: 1, x: 0 }} exit={{ opacity: 0, x: -100 }} transition={{ duration: 0.3 }} style={{ padding: 16, marginBottom: 12, background: '#e7f5ff', borderRadius: 8, display: 'flex', justifyContent: 'space-between', alignItems: 'center', }} > <p>{notification.message}</p> <button onClick={() => removeNotification(notification.id)}> × </button> </motion.div> ))} </AnimatePresence> </div> ); }
تحذير: استخدم دائمًا مفاتيح فريدة عند تحريك القوائم باستخدام AnimatePresence. بدون مفاتيح مناسبة، قد لا تعمل رسوم الخروج المتحركة بشكل صحيح.

المتغيرات للرسوم المتحركة المعقدة

استخدم المتغيرات لإنشاء تكوينات رسوم متحركة قابلة لإعادة الاستخدام:

import { motion } from 'framer-motion'; const containerVariants = { hidden: { opacity: 0 }, visible: { opacity: 1, transition: { delayChildren: 0.3, staggerChildren: 0.2, }, }, }; const itemVariants = { hidden: { y: 20, opacity: 0 }, visible: { y: 0, opacity: 1, }, }; function StaggeredList() { const items = ['عنصر 1', 'عنصر 2', 'عنصر 3', 'عنصر 4', 'عنصر 5']; return ( <motion.ul variants={containerVariants} initial="hidden" animate="visible" style={{ listStyle: 'none', padding: 0 }} > {items.map((item, index) => ( <motion.li key={index} variants={itemVariants} style={{ padding: 20, marginBottom: 12, background: '#f8f9fa', borderRadius: 8, }} > {item} </motion.li> ))} </motion.ul> ); }

انتقالات الصفحة

أنشئ انتقالات سلسة بين المسارات:

import { motion, AnimatePresence } from 'framer-motion'; import { Routes, Route, useLocation } from 'react-router-dom'; const pageVariants = { initial: { opacity: 0, x: '-100vw', }, in: { opacity: 1, x: 0, }, out: { opacity: 0, x: '100vw', }, }; const pageTransition = { type: 'tween', ease: 'anticipate', duration: 0.5, }; function Home() { return ( <motion.div initial="initial" animate="in" exit="out" variants={pageVariants} transition={pageTransition} > <h1>الصفحة الرئيسية</h1> <p>مرحبًا بك في الصفحة الرئيسية!</p> </motion.div> ); } function About() { return ( <motion.div initial="initial" animate="in" exit="out" variants={pageVariants} transition={pageTransition} > <h1>صفحة حول</h1> <p>تعرف أكثر عنا!</p> </motion.div> ); } function AnimatedRoutes() { const location = useLocation(); return ( <AnimatePresence mode="wait"> <Routes location={location} key={location.pathname}> <Route path="/" element={<Home />} /> <Route path="/about" element={<About />} /> </Routes> </AnimatePresence> ); }
نصيحة: استخدم mode="wait" في AnimatePresence للتأكد من اكتمال رسم الخروج المتحرك قبل دخول المكون التالي.

رسوم السحب والإفلات المتحركة

اجعل العناصر قابلة للسحب مع الفيزياء المدمجة:

import { motion } from 'framer-motion'; function DraggableBox() { return ( <motion.div drag dragConstraints={{ top: -50, left: -50, right: 50, bottom: 50, }} dragElastic={0.2} whileDrag={{ scale: 1.1, cursor: 'grabbing' }} style={{ width: 150, height: 150, background: '#6c757d', borderRadius: 8, cursor: 'grab', display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'white', }} > اسحبني! </motion.div> ); } // السحب مع العودة التلقائية function SnapBackBox() { return ( <motion.div drag dragSnapToOrigin whileDrag={{ scale: 1.1 }} style={{ width: 150, height: 150, background: '#17a2b8', borderRadius: 8, cursor: 'grab', display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'white', }} > أعود للأصل! </motion.div> ); }

رسوم التخطيط المتحركة

قم بتحريك تغييرات التخطيط تلقائيًا باستخدام خاصية layout:

import { useState } from 'react'; import { motion } from 'framer-motion'; function LayoutAnimation() { const [isExpanded, setIsExpanded] = useState(false); return ( <motion.div layout onClick={() => setIsExpanded(!isExpanded)} style={{ background: '#20c997', borderRadius: 8, padding: 20, cursor: 'pointer', width: isExpanded ? 400 : 200, height: isExpanded ? 300 : 100, }} > <motion.h3 layout>صندوق قابل للتوسيع</motion.h3> {isExpanded && ( <motion.p initial={{ opacity: 0 }} animate={{ opacity: 1 }} transition={{ delay: 0.2 }} > يظهر هذا المحتوى عند التوسيع! </motion.p> )} </motion.div> ); } // رسم شبكة متحرك function AnimatedGrid() { const [items, setItems] = useState([1, 2, 3, 4, 5, 6]); const shuffle = () => { setItems([...items].sort(() => Math.random() - 0.5)); }; return ( <div> <button onClick={shuffle}>خلط</button> <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 16, marginTop: 20, }} > {items.map((item) => ( <motion.div key={item} layout transition={{ type: 'spring', stiffness: 300, damping: 30 }} style={{ padding: 40, background: '#fd7e14', borderRadius: 8, color: 'white', textAlign: 'center', }} > {item} </motion.div> ))} </div> </div> ); }

Hooks الرسوم المتحركة المخصصة

أنشئ hooks رسوم متحركة قابلة لإعادة الاستخدام:

import { useAnimation } from 'framer-motion'; import { useEffect } from 'react'; function useScrollAnimation() { const controls = useAnimation(); useEffect(() => { const handleScroll = () => { const scrollY = window.scrollY; if (scrollY > 100) { controls.start({ opacity: 1, y: 0 }); } else { controls.start({ opacity: 0, y: 50 }); } }; window.addEventListener('scroll', handleScroll); return () => window.removeEventListener('scroll', handleScroll); }, [controls]); return controls; } // استخدام الـ hook المخصص function ScrollReveal() { const controls = useScrollAnimation(); return ( <motion.div animate={controls} initial={{ opacity: 0, y: 50 }} style={{ padding: 40, background: '#e83e8c', borderRadius: 8, color: 'white', marginTop: 200, }} > مرر لأسفل لتكشف عني! </motion.div> ); }

رسوم التحميل والهيكل المتحركة

أنشئ حالات تحميل سلسة:

import { motion } from 'framer-motion'; function SkeletonLoader() { return ( <div> {[1, 2, 3].map((item) => ( <motion.div key={item} animate={{ opacity: [0.5, 1, 0.5], }} transition={{ duration: 1.5, repeat: Infinity, ease: 'easeInOut', }} style={{ height: 100, background: '#e9ecef', borderRadius: 8, marginBottom: 12, }} /> ))} </div> ); } // رسم دائرة تحميل متحرك function Spinner() { return ( <motion.div animate={{ rotate: 360 }} transition={{ duration: 1, repeat: Infinity, ease: 'linear', }} style={{ width: 50, height: 50, border: '4px solid #e9ecef', borderTopColor: '#007bff', borderRadius: '50%', }} /> ); }
تمرين 1: أنشئ مكون نافذة منبثقة مع رسوم دخول وخروج باستخدام Framer Motion. يجب أن تنزلق النافذة من الأسفل عند الفتح وتختفي تدريجيًا عند الإغلاق. أضف خلفية برسم اختفاء تدريجي.
تمرين 2: قم ببناء معرض صور مع تأثيرات التمرير، ورسوم النقر للتوسيع، وانتقالات سلسة بين طريقة العرض الشبكي والملء الكامل. استخدم AnimatePresence للتراكب بملء الشاشة.
تمرين 3: نفذ معالج نموذج متعدد الخطوات مع رسوم انتقال الصفحة. يجب أن تنزلق كل خطوة من اليمين عند التحرك للأمام ومن اليسار عند الرجوع. أضف رسوم مؤشر التقدم المتحركة.