الرسوم المتحركة
الرسوم المتحركة هي المفتاح لإحياء المشهد ثلاثي الأبعاد الخاص بك. يمكن أن تكون محفزة بتفاعلات المستخدم، التصفح أو الوقت وتطبق على الكائنات ثلاثية الأبعاد، الأضواء والكاميرات.
إتقان الرسوم المتحركة هو مهارة أساسية لخلق تجارب غامرة. كلما زادت التقنيات التي تعرفها، زاد ما يمكنك التعبير عن إبداعك.
ملاحظة: يتم تغطية الرسوم المتحركة للنموذج ثلاثي الأبعاد في فصل النماذج.
Lerp
Lerp هو وظيفة رياضية تقوم بالمزج بين قيمتين. يعتبر مفيدًا لتحريك قيمة من نقطة إلى أخرى.
const value = THREE.MathUtils.lerp(start, end, t);
المعامل الأول هو قيمة البداية، والمعامل الثاني هو قيمة النهاية والمعامل الثالث هو عامل المزج بين 0 و1.
كلما اقترب عامل المزج من 0، كان التحريك أبطأ. وكلما اقترب عامل المزج من 1، كان التحريك أسرع للوصول إلى قيمة النهاية أو الهدف.
دعونا نجرب ذلك على مكون AnimatedBox
من حزمة البداية.
يحتوي المكون على مصفوفة boxPositions
التي تحتوي على مواقع المكعب في أوقات مختلفة. دعونا نضيف خطاف useFrame
لتحديث الموقع للمكعب بناءً على الوقت:
import { RoundedBox } from "@react-three/drei"; import { useRef } from "react"; export const AnimatedBox = ({ boxPositions, ...props }) => { const box = useRef(); useFrame(({ clock }) => { const seconds = parseInt(clock.getElapsedTime()); const targetPosition = boxPositions[seconds % boxPositions.length]; box.current.position.x = targetPosition.x; box.current.position.y = targetPosition.y; box.current.position.z = targetPosition.z; }); // ... };
هنا لا يتم تحريك المكعب. بل ينتقل بين موقع وآخر. دعونا نستخدم وظيفة lerp
لتحريكه:
import * as THREE from "three"; // ... export const AnimatedBox = ({ boxPositions, ...props }) => { const box = useRef(); useFrame(({ clock }) => { const seconds = parseInt(clock.getElapsedTime()); const targetPosition = boxPositions[seconds % boxPositions.length]; box.current.position.x = THREE.MathUtils.lerp( box.current.position.x, targetPosition.x, 0.05 ); box.current.position.y = THREE.MathUtils.lerp( box.current.position.y, targetPosition.y, 0.05 ); box.current.position.z = THREE.MathUtils.lerp( box.current.position.z, targetPosition.z, 0.05 ); }); // ... };
الآن المكعب محرك. إنه يتحرك بسلاسة من موقع إلى آخر.
تحتوي فئة Vector3
على دالة lerp
والتي يمكن استخدامها بدلاً من وظيفة THREE.MathUtils.lerp
:
// box.current.position.x = THREE.MathUtils.lerp( // box.current.position.x, // targetPosition.x, // 0.05 // ); // box.current.position.y = THREE.MathUtils.lerp( // box.current.position.y, // targetPosition.y, // 0.05 // ); // box.current.position.z = THREE.MathUtils.lerp( // box.current.position.z, // targetPosition.z, // 0.05 // ); box.current.position.lerp(targetPosition, 0.05);
هذا يوفر الكثير من الأسطر للحصول على نفس النتيجة!
لا تتردد في التلاعب بالوقت واستخدام lerp على خصائص أخرى مثل الدوران
أو المقياس
لتجربة تأثيرات مختلفة.
Float
Float هو مكون مغلف من مكتبة Drei لمنح الانطباع ببساطة أن الكائن يطفو.
تحتوي على الخصائص التالية:
speed
: سرعة الحركة، القيمة الافتراضية 1rotationIntensity
: كثافة الدوران XYZ، القيمة الافتراضية 1floatIntensity
: كثافة الطفو لأعلى/لأسفل، يعمل كمعامل مضاعف مع floatingRange، القيمة الافتراضية 1floatingRange
: نطاق قيم المحور y الذي سيطفو داخله الكائن، القيمة الافتراضية [-0.1, 0.1]
لنقم بجعل البطة تطفو في Experience.jsx
:
import { Float, Scroll } from "@react-three/drei"; // ... export const Experience = () => { const viewport = useThree((state) => state.viewport); return ( <> <group position-y={-0.75}> <Float floatIntensity={2} speed={3}> <Duck /> </Float> {/* ... */} </group> {/* ... */} </> ); };
الآن البطة تطفو، تبدو كأنها بالون هيليوم. إنه تأثير بسيط ولكنه يضيف حياة إلى المشهد.
GSAP
GSAP هي مكتبة رسوم متحركة JavaScript، تُستخدم بشكل واسع في صناعة الويب. هي سهلة الاستخدام وقوية.
ليست مخصصة لـ Three.js/React Three Fiber لكنها متوافقة معها.
مع GSAP يمكنك تحريك أي خاصية لأي كائن. يمكنك ربط الرسوم المتحركة، إنشاء جداول زمنية وتشغيلها عند تفاعلات المستخدم أو التمرير.
دعنا ننتقل إلى مكون Background
ونحرك بسلاسة لون الخلفية عند التمرير.
أولاً، نحتاج إلى تثبيت مكتبة GSAP:
yarn add gsap
ثم، دعونا ننشئ مرجعين. واحد لـ GSAP timeline وآخر لتخزين قيمة لون الخلفية:
import { gsap } from "gsap"; // ... export const Background = () => { const skyMaterial = useRef(); const tl = useRef(); const skyData = useRef({ color: "#313131", }); // ... };
ثم دعنا ننشئ useEffect
hook لـ تهيئة الجدول الزمني:
// ... useEffect(() => { tl.current = gsap.timeline(); tl.current .to(skyData.current, { duration: 1, color: "#ffc544", }) .to(skyData.current, { duration: 1, color: "#7c4e9f", }); }, []); // ...
gsap.timeline()
ينشئ جدولًا زمنيًا.
طريقة to
تحرك كائن skyData
. تقوم بتغيير خاصية اللون من #313131
(القيمة الافتراضية) إلى #ffc544
في ثانية واحدة. ثم تغير خاصية اللون من #ffc544
إلى #7c4e9f
في ثانية واحدة.
GSAP يقوم تلقائيًا بـ استنباط قيمة اللون بين اللونين ولكن لا يمكنك استخدام أسماء الألوان بـ CSS مثل red
أو blue
.
أخيرًا، دعنا نحدث لون skyMaterial
:
// ... useFrame(() => { if (!tl.current) { return; } skyMaterial.current.color.set(skyData.current.color); }); // ...
ملاحظة: نحتاج إلى skyData
وندعو طريقة set
على خاصية color
لأن skyMaterial.current.color
هو كائن THREE.Color
. GSAP لا يمكنه تلقائيًا استنباطه. يمكنه فقط استنباط الأرقام والسلاسل.
وهذا يعني أنه يمكنك تحريك position
, rotation
و scale
مباشرة على mesh
أو group
دون الحاجة لإنشاء كائن إضافي مثل الذي قمنا به مع skyData
ولا تحتاج لتعيينها يدويًا في useFrame
hook.
عند إعادة التحميل، لون الخلفية يكون متحركًا بمجرد تحميله.
ما نريده هو أن نشغل الرسوم المتحركة عندما يقوم المستخدم بالتمرير.
نحتاج أولاً إلى إيقاف الجدول الزمني بعد إنشائه:
// ... useEffect(() => { tl.current = gsap.timeline(); tl.current .to(skyData.current, { duration: 1, color: "#ffc544", }) .to(skyData.current, { duration: 1, color: "#7c4e9f", }); tl.current.pause(); }, []); // ...
ثم نحتاج إلى الحصول على بيانات التمرير من useScroll
hook وتعيين تقدم الجدول الزمني للإزاحة التمرير:
// ... const scrollData = useScroll(); useFrame(() => { if (!tl.current) { return; } tl.current.progress(scrollData.offset); skyMaterial.current.color.set(skyData.current.color); }); // ...
الآن لون الخلفية يكون متحركًا بشكل مثالي عندما يقوم المستخدم بالتمرير من قسم إلى آخر.
لاكتشاف جميع الخيارات المختلفة المتوفرة لديك مع GSAP وكيفية استخدامها تحقق من التوثيق.
للمزيد من التعمق يمكنك محاولة تحريك الموضع, التدوير والمقياس لـ البطة.
الرسوم المتحركة المعتمدة على الزنبرك
على العكس من الرسوم المتحركة الخطية التي رأيناها حتى الآن، فإن الرسوم المتحركة المعتمدة على الزنبرك هي مستندة إلى الفيزياء. فهي أكثر واقعية وتعبيرية.
بدلاً من الانتقال من نقطة إلى أخرى بسرعة ثابتة، يتم تسريع وتباطؤ الحركة بناءً على معايير قابلة للتكوين.
العيب الوحيد هو أنها أكثر صعوبة في التحكم حيث لا تعرف مدة الرسوم المتحركة مسبقًا وموقع الجسم في وقت معين.
اثنتان من المكتبات الشهيرة لإنشاء رسوم متحركة معتمدة على الزنبرك هما Framer Motion وReact Spring.
كلتاهما متوافقة مع React Three Fiber وسهلة الاستخدام.
Framer Motion
لفهم كيفية عمل Framer Motion، دعونا نقوم بتحريك الأسنان في القسم الثاني.
أولاً، نحتاج إلى تثبيت مكتبة Framer Motion:
yarn add framer-motion framer-motion-3d
ملاحظة: نظرًا لأننا نريد تحريك كائنات ثلاثية الأبعاد، نحتاج إلى تثبيت الحزمة framer-motion-3d
.
لتحريك كائن ثلاثي الأبعاد باستخدام Framer Motion، نحتاج إلى استخدام المكافئ motion
للمكون من framer-motion-3d
بدلاً من مكونات React Three Fiber:
import { motion } from "framer-motion-3d";
واستبدال مكون mesh
الخاص بنا بـ motion.mesh
:
<motion.mesh position-x={-1} position-y={-1}> {/* ... */} </motion.mesh>
الآن يمكننا استخدام props motion
لتحريك السن:
// ... export const Teeth = () => { return ( <group> <motion.mesh position-x={-1} position-y={-1} animate={{ y: 0, }} transition={{ repeat: Infinity, repeatDelay: 1, }} > <coneGeometry args={[0.5, 1, 4]} /> <meshStandardMaterial color="#ffffff" /> </motion.mesh> <mesh position-x={0} position-y={1} rotation-x={Math.PI}> <coneGeometry args={[0.5, 1, 4]} /> <meshStandardMaterial color="#ffffff" /> </mesh> <mesh position-x={1} position-y={-1}> <coneGeometry args={[0.5, 1, 4]} /> <meshStandardMaterial color="#ffffff" /> </mesh> </group> ); };
يُعد prop animate
كائنًا يحتوي على الخصائص المراد تحريكها. هنا نريد تحريك الموقع y
من -1
إلى 0
.
يُعد prop transition
كائنًا يحتوي على خيارات الرسوم المتحركة. هنا نريد تكرار الرسوم المتحركة بلا نهاية مع تأخير لمدة ثانية واحدة بين كل تكرار.
أصبح الآن السن الأيسر متحركًا.
لإنشاء نظام وتحريك جميع أسناننا، بدلاً من استخدام prop animate
، سنستخدم variants
. مما سيساعدنا على التحكم في الرسوم المتحركة في مكان واحد.
لننشئ حالة لتخزين قيمة النسخة للأسنان:
export const Teeth = () => { const [variant, setVariant] = useState("closed"); useEffect(() => { const interval = setInterval(() => { setVariant((variant) => (variant === "opened" ? "closed" : "opened")); }, 1500); return () => clearInterval(interval); }); // ... };
كل 1.5 ثانية، نقوم بتبديل النسخة بين opened
وclosed
.
ثم نحتاج إلى تغليف مكونات motion.mesh
الخاصة بنا بمكون motion.group
وضبط prop animate
بقيمة النسخة:
// ... return <motion.group animate={variant}>{/* ... */}</motion.group>; // ...
الآن دعونا نستبدل جميع أسناننا بـ motion.mesh
ونحدد variants
:
// ... return ( <motion.group animate={variant}> <motion.mesh position-x={-1} position-y={-1} variants={{ closed: { y: -1, rotateY: 0, }, opened: { scale: 1.1, y: 0, rotateY: Math.PI / 2, }, }} > <coneGeometry args={[0.5, 1, 4]} /> <motion.meshStandardMaterial color="#ffffff" variants={{ closed: { color: "#ffffff", }, opened: { color: "#7564a4", }, }} /> </motion.mesh> <motion.mesh position-x={0} position-y={1} rotation-x={Math.PI} variants={{ closed: { y: 1, rotateY: 0, }, opened: { scale: 1.2, y: 0, rotateY: Math.PI / 2, }, }} > <coneGeometry args={[0.5, 1, 4]} /> <motion.meshStandardMaterial color="#ffffff" variants={{ closed: { color: "#ffffff", }, opened: { color: "#7c5ecf", }, }} /> </motion.mesh> <motion.mesh position-x={1} position-y={-1} variants={{ closed: { y: -1, rotateY: 0, }, opened: { scale: 1.1, y: 0, rotateY: Math.PI / 2, }, }} > <coneGeometry args={[0.5, 1, 4]} /> <motion.meshStandardMaterial color="#ffffff" variants={{ closed: { color: "#ffffff", }, opened: { color: "#6232e6", }, }} /> </motion.mesh> </motion.group> ); // ...
نحن نقوم أيضًا بتحريك scale
وrotation
لـ mesh
وكذلك color
لـ meshStandardMaterial
للحصول على تأثير أفضل.
نستطيع تغيير إعدادات الرسوم المتحركة لجعلها أكثر واقعية.
لتطبيقها على جميع مكونات motion.mesh
الخاصة بنا، يمكننا استخدام مكون MotionConfig
من framer-motion
:
import { MotionConfig } from "framer-motion"; import { motion } from "framer-motion-3d"; import { useEffect, useState } from "react"; export const Teeth = () => { const [variant, setVariant] = useState("closed"); useEffect(() => { const interval = setInterval(() => { setVariant((variant) => (variant === "opened" ? "closed" : "opened")); }, 1500); return () => clearInterval(interval); }); return ( <MotionConfig transition={{ type: "spring", mass: 5, stiffness: 500, damping: 42, }} > <motion.group animate={variant}> <motion.mesh position-x={-1} position-y={-1} variants={{ closed: { y: -1, rotateY: 0, }, opened: { scale: 1.1, y: 0, rotateY: Math.PI / 2, }, }} > <coneGeometry args={[0.5, 1, 4]} /> <motion.meshStandardMaterial color="#ffffff" variants={{ closed: { color: "#ffffff", }, opened: { color: "#7564a4", }, }} /> </motion.mesh> <motion.mesh position-x={0} position-y={1} rotation-x={Math.PI} variants={{ closed: { y: 1, rotateY: 0, }, opened: { scale: 1.2, y: 0, rotateY: Math.PI / 2, }, }} > <coneGeometry args={[0.5, 1, 4]} /> <motion.meshStandardMaterial color="#ffffff" variants={{ closed: { color: "#ffffff", }, opened: { color: "#7c5ecf", }, }} /> </motion.mesh> <motion.mesh position-x={1} position-y={-1} variants={{ closed: { y: -1, rotateY: 0, }, opened: { scale: 1.1, y: 0, rotateY: Math.PI / 2, }, }} > <coneGeometry args={[0.5, 1, 4]} /> <motion.meshStandardMaterial color="#ffffff" variants={{ closed: { color: "#ffffff", }, opened: { color: "#6232e6", }, }} /> </motion.mesh> </motion.group> </MotionConfig> ); };
أصبحت الرسوم المتحركة الآن أكثر واقعية. حيث ترتد قليلًا عندما تصل إلى الأعلى والأسفل.
يمكنك التلاعب بإعدادات الرسوم المتحركة لفهم كيف تؤثر القيم على الرسوم المتحركة.
React Spring
دعونا الأن نقوم بتحريك مكون AnimatedDodecahedron
باستخدام React Spring.
سوف نقوم بضم كل من Framer Motion وReact Spring في مشروع واحد لأغراض العرض. لأنه بما أنهما متشابهين جدًا، فإن أحدهما يجب أن يكون كافيًا لمشاريعك. اختر واحدًا تفضل استخدامه.
أولا، نحن بحاجة إلى تثبيت مكتبة React Spring:
yarn add @react-spring/three
ثم لتعريف حركتنا، نحتاج لاستخدام الـuseSpring
hook:
import { useSpring } from "@react-spring/three"; import { Dodecahedron } from "@react-three/drei"; export const AnimatedDodecahedron = () => { const { x, y, rotationX, rotationZ } = useSpring({ from: { x: -1, y: 1, rotationX: 0, rotationZ: 0, }, to: [ { x: 1, y: 1, delay: 500, }, { x: -1, y: 0, rotationX: Math.PI, rotationZ: Math.PI, delay: 50, }, { x: 1, y: 0, delay: 50, }, { x: -1, y: -1, rotationX: Math.PI * 2, rotationZ: Math.PI * 2, delay: 50, }, { x: 1, y: -1, delay: 50, }, { x: -1, y: 1, rotationX: 0, rotationZ: 0, delay: 500, }, ], config: { mass: 4, tension: 600, friction: 80, }, loop: true, immediate: true, }); // ... };
- الخاصية
from
هي الحالة الأولية للـحركة. - الخاصية
to
هي مصفوفة من الـحالات. كل حالة هي كائن يحتوي على الخصائص التي ستتحرك. خاصية delay هي الفاصل الزمني قبل بدء الـحركة. - الخاصية
config
هي كائن يحتوي على إعدادات الحركة. - الخاصية
loop
هي قيمة منطقية تشير إلى ما إذا كان يجب أن تكون الـحركة متكررة. - الخاصية
immediate
هي قيمة منطقية تشير إلى ما إذا كان يجب أن تبدأ الـحركة فوراً.
سنقوم بتطبيق الـحركة على group
تغلف مكون Dodecahedron
. كما رأينا لـframer motion التي تتطلب استخدام المكون motion.group
, نحن بحاجة إلى استخدام المكون animated
من react-spring/three
:
import { animated, useSpring } from "@react-spring/three"; export const AnimatedDodecahedronAnim = () => { const { x, y, rotationX, rotationZ } = useSpring({ // ... }); return ( <animated.group position-x={x} position-y={y} rotation-x={rotationX} rotation-z={rotationZ} > <Dodecahedron> <meshStandardMaterial color="red" transparent opacity={0.6} /> </Dodecahedron> </animated.group> ); };
إذا قمت بتجربتها، فإن Dodecahedron ربما لن يكون متحركًا وقد تواجه هذه التحذير في وحدة التحكم:
هذا بسبب أننا ثبتنا الإصدار الأحدث من React Spring ولكن مكتبة Drei لديها بالفعل React Spring كـ اعتماد ولكن ليس الإصدار الأحدث.
لحل هذه المشكلة، نحتاج إلى تثبيت نفس إصدار React Spring كما هو مستخدم من قبل Drei.
لعثور على إصدار React Spring المستخدم من قبل Drei، يمكننا الذهاب إلى ملف yarn.lock
والبحث عن @react-spring/three
في قسم الاعتمادات لـ @react-three/drei
:
"@react-three/drei@^9.74.15": version "9.74.15" resolved "https://registry.yarnpkg.com/@react-three/drei/-/drei-9.74.15.tgz#9d33496afe45e4f164852c99671fe5a5bb863457" integrity sha512-w2c7V6SLmEKwImF2c/sB+8SmlkoXwn6CTVwGoWMqvrRbZs4Qo5UPP5VIxQwTjUyA0oeXKlAoLNhSlUXgOJsp7Q== dependencies: "@babel/runtime" "^7.11.2" "@mediapipe/tasks-vision" "0.10.2-rc2" "@react-spring/three" "~9.6.1" "@use-gesture/react" "^10.2.24" camera-controls "^2.4.2" detect-gpu "^5.0.28" glsl-noise "^0.0.0" lodash.clamp "^4.0.3" lodash.omit "^4.5.0" lodash.pick "^4.4.0" maath "^0.6.0" meshline "^3.1.6" react-composer "^5.0.3" react-merge-refs "^1.1.0" stats.js "^0.17.0" suspend-react "^0.1.3" three-mesh-bvh "^0.6.0" three-stdlib "^2.23.9" troika-three-text "^0.47.2" utility-types "^3.10.0" zustand "^3.5.13"
هنا يمكننا رؤية أن الإصدار من React Spring المستخدم في Drei هو ~9.6.1
.
لنقم الآن بتثبيت هذا الإصدار:
yarn add @react-spring/three@~9.6.1
الآن أعد تشغيل خادم التطوير والـحركة يجب أن تعمل.
الـDodecahedron الآن متحرك. إنها تدور وتتحرك من نقطة إلى أخرى مع تأخيرات مختلفة بين كل حركة.
تحقق من وثائق React Spring لاكتشاف جميع الـhooks والـمكونات التي يمكنك استخدامها لتحريك الكائنات ثلاثية الأبعاد الخاصة بك.
الخاتمة
الآن لديك مجموعة من التقنيات المختلفة لتحريك المشاهد ثلاثية الأبعاد الخاصة بك. يمكنك الآن التعبير عن إبداعك وإحياء مشاريعك.
React Three Fiber: The Ultimate Guide to 3D Web Development
✨ You have reached the end of the preview ✨
Go to the next level with Three.js and React Three Fiber!
Get full access to this lesson and the complete course when you enroll:
- 🔓 Full lesson videos with no limits
- 💻 Access to the final source code
- 🎓 Course progress tracking & completion
- 💬 Invite to our private Discord community
One-time payment. Lifetime updates included.