كيف تعمل الرسوم المتحركة في React Native: دليل معمق للمطورين
مقدمة إلى عالم الرسوم المتحركة في React Native
يُعد React Native إطار عمل استثنائيًا يُمكّن المطورين من بناء تطبيقات جوال متعددة المنصات بكفاءة عالية. إنه مفيد بشكل خاص لمطوري الويب الذين يسعون إلى حل سريع ومنخفض التكلفة لتطوير تطبيقات جوال أصلية تعمل بسلاسة على كل من Android و iOS. الميزة الإضافية تكمن في توفير التكاليف الباهظة لفرق تطوير منفصلة لكل من iOS و Android والويب، مما يقلل من النفقات ويسمح بمشاركة جزء كبير من قاعدة التعليمات البرمجية بين جميع الإصدارات الثلاثة (Android و iOS والويب)، مما يسهل إجراء التغييرات وإطلاق التحديثات.
يسمح React Native أيضًا بالعمل على مقربة من نظام التشغيل الفعلي، وهو أمر بالغ الأهمية للمهام التي تتطلب أداءً عاليًا مثل الرسوم المتحركة. تُشكل الرسوم المتحركة جزءًا لا يتجزأ من أي تطبيق، فهي تجعله تفاعليًا وأكثر جاذبية للمستخدم النهائي. ولكن في كثير من الأحيان، قد تشعر وكأنك تتعامل مع صندوق أسود عند العمل مع الرسوم المتحركة. لذا، دعونا نتعمق في فهم كيفية عمل الرسوم المتحركة في React Native.
فهم آليات عمل الرسوم المتحركة في React Native
يُقدم React Native واجهة برمجية تُعرف بـ Animated API. تتضمن هذه الواجهة العديد من المكونات الرائعة مثل القيم القابلة للتحريك، والرسوم المتحركة من نوع spring و timing، والأحداث. ومع ذلك، لن نناقش تفاصيل هذه الواجهة البرمجية هنا، بل سنركز على الجانب الأكثر أهمية: كيف يقوم React Native بتحريك العناصر على الشاشة وما الذي يحدث تحت الغطاء.
نظرًا لأن React Native يستخدم React و JavaScript، هناك طريقتان رئيسيتان لتنفيذ الرسوم المتحركة على الشاشة. أولاً، دعنا نوضح حقيقة أساسية: يقوم React Native ببناء طرق عرض أصلية فعلية على الشاشة، وليس تلك التي يتم عرضها عبر متصفحات الويب المضمنة مثل Ionic. ولهذا السبب، إذا كنت ترغب في تحريك طريقة عرض بأي شكل من الأشكال، فيجب أن يتم ذلك في النهاية على طريقة العرض الأصلية.
يتعين على JavaScript التواصل مع نظام التشغيل بطريقة ما لإبلاغه بضرورة تحديث طريقة العرض. يعمل JavaScript في خيط مختلف عن خيط واجهة المستخدم (UI thread) – وهو الخيط الرئيسي – وهذا الخيط الأخير هو الوحيد الذي يمكنه تحديث طرق العرض. لذلك، يحتاج JS إلى استخدام الجسر (bridge) الذي يوفره React Native لتسلسل البيانات وإرسالها إلى نظام التشغيل.
1. الرسوم المتحركة عبر خيط JavaScript: المرونة ولكن بحذر
تتضمن هذه الطريقة أخذ طريقة عرض مرئية بالفعل على شاشة المستخدم، والعمل على تحديد موقعها التالي في خيط JavaScript. الخطوات تقريبًا كالتالي:
- تبدأ الرسوم المتحركة.
- يقوم
JavaScriptبتشغيل الدالةrequestAnimationFrame، وهي دالة تحاول التشغيل بمعدل 60 استدعاءً في الثانية (60 إطارًا في الثانية). - يحسب
JavaScriptالموضع التالي / الشفافية / التحويل (transform) / أي خاصية أخرى تقوم بتحريكها على طريقة العرض. - يقوم
JavaScriptبتسلسل هذه القيمة ويرسلها عبر الجسر. - في الطرف الآخر من الجسر، يقوم
Java(فيAndroid) أوObjective C(فيiOS) بإلغاء تسلسل هذه القيمة وتطبيق التحويلات المحددة على طريقة العرض المذكورة. - يتم تحديث الإطار على الشاشة.
هل لاحظت ما حدث هناك؟ لا تقوم أي من الخطوات بإعادة عرض مكون React Native فعليًا. هذا يعني أن Animated API يتجاوز فلسفة React بعدم تغيير متغيرات state. الخبر السار هو أن هذا مفيد جدًا في حالة الرسوم المتحركة، لأنه سيكون بطيئًا جدًا وسيؤثر سلبًا على الأداء إذا سمحنا لـ React بإعادة عرض المكون 60 مرة في الثانية!
على الرغم من هذه المزايا، هناك مشكلة أساسية هنا: JavaScript أحادي الخيط (single-threaded). لذا، فإن الطبيعة غير المتزامنة لـ JavaScript لا تعمل بشكل مثالي هنا لأن الرسوم المتحركة هي مهمة تعتمد على وحدة المعالجة المركزية (CPU bound task). دعنا نلقي نظرة على إيجابيات وسلبيات هذا النهج.
مزايا الرسوم المتحركة عبر خيط JavaScript:
- يمكنك إنشاء رسوم متحركة معقدة للغاية مكتوبة بلغة
JSوتظهر كرسوم متحركة أصلية. - تحكم أكبر في حالة الرسوم المتحركة.
عيوب الرسوم المتحركة عبر خيط JavaScript:
- عقوبة أداء كبيرة إذا كان خيط
JavaScriptمشغولًا للغاية. - إذا كان الجسر مشغولًا، ينخفض الأداء عندما يرغب نظام التشغيل /
JSفي التواصل مع بعضهما البعض.
هذا العيب الأخير هو عيب كبير بصراحة. هناك فيديو يوضح هذه المشكلة في الوقت الفعلي، حيث يمكنك أن ترى كيف يتسبب JS في مشاكل كبيرة مع الرسوم المتحركة عندما يصبح خيط JavaScript مشغولًا.
لماذا تتأخر الرسوم المتحركة المستندة إلى JavaScript؟
تبدأ الرسوم المتحركة التي تتم في JS في التأخر عندما تكون الرسوم المتحركة قيد التشغيل ويطلب المستخدم (أو التطبيق) إجراءً آخر يجب أن يتعامل معه خيط JS. على سبيل المثال، تخيل أن هناك رسومًا متحركة تحدث. هذا يعني أن JS مشغول بتشغيل الدالة requestAnimationFrame. بافتراض أن التحديثات نفسها ليست ثقيلة جدًا، افترض أن المستخدم يبدأ بالنقر على زر على الشاشة يزيد عدادًا. الآن، مع استدعاء requestAnimationFrame بشكل متكرر، يتم أيضًا إعادة عرض شجرة React الافتراضية مرارًا وتكرارًا لحساب العداد المتزايد. كلاهما مهمتان تعتمدان على وحدة المعالجة المركزية (CPU bound tasks) وتعملان على خيط واحد، لذلك سيكون هناك تأثير على الأداء. ستبدأ requestAnimationFrame في إسقاط الإطارات بسبب العمل الإضافي الذي يقوم به خيط JS. كل هذا يعني أنك ستحصل على رسوم متحركة متقطعة للغاية.
2. المحرك الأصلي للرسوم المتحركة (Native Driver): تعزيز الأداء
لا تقلق، لأن React Native يسمح لك بالفعل بتشغيل الرسوم المتحركة على المحرك الأصلي أيضًا! ماذا أعني بذلك، قد تسأل؟ حسنًا، ببساطة، هذا يعني أن React Native يُخفف عبء عمل الرسوم المتحركة من خيط JS إلى خيط واجهة المستخدم (UI thread) – أي نظام التشغيل – ويسمح له بالتعامل مع تحريك الكائن. هذا له العديد من الفوائد:
- يصبح خيط
JS(وجسرReact Native) الآن حرًا للتعامل مع المهام المكثفة الأخرى مثل النقرات المتكررة من المستخدم. - الرسوم المتحركة أكثر سلاسة بكثير لأنه لا توجد تكلفة إضافية للتسلسل/إلغاء التسلسل واتصال الجسر لكل إطار.
كيف يحقق React Native ذلك؟
يسمح مطورو React Native لك كمطور بتوفير خاصية تسمى useNativeDriver كقيمة منطقية (boolean) عند إنشاء كائن رسوم متحركة. عند تعيينها على true، يقوم React Native، قبل بدء الرسوم المتحركة، بتسلسل حالة الرسوم المتحركة بأكملها وما يجب القيام به في المستقبل. ثم ينقلها مرة واحدة عبر الجسر إلى نظام التشغيل. من تلك اللحظة فصاعدًا، يقوم كود Java (في Android) أو Objective C (في iOS) بتشغيل الرسوم المتحركة بدلاً من قيام JavaScript بحساب إطار الرسوم المتحركة التالي وإرسال تلك البيانات مرارًا وتكرارًا عبر الجسر. هذا يسرع الرسوم المتحركة كثيرًا وتعمل الرسوم المتحركة بسلاسة أكبر، خاصة على الأجهزة ذات المواصفات المنخفضة.
دعنا نشاهد عرضًا مرئيًا للرسوم المتحركة الأصلية مقابل الرسوم المتحركة المستندة إلى JS في React Native:

في هذه الحالة، تبدو الخطوات تقريبًا كالتالي:
- تبدأ الرسوم المتحركة.
- يقوم
JSبتسلسل معلومات الرسوم المتحركة ويرسلها عبر الجسر. - في الطرف الآخر، يستقبل نظام التشغيل تلك المعلومات ويشغل الرسوم المتحركة بشكل أصلي.
هذا كل شيء! أصبحت الرسوم المتحركة أخف بكثير على خيط JS الآن. لا مزيد من تشغيل requestAnimationFrame بلا نهاية. ومع ذلك، فإن هذه الطريقة لها نصيبها من الإيجابيات والسلبيات.
مزايا المحرك الأصلي للرسوم المتحركة:
- رسوم متحركة أسرع.
- خيط
JSغير محجوب. - جسر أقل ازدحامًا.
عيوب المحرك الأصلي للرسوم المتحركة:
- تحكم أقل في الرسوم المتحركة (لا يمكن لـ
JSرؤية ما يحدث على الشاشة بمجرد بدء الرسوم المتحركة التلقائية). - خصائص أقل للتحكم فيها – لا يدعم المحرك الأصلي جميع الخصائص التي يمكن تحريكها. على سبيل المثال، لا يمكن تحريك
widthأوheightبشكل أصلي، ولكنopacityوtransformيمكن.
في كثير من الحالات، ستجد أنه يمكنك العمل حول مجموعة مختلفة من الرسوم المتحركة باستخدام useNativeDriver: true لإنشاء تأثير مشابه لا يمكن تحقيقه بدون تعيين useNativeDriver: false. يعمل فريق React Native على إضافة دعم لمزيد من الخصائص، ولكن في الوقت الحالي، أعتقد أنها تعمل بشكل جيد.
الخلاصة التقنية
يُقدم React Native خيارين قويين لتنفيذ الرسوم المتحركة، كل منهما بمزاياه وعيوبه الواضحة. الفهم العميق لكيفية عمل كل آلية – سواء عبر خيط JavaScript أو من خلال المحرك الأصلي (Native Driver) – أمر بالغ الأهمية للمطورين. إن اختيار النهج الصحيح يعتمد بشكل كبير على تعقيد الرسوم المتحركة، والحاجة إلى التحكم الدقيق، والأداء المستهدف. في حين أن الرسوم المتحركة المستندة إلى JS توفر مرونة لا مثيل لها، فإنها قد تؤدي إلى اختناقات في الأداء في سيناريوهات معينة. على النقيض، يُقدم Native Driver تحسينات كبيرة في الأداء والسلاسة، خاصة على الأجهزة الأقل قوة، ولكنه يأتي مع قيود على التحكم والخصائص المدعومة. إن الموازنة بين هذه العوامل هي مفتاح بناء تطبيقات React Native تفاعلية وسريعة الاستجابة.