أساسيات React.js
الرسوم المتحركة في React
الرسوم المتحركة في 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: نفذ معالج نموذج متعدد الخطوات مع رسوم انتقال الصفحة. يجب أن تنزلق كل خطوة من اليمين عند التحرك للأمام ومن اليسار عند الرجوع. أضف رسوم مؤشر التقدم المتحركة.