تعزيز تجربة المستخدم: إضافة حركات تفاعلية وانتقالات صفحات سلسة في Next.js باستخدام Framer Motion

دقائق القراءة: 12

مقدمة: إضفاء الحيوية على تطبيقات الويب الساكنة

في عالم الويب الواسع، غالبًا ما نصادف مواقع وتطبيقات ثابتة تفتقر إلى الديناميكية والتفاعل. ولكن، كون التطبيق ثابتًا لا يعني بالضرورة أن يكون مملًا. كيف يمكننا إذن استخدام مكتبة Framer Motion لإضافة لمسة من الحيوية والتفاعل إلى تطبيقات الويب الخاصة بنا، وتقديم تجربة مستخدم أكثر جاذبية؟

يهدف هذا المقال إلى استكشاف قوة Framer Motion في بناء حركات تفاعلية وانتقالات صفحات سلسة ضمن بيئة Next.js، مما يحول تطبيقاتك من مجرد واجهات ساكنة إلى تجارب مستخدم غنية وديناميكية.

ما هو Framer Motion؟

Framer Motion هي واجهة برمجة تطبيقات (API) متطورة مستمدة مباشرة من Framer API. توفر هذه المكتبة مجموعة واسعة من الحركات الجاهزة وعناصر التحكم بالإيماءات، مما يسهل بشكل كبير إنشاء تأثيرات ديناميكية وجذابة في واجهات المستخدم.

فهم العلاقة بين Framer و Framer Motion

  • Framer: هو في الأساس أداة قوية لتصميم النماذج الأولية لواجهة المستخدم (UI prototyping tool) تتيح للمطورين والمصممين إنشاء واجهات تفاعلية مع حركات معقدة يمكن تسليمها بسهولة إلى فرق العمل.
  • Framer API: هي مكتبة JavaScript تسمح لك بتحقيق نفس المستوى من التفاعل والحركات برمجياً.
  • Framer Motion: تنبع من هذا العمل، ولكنها متاحة بشكل ملائم كحزمة منفصلة يمكننا استخدامها للتحكم في الحركات بشكل مستقل، مما يجعلها خيارًا ممتازًا لإضافة الرسوم المتحركة إلى تطبيقات React و Next.js.

ماذا سنبني في هذا الدليل؟

سنستعرض في هذا الدليل العملي مفاهيم Framer Motion لتطبيق تأثيرات تفاعلية وانتقالات صفحات ديناميكية على تطبيقنا. سنبدأ بحركات بسيطة تحدث عند تحميل الصفحة، ثم نتعلم كيفية تفعيلها عند التمرير (hover)، ونختتم ببناء غلاف (wrapper) يسمح لنا بتنفيذ انتقالات صفحات سلسة في تطبيقات Next.js.

عرض توضيحي لحركات تفاعلية وانتقالات صفحات في تطبيق Next.js باستخدام Framer Motion

قبل البدء: المتطلبات الأساسية

هذا المقال هو الجزء الثاني من سلسلة مقالات تركز على بناء موسوعة "ريك ومورتي" (Rick and Morty wiki). يركز الجزء الأول على جلب البيانات من Rick and Morty API وإنشاء صفحات ديناميكية. على الرغم من أنه يمكنك المتابعة دون المرور بالجزء الأول، إلا أنه قد يكون من المفيد أن يكون لديك نقطة بداية جاهزة. بخلاف ذلك، يمكنك تطبيق معظم هذه المفاهيم مع أي تطبيق React.

الخطوة 0: تثبيت Framer Motion في تطبيق Next.js الخاص بك

نظرًا لأننا سنستخدم Framer Motion لتوفير ميزات الرسوم المتحركة، فإن أول ما يتعين علينا فعله هو تثبيتها! بمجرد تشغيل التطبيق محليًا، يمكنك تثبيتها باستخدام:

yarn add framer-motion
# أو
npm install framer-motion

وبعد هذه الخطوة، يمكنك إعادة تشغيل خادم التطوير الخاص بك وسنكون جاهزين للانطلاق!

نقطة البداية: تطبيق Rick and Morty wiki في Next.js

الخطوة 1: تحريك عنوان الصفحة باستخدام Framer Motion في تطبيق Next.js

للبدء، سنقوم بتحريك عنوان الصفحة في تطبيق الموسوعة الخاص بنا. سنقوم على وجه التحديد بتهيئة Framer Motion لجعل العنوان يظهر تدريجيًا (fade in) وينمو (grow) عند تحميل الصفحة لأول مرة.

استيراد مكتبة Motion

أولاً وقبل كل شيء، نحتاج إلى استيراد motion إلى تطبيقنا. ابدأ بإضافة عبارة الاستيراد التالية إلى أعلى ملف pages/index.js:

import { motion } from 'framer-motion';

تغليف العنصر وتحديد الحركات

الآن بعد أن أصبحنا جاهزين لاستخدام motion، يمكننا البدء بتغليف عنوان <h1> الخاص بنا بمكون motion:

<motion.div>
  <h1 className="title">
    Wubba Lubba Dub Dub!
  </h1>
</motion.div>

تغليف العنصر هو ما سيسمح لنا بالربط مع Motion API. إذا قمنا بإعادة تحميل صفحتنا، فلن يحدث شيء بعد. هذا لأننا لم نقم بتهيئة حركتنا بعد، فلنفعل ذلك!

عند استخدام Motion API مع مكون <motion.x> الخاص بنا، لدينا مفهومين أساسيين نحتاج إلى استخدامهما:

  • دورة حياة الحركة (Animation lifecycle)
  • المتغيرات (Variants)

كل خاصية من خصائص دورة حياة الحركة مثل initial و animate تسمح لنا بتعريف اسم حركتنا كمتغير. خاصية variants هي المكان الذي نقوم فيه بتهيئة هذه الحركات عن طريق تعريف أسماء المتغيرات جنبًا إلى جنب مع الحركة التي نرغب في تنفيذها.

للبدء، دعنا نضيف تعريفين لدورة الحياة إلى مكون العنوان الخاص بنا عن طريق إضافة خاصيتين لدورة الحياة:

<motion.div initial="hidden" animate="visible">
  <h1 className="title">
    Wubba Lubba Dub Dub!
  </h1>
</motion.div>

الآن، نريد تعريف هذه المتغيرات:

<motion.div
  initial="hidden"
  animate="visible"
  variants={{
    hidden: {},
    visible: {},
  }}
>
  <h1 className="title">
    Wubba Lubba Dub Dub!
  </h1>
</motion.div>

نحن هنا نحدد متغيرين – hidden و visible – واللذين نرجع إليهما بعد ذلك في خصائص دورة الحياة initial و animate. مرة أخرى، عند إعادة تحميل الصفحة، لن يحدث شيء لأننا لم نقم بعد بتعريف الحركات نفسها، فلنفعل ذلك:

<motion.div
  initial="hidden"
  animate="visible"
  variants={{
    hidden: { scale: .8, opacity: 0 },
    visible: {
      scale: 1, opacity: 1,
      transition: { delay: .4 }
    },
  }}
>
  <h1 className="title">
    Wubba Lubba Dub Dub!
  </h1>
</motion.div>

هذا ما يحدث هنا:

  • لدينا حالتان مختلفتان لدورة الحياة: initial و animate. الحالة initial هي ما يتم تحميله "مبدئيًا" عند تحميل الصفحة، بينما animate هي ما يحدث بعد تحميل الصفحة.
  • في حالتنا الأولية (initial)، نقوم بتعيين العنصر ليكون مصغرًا قليلاً (scale: .8) مع شفافية صفرية (opacity: 0).
  • عند تحميل الصفحة وتفعيل حركتنا، نعيد المقياس إلى 1 والشفافية إلى 1.
  • نقوم أيضًا بتعيين تأخير (delay) على انتقالنا بحيث ينتظر 0.4 ثانية قبل تفعيل الحركة. هذا للمساعدة في السماح بتحميل الأشياء قليلاً قبل التفعيل.

إذن، في المثال أعلاه، ما يحدث فعليًا هو أنه بعد 0.4 ثانية من تحميل الصفحة، سيظهر العنوان تدريجيًا ويبدو وكأنه ينمو قليلاً. وإذا حفظنا ذلك وأعدنا تحميل الصفحة، يمكننا رؤية تأثير عنواننا!

عنوان متحرك في تطبيق Next.js باستخدام Framer Motion

الخطوة 2: إضافة تأثيرات التمرير المتحركة (Hover Effects) إلى العناصر في تطبيق Next.js

الآن بعد أن أصبح لدينا فهم أساسي لكيفية إضافة الحركات عند تحميل الصفحة، دعنا نبدأ بإضافة بعض التفاعل. سنضيف تأثيرات التمرير (hover effects) إلى كل بطاقة شخصية. بهذه الطريقة، عندما يتحرك مؤشرك فوق إحدى البطاقات، سنقوم بتفعيل حركتنا.

تحويل عنصر القائمة إلى عنصر Motion

أولاً، داخل شبكة القائمة غير المرتبة <ul className="grid">، دعنا نحدث عنصر القائمة <li> ليصبح عنصر <motion.li>:

<motion.li key={id} className="card">
  ...
</motion.li>

إذا قمت بالحفظ وإعادة تحميل الصفحة، ستلاحظ أن لدينا مشكلة بالفعل.

التطبيق مع أنماط مفقودة لبطاقات الشخصيات

حل مشكلة الأنماط (CSS)

بسبب التكامل بين motion وتكامل Next.js CSS، يواجه تطبيقنا مشكلة في اسم الفئة (class name). بينما لا يحل هذا المشكلة من "جوهرها"، يمكننا إصلاح ذلك لعرضنا التوضيحي عن طريق إزالة الخاصية jsx من كتلة <style> العلوية حيث يوجد تعريف .card الخاص بنا. قم بتغيير:

<style jsx>{`

إلى:

<style>{`

الآن إذا أعدنا تحميل صفحتنا، سنعود إلى حيث بدأنا.

التطبيق بأنماط صحيحة

تطبيق تأثير التمرير (Hover Effect)

لإضافة تأثير التمرير الخاص بنا، سنقوم بإنشاء خاصية جديدة على مكون <motion.li> الخاص بنا تسمى whileHover ونملأها ببعض الأنماط الأساسية:

<motion.li
  key={id}
  className="card"
  whileHover={{
    scale: 1.2,
    transition: { duration: .2 }
  }}
>

في الكود أعلاه، نخبر motion أنه عندما يمرر شخص ما مؤشر الفأرة فوق عنصرنا، نريد أن ينمو عن طريق تكبيره إلى 1.2 مرة، ونريد أن تستمر هذه الحركة 0.2 ثانية. وإذا أعدنا تحميل الصفحة، يمكننا رؤية حركتنا!

تأثير التمرير في تطبيق Next.js باستخدام Framer Motion

تحسين تأثير التمرير: معالجة التداخل

إذا نظرت إلى الجزء السفلي من التأثير، فعندما تنمو البطاقة، فإنها تتداخل مع البطاقة الموجودة أسفلها وتبدو وكأنها معطلة بعض الشيء. يمكننا إصلاح ذلك عن طريق تطبيق بعض خاصيات z-indexing ولون خلفية!

<motion.li
  key={id}
  className="card"
  whileHover={{
    position: 'relative',
    zIndex: 1,
    background: 'white',
    scale: 1.2,
    transition: { duration: .2 }
  }}
>

وإذا أعدنا تحميل الصفحة مرة أخرى، يمكننا الآن أن نرى أنه عندما تنمو بطاقتنا، فإنها تظهر فوق البطاقة الموجودة أسفلها!

إصلاح تداخل Z-indexing والخلفية على تأثير التمرير

الخطوة 3: إضافة انتقالات الصفحات باستخدام Framer Motion إلى تطبيق Next.js

تفعيل بعض الحركات عند تحميل الصفحة وتأثيرات التمرير أمر رائع، ولكن اللمسة الجميلة هي عندما يوفر تطبيقك انتقالات سلسة بين الصفحات. يمكن أن يكون هذا جزءًا مما يجعله يبدو وكأنه "تطبيق ويب" بدلاً من "موقع ويب" ثابت.

للقيام بذلك، سنحتاج إلى تهيئة تطبيق Next.js الخاص بنا بغلاف (wrapper) حول الصفحات الجذرية للموقع. سيسمح لنا هذا بالربط بدورة حياة التنقل (navigation lifecycle) وتحريك صفحاتنا بشكل صحيح.

إنشاء ملف _app.js

للبدء، نحتاج إلى إنشاء ملف جديد ضمن دليل pages يسمى _app.js:

// في pages/_app.js
function App({ Component, pageProps }) {
  return (
    <Component {...pageProps} />
  )
}

export default App;

بينما لا نحتاج بالضرورة إلى فهم تفاصيل ما يحدث، فإننا نقوم بشكل أساسي بإنشاء غلاف حيث يمكننا إضافة وظائف إضافية. مع هذا الملف الجديد، إذا قمت بإعادة تحميل الصفحة، فلن ترى أي تغييرات بعد.

تطبيق انتقالات الصفحات

بعد ذلك، سنضيف الأساس الذي يسمح لنا بإعداد انتقالات الصفحات. أولاً، دعنا نستورد motion في أعلى صفحتنا:

import { motion } from 'framer-motion';

ثم، على غرار حركاتنا الأخرى، دعنا ننشئ مكون <motion.div> جديدًا يغلف صفحتنا.

<motion.div
  initial="pageInitial"
  animate="pageAnimate"
  variants={{
    pageInitial: { opacity: 0 },
    pageAnimate: { opacity: 1 },
  }}
>
  <Component {...pageProps} />
</motion.div>

هنا، نقوم بتعيين حالة أولية (initial state) بشفافية 0 (opacity: 0) وحالة متحركة (animate state) بشفافية 1 (opacity: 1)، مع فكرة أنها ستظهر تدريجيًا. الآن إذا قمت بتحديث الصفحة، ستلاحظ أن الصفحة تظهر تدريجيًا. ولكن إذا نقرت على إحدى الشخصيات، فعند تحميل صفحة الشخصية، فإنها لا تظهر تدريجيًا.

تغيير الصفحة بدون تأثير انتقال

تفعيل الانتقال عند تغيير المسار (Route)

المشكلة هي أن مكون motion حاليًا لا يفهم أنه مسار جديد، لذلك نحتاج إلى إجباره على التعرف على ذلك والتحديث. للقيام بذلك، سنقوم بتفكيك وسيط router في خصائص App الخاصة بنا:

function App({ Component, pageProps, router }) {

وعلى مكون motion الخاص بنا، سنستخدمه كمفتاح (key):

<motion.div key={router.route} initial="pageInitial" animate="pageAnimate" variants={{

الآن إذا أعدنا تحميل الصفحة وتنقلنا بين صفحة الشخصية وصفحتنا الرئيسية، سترى أنه يقوم الآن بإظهار المحتوى تدريجيًا!

انتقال صفحات تدريجي في Next.js باستخدام Framer Motion

الخطوة 4: استخدام Keyframes في Framer Motion للحركات المتقدمة

الآن بعد أن أتقنا أساسيات إعداد الحركات في دورة حياة تطبيقنا، يمكننا أن نصبح أكثر تقدمًا باستخدام keyframes. طريقة عمل keyframes هي أنه عندما نحدد حركة، يمكننا تعيين هذه الحركة لتنتقل عبر مجموعة من القيم المختلفة لخاصيتنا المعطاة، مما يسمح لنا ببناء حركات مخصصة حسب رغبتنا.

على سبيل المثال، لنفترض أننا نريد أن نجعل عنصرًا عند التمرير ينمو إلى ضعف حجمه، ثم يصبح أصغر قليلاً إلى 1.5 ضعف حجمه، ثم يعود إلى ضعف حجمه مرة أخرى، كل ذلك في تسلسل حركة واحد. يمكننا القيام بذلك باستخدام keyframes!

الصيغة لذلك ستبدو كالتالي:

scale: [ 1 , 2 , 1.5 , 2 ]

نحن نحدد تسلسلنا باستخدام مصفوفة تقول إننا نريد أن يبدأ عنصرنا بحجمه الطبيعي عند 1x، ثم في الإطار التالي سينمو إلى 2x، ثم يتقلص قليلاً إلى 1.5x، ثم ينمو أخيرًا مرة أخرى إلى 2x.

تطبيق Keyframes على تأثير التمرير

لاختبار ذلك، سنقوم بإجراء بعض التغييرات على تأثير التمرير الذي قمنا بتهيئته بالفعل لبطاقات الشخصيات الخاصة بنا. في ملف pages/index.js، قم بتحديث خاصية whileHover على عناصر قائمة motion لدينا إلى:

<motion.li
  key={id}
  className="card"
  whileHover={{
    position: 'relative',
    zIndex: 1,
    background: 'white',
    scale: [1, 1.4, 1.2],
    rotate: [0, 10, -10, 0],
    transition: { duration: .2 }
  }}
>

نحن نحدد مجموعتين من keyframes، وهما كالتالي:

  • حجمه الأولي هو 1x ولديه دوران 0 (أو لا دوران).
  • ثم يتدرج إلى 1.4x الحجم ويدور 10 درجات.
  • ثم يتدرج مرة أخرى إلى 1.2x الحجم ويدور في الاتجاه الآخر إلى -10 درجات.
  • في هذه النقطة، تكتمل keyframes المقياس (scale)، لذلك لن نقوم بتغيير المقياس بعد الآن، ولكن لدينا دوران أخير واحد، حيث نعود إلى وضعنا الأولي 0 (أو لا دوران).

وإذا أعدنا تحميل الصفحة ومررنا فوق عناصرنا، يمكننا رؤية تأثيراتنا في العمل!

تأثير التمرير في Framer Motion مع الدوران والتكبير

بدون keyframes، يمكننا فقط تنفيذ تأثيرات الحركة من حالة أولية واحدة إلى قيمة واحدة أخرى. ولكن مع keyframes، يمكننا إضافة حركات أكثر ديناميكية تتنقل بين قيم مختلفة.

خطوة إضافية: إضافة تأثيرات غريبة وممتعة لتطبيق Rick and Morty

لإضافة طبقة أخرى من المرح إلى هذا، يمكننا اللعب بخصائص أخرى تجعل حركاتنا أكثر ديناميكية. للبدء، سنرفع تأثيرات التمرير لدينا إلى مستوى أعلى.

تغيير الألوان والتباين عند التمرير

في ملف pages/index.js داخل خاصية whileHover لعنصر بطاقة <motion.li>، دعنا نضيف الخاصية التالية:

filter: [ 'hue-rotate(0)' , 'hue-rotate(360deg)' , 'hue-rotate(45deg)' , 'hue-rotate(0)' ],

هنا، نقوم بإعداد مجموعة جديدة من keyframes التي ستقوم "بتدوير" درجة اللون (hue) للصورة بناءً على دالة CSS hue-rotate. وإذا قمنا بالحفظ وإعادة تحميل صفحتنا، فإن هذا يمنحنا تأثيرًا لونيًا لطيفًا.

تغيير لون الصورة عند التمرير باستخدام Framer Motion وفلاتر CSS

ولكن هذا خفي جدًا بالنسبة لي – أريده أن يكون أكثر غرابة. دعنا نحدث خاصية filter الخاصة بنا إلى ما يلي:

filter: [ 'hue-rotate(0) contrast(100%)' , 'hue-rotate(360deg) contrast(200%)' , 'hue-rotate(45deg) contrast(300%)' , 'hue-rotate(0) contrast(100%)' ],

الآن، لا يتغير اللون فحسب، بل نستخدم دالة CSS contrast لجعل الألوان أكثر حدة، مما يمنحنا تأثيرًا أكثر غرابة.

تغيير التباين والألوان باستخدام فلاتر CSS عند التمرير مع Framer Motion

تأثيرات الخروج (Exit Animations) لانتقالات الصفحات

بعد ذلك، يمكننا فعل شيء مشابه قليلاً مع انتقالات صفحاتنا! للقيام بذلك، سنستخدم جزءًا جديدًا من دورة حياة مكون Motion – وهو exit (الخروج). للقيام بذلك، نحتاج إلى استخدام مكون AnimatePresence من Motion الذي يسمح لنا بتحريك المكونات عند إزالتها من شجرة React.

للبدء، دعنا نفتح ملف pages/_app.js ونستورد هذا المكون في الأعلى:

import { motion, AnimatePresence } from 'framer-motion';

بعد ذلك، نريد تغليف مكون <motion.div> الخاص بنا بمكون AnimatePresence الجديد:

<AnimatePresence>
  <motion.div key={router.route} initial="pageInitial" animate="pageAnimate" variants={{

مع تغليف مكوننا، يمكننا الآن تعيين خاصية دورة الحياة الجديدة exit جنبًا إلى جنب مع متغيرها:

<motion.div
  key={router.route}
  initial="pageInitial"
  animate="pageAnimate"
  exit="pageExit"
  variants={{
    pageInitial: { opacity: 0 },
    pageAnimate: { opacity: 1 },
    pageExit: { backgroundColor: 'white', filter: `invert()`, opacity: 0 }
  }}
>

في الكود أعلاه، نقوم بما يلي:

  • تهيئة متغير pageExit.
  • تعيين خاصية دورة الحياة exit إلى pageExit.
  • داخل متغير pageExit، نقوم بتعيين لون الخلفية إلى الأبيض ونضيف فلترًا لعكس الألوان (invert()).

ملاحظة: من المهم تعيين لون الخلفية إلى الأبيض، وإلا فلن يتم تطبيق فلتر العكس على الخلفية.

وإذا قمنا بالحفظ وإعادة تحميل الصفحة، فعندما نتنقل إلى عنصر آخر، نحصل على تأثيرنا!

عكس الألوان عند انتقال الصفحة باستخدام Framer Motion

ماذا يمكننا أن نفعل أيضًا؟

Framer Motion مكتبة غنية بالخيارات، وإليك بعض الأفكار الإضافية التي يمكنك استكشافها:

  • إضافة حركات لتأخير نتائج البحث: يمكنك إضافة ميزة تجعل النتائج تظهر بشكل متتابع عن طريق تحريكها قليلاً إلى الأعلى. يمكن تحقيق ذلك باستخدام خاصية الانتقال staggerChildren وتعيين خصائص الموضع x و y على عناصر القائمة.
  • تحريك الأزرار: حاليًا، الأزرار ثابتة. أضف بعض تأثيرات التمرير (hover) والنقر (click) إلى الأزرار مثل زر "تحميل المزيد" (Load More) في الأسفل.
  • إضافة المزيد من التأثيرات الغريبة: هذا تطبيق "ريك ومورتي" في النهاية، اجعله غريبًا قدر الإمكان! ولكن تأكد من أنه لا يزال قابلاً للاستخدام.

الخلاصة التقنية

تُعد Framer Motion أداة لا غنى عنها للمطورين الذين يسعون لرفع مستوى تجربة المستخدم في تطبيقات Next.js و React. بفضل واجهتها البديهية وميزاتها القوية، يمكن للمطورين بسهولة دمج حركات معقدة وانتقالات صفحات سلسة، مما يضفي طابعًا ديناميكيًا وتفاعليًا على تطبيقات الويب. إن القدرة على استخدام variants و keyframes، بالإضافة إلى دعمها لتأثيرات الخروج (exit animations) عبر AnimatePresence، يجعلها حلاً شاملاً لإنشاء واجهات مستخدم جذابة وممتعة. لا تقتصر فائدتها على الجماليات فحسب، بل تسهم بشكل مباشر في تحسين انطباع المستخدم وتفاعله مع التطبيق، مما يعزز من قيمة المنتج النهائي.

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *