-
المساهمات
164 -
تاريخ الانضمام
-
تاريخ آخر زيارة
نوع المحتوى
ريادة الأعمال
البرمجة
التصميم
DevOps
التسويق والمبيعات
العمل الحر
البرامج والتطبيقات
آخر التحديثات
قصص نجاح
أسئلة وأجوبة
كتب
دورات
كل منشورات العضو ابراهيم الخضور
-
نقدم في هذا المقال مقاربة تطبيقية مبنية على المفاهيم اﻷساسية التي قدمتها المقالات السابقة. وستتعلم كيف تبني دوال مخصصة بنفسك وتطلع على بعض التفاصيل المفيدة عند التعامل مع الدوال أثناء دراستك لها المقال. ننصحك قبل أن تبدأ العمل معنا في هذه السلسلة أن تطلع على: أساسيات علوم الحاسب. أساسيات HTML. أساسيات عمل CSS أساسيات جافا سكريبت كما شرحناها في سلسلة المقالات السابقة. تطبيق عملي: بناء دالة سنبني في هذا التمرين دالة باسم ()displaymessage مهمتها عرض مربع رسالة على صفحة ويب، وستعمل كبديل خاص بك عن الدالة المدمجة مع المتصفح ()alert.تحدثنا عن عمل هذه الدالة سابقًا لكننا سنعيدها لتتذكر بعض التفاصيل. لهذا اكتب الشيفرة التالية في طرفية جافا سكريبت في متصفحك: alert("This is a message"); تقبل الدالة alert وسيطًا واحدًا هو النص الذي تريد عرضه ضمن صندوق الرسالة، وبتغييرك هذا النص تتغير الرسالة ضمن الصندوق. لكن هذه الدالة محدودة القدرات، إذ ستتمكن من تغيير النص المعروض بسهولة لكن من الصعب تغيير أشياء أخرى كاللون واﻷيقونة وغيرها من التفاصيل. لهذا سنبني دالة أكثر متعة. ملاحظة: ينبغي أن تعمل الشيفرة ضمن جميع المتصفحات الحديثة، لكن قد يبدو التنسيق غريبًا قليلًا في المتصفحات اﻷقدم. لهذا ننصحك بتنفيذ هذا التمرين في متصفحات حديثة مثل فايرفوكس وأوبرا وكروم. الدالة اﻷساسية لنبدأ بتشكيل دالة أساسية مع التقيد بنفس قواعد التسمية المتبعة عند تسمية المتغيرات، فلا مشكلة في ذلك طالما أن الدوال يتبعها قوسان وبالتالي يمكن تمييزها عن المتغيرات. أنشئ نسخة خاصة بك من ملف HTM الخاص بالتمرين وستجده بسيطًا يحتوي جسمه على زر واحد ويضم أيضًا تنسيقات CSS أساسية لتنسيق صندوق الرسالة، كما ستجد العنصر <script> الذي يضم شيفرة جافا سكريبت الخاصة بالتمرين أضف الشيفرة التالية ضمن العنصر <script>: function displayMessage() { ... } تبدأ الشيفرة بالكلمة المفتاحية function والتي تعني أننا نعرّف تابعًا يليها الاسم الذي نريد تسمية الدالة به يليه زوج من الأقواس وزوج من اﻷقواس المعقوصة. ونضع أية معاملات نريد نعطيها للدالة داخل القوسين العاديين، بينما نضع الشيفرة التي تنفذها الدالة بين القوسين المعقوصين. أضف أخيرًا الشيفرة التالية ضمن القوسين المعقوصين: const body = document.body; const panel = document.createElement("div"); panel.setAttribute("class", "msgBox"); body.appendChild(panel); const msg = document.createElement("p"); msg.textContent = "This is a message box"; panel.appendChild(msg); const closeBtn = document.createElement("button"); closeBtn.textContent = "x"; panel.appendChild(closeBtn); closeBtn.addEventListener("click", () => panel.parentNode.removeChild(panel), ); يختار السطر الأول من الشيفرة العنصر <body> باستخدام الواجهة البرمجية لشجرة DOM للحصول على الخاصية body للكائن document الذي يمثل مستند HTML بالكامل ثم يسند قيمته لثابت يُدعى body: const body = document.body; يستخدم القسم الثاني أحد دوال الواجهة البرمجية ()document.createElement ﻹنشاء عنصر <div> ثم يخزّن مرجعًا إليه ضمن ثابت يُدعى panel. سيمثّل هذا العنصر الحاوية الخارجية لصندوق الرسائل. نستخدم بعد ذلك دالة أخرى ()Element.setAtrtribute لضبط السمة class العائدة للوحة صندوق الرسائل على msgbox كي يسهل تنسيق العنصر. فلو ألقيت نظرة على تنسيقات الصفحة ستجد أنها تحتوي على الصنف msgbox. لتنسيق صندوق الرسائل ومحتواه. نستدعي أخيرًا الدالة ()Node.appendChild للثابت body الذي عرَفناه سابقًا والتي تضع عنصرًا ضمن آخر كابن له. وخصصنا العنصر <div> ليكون ابنًا للعنصر <body> والسبب في ذلك ألا يظهر العنصر الذي ننشئه في مكانه الافتراضي في الصفحة بل نريد وضعه في مكان نخصصه له. const panel = document.createElement("div"); panel.setAttribute("class", "msgBox"); body.appendChild(panel); نستخدم تاليًا الدالتين ()createElement و ()appendChilde اللتان رأينا عملهما سابقًا في إنشاء عنصري فقرة نصية <p> والحاقهما في الصفحة كابنين للعنصر <div>. وبعدها نستخدم الخاصية Node.textContent والتي تمثل المحتوى النصي للفقرة لوضع الرسالة المطلوبة ضمن الفقرة النصية وإشارة "x" كعنوان للزر لتعكس وظيفته وهي إغلاق صندوق الرسائل. const msg = document.createElement("p"); msg.textContent = "This is a message box"; panel.appendChild(msg); const closeBtn = document.createElement("button"); closeBtn.textContent = "x"; panel.appendChild(closeBtn); ثم نستدعي في النهاية الدالة ()addEveentListener ﻹضافة دالة (دالة سهمية غير مسماة) تُستدعى عندما ينقر المستخدم على الزر ومهمتها حذف العنصر الذي يمثل صندوق الرسائل بأكمله من الصفحة. يُمرر للتابع ()addEveentListener الذي يمكن أن يستخدمه أي عنصر في الصفحة دالة أخرى واسم الحدث الذي ينبغي ترصده وهو في حالتنا "click"، أي ستُنفّذ الدالة عندما ينقر المستخدم على الزر ( سنتحدث بتفصيل أكثر عن اﻷحداث في مقال "مدخل إلى اﻹحداث في جافا سكريبت. أما الدالة التي نستخدمها عند وقوع الحدث فتضم دالة أخرى من دوال الواجهة البرمجية لشجرة المستند DOM وهي ()Node.removeChild التي تزيل ابنًا محددًا من أبناء العنصر وهو في حالتنا العنصر <div>: closeBtn.addEventListener("click", () => panel.parentNode.removeChild(panel)); تعرض الشيفرة السابقة طريقة إنشاء عناصر HTML برمجيًا وستضيف شيفرتنا السابقة إلى الصفحة مايلي: <div class="msgBox"> <p>This is a message box</p> <button>x</button> </div> لا تقلق إن لم تتذكر تمامًا كيف تعمل الشيفرة السابقة، فكل ما نهتم له اﻵن هو هيكلية الدالة التي أنشأناها واستخدامها. استدعاء الدالة عرفّنا اﻵن الدالة التي نريدها ضمن العنصر <script> بالشكل الصحيح، لكنها لن تفعل شيئًا بنفسها. جرّب أن تضيف السطر التالي تحت الدالة في الشيفرة: displayMessage(); يستدعي هذا السطر الدالة وينفذها مباشرة. فعندما تحفظ التغييرات وتعيد تحميل الصفحة، يعرض المتصفح الشيفرة في الصفحة مباشرة ولمرة واحدة، لأننا استدعيناها مرة واحدة. افتح أدوات مطوري ويب في نتصفح وانتقل إلى طرفية جافا سكريبت واكتب السطر السابق مجددًا وسترى كيف تظهر الرسالة مجددًا! حققنا إذا ما نريده وهي دالة يمكن استخدامها بشكل متكرر في أي وقت نشاء. لكن لربما من اﻷفضل استخدامها كاستجابة لحدث ما أو إجراء ما، وهذا ما يحدث في التطبيقات الواقعية، فصندوق الرسائل يظهر مثلًا كاستجابة لوجود بيانات جديدة أو وقوع خطأ ما، أو تنبيه لفعل ما كأن يحاول المستخدم حذف ملفه مثلًا فتكون الرسالة على الشكل "هل أنت متأكد من ذلك؟"، أو عندما يضيف المستخدم بنجاح جهة اتصال جديدة وهكذا. أما في مثالنا، سنعرض الرسالة عندما ينقر المستخدم على الزر. احذف السطر اﻷخير الذي أضفته إلى الشيفرة. ما سنفعله تاليًا هو اختيار الزر ثم حفظ مرجع إليه ضمن ثابت، لهذا أَضف الشيفرة التالية أعلى تعريف الدالة: const btn = document.querySelector("button"); أضف السطر التالي بعد السطر السابق: btn.addEventListener("click", displayMessage); وعلى غرار ما فعلناه للتعامل مع حدث النقر على زر اﻹغلاق closeBtn نستدعي في هذا السطر الشيفرة كاستجابة لحدث النقر على الزر، لكن بدلًا من استدعاء دالة غير مسماة سنستدعي الدالة التي أنشأناها ()displayMessage باسمها. احفظ التغييرات وأعد تحميل الصفحة، سترى اﻵن الرسالة فقط عندما تنقر على الزر. قد تتسائل لماذا لم نضع القوسين بعد اسم الدالة عندما نراها كمعامل لدالة ترصد الأحداث؟ السبب هو أننا لا نريد تنفيذ الدالة دون النقر على الزر. فلو غيرت الشيفرة لتصبح على الشكل: btn.addEventListener("click", displayMessage()); ثم حفظت التغيرات وأعدت تحميل الصفحة، ستظهر الرسالة مباشرة دون النقر على الزر. تُدعى اﻷقواس أحيانًا وفق هذا السياق "عامل تنفيذ الدالة function invocation operator"، وتسُستخدم عندما تريد تنفيذ الدالة مباشرة ضمن نطاق العمل الحالي. بالمقابل، لا تُنفّذ الشيفرة داخل الدالة غير المسماة مباشرة، لأنها ضمن نطاق الدالة التي تترصد الحدث. تراجع عن التغيرات السابقة إن جربت الفكرة السابقة قبل المتابعة. تحسين الدوال باستخدام المعاملات لا زالت الدالة بشكلها الحالي غير مفيدة، فلا نريد عرض نفس الرسالة دائمًا، لهذا سنحاول تحسين الدالة بإضافة معاملات تسمح لنا باستدعائها وفق عدة خيارات: عدّل بداية السطر اﻷول من الدالة ليصبح كالتالي: function displayMessage(msgText, msgType) { عندما نستدعي الدالة اﻵن، يمكننا تزويدها بمتغيرين ضمن القوسين لتحديد الرسالة التي تعرضها في صندوق الرسائل ونوع هذه الرسالة. للاستفادة من المعامل اﻷول، عدّل السطر التالي: msg.textContent = "This is a message box"; ليصبح بالشكل: msg.textContent = msgText; عليك اﻵن تعديل استدعاء الدالة لتضم نص الرسالة الجديد، لهذا عدّل السطر التالي: btn.addEventListener("click", displayMessage); ليصبح بالشكل: btn.addEventListener("click", () => displayMessage("Woo, this is a different message!"), ); إن أردنا أن نخصص معاملات ضمن دالة نستدعيها ضمن قوسي دالة أخرى، لا يمكننا استدعائها مباشرة، ولا بد من وضعها ضمن دالة غير مسماة، وبالتالي لن تكون ضمن مجال رؤية الدالة المستدعية مباشرة زلن تُستدعى مباشرة. وهكذا لن تُستدعى الدالة حتى ينقر المستخدم على الزر. أعد تحميل الصفحة مجددًا وسترى أنها لا تزال تعمل جيدًا، ما عدا أنك تستطيع تغيير الرسالة الموجودة ضمن المعامل لتعرض رسالة مختلفة في صندوق الرسائل. معامل أكثر تعقيدًا بالنسبة للمعامل اﻵخر، سيتطلب اﻷمر عملًا أكثر. إذ سنجعل صندوق الرسائل يعرض أيقونة مختلفة وخلفية ذات لون مختلف وفقًا لقيمة هذا المعامل (msgType). حمّل اولاً اﻷيقونات اللازمة لهذا التمرين (أيقونة التحذير وأيقونة المحادثة) ثم خزنهما في المجلد "icons". ابحث عن تنسيقات CSS داخل الملف لأننا سنجري بعض التغييرات لعرض اﻷيقونات، ثم عدّل اتساع صندوق الرسائل من width: 200px إلى width: 242px. أضف الشيفرة التالية داخل القاعدة {} msgBox p.: padding-left: 82px; background-position: 25px center; background-repeat: no-repeat; سنضيف اﻵن بعض الشيفرة إلى الدالة ()displayMessage لعرض اﻷيقونات بالشكل المطلوب. لهذا ضع الشيفرة التالية داخل القوسين المعقوصين للدالة: if (msgType === "warning") { msg.style.backgroundImage = "url(icons/warning.png)"; panel.style.backgroundColor = "red"; } else if (msgType === "chat") { msg.style.backgroundImage = "url(icons/chat.png)"; panel.style.backgroundColor = "aqua"; } else { msg.style.paddingLeft = "20px"; } إن كانت قيمة العامل msgBox هي warning ستُعرض أيقونة التحذير وتصبح لون خلفية صندوق الرسائل أحمر، وإن كانت قيمته chat ستُعرض أيقونة المحادثة ويصبح لون الخلفية أزرق مائي. إما إن لم تُضبط قيمة المعامل msgType أو أسندت إليه قيمة غير محددة، ستُنفَّذ الشيفرة داخل {}else وستأخذ الفقرة النصية الحاشية الافتراضية ولن تظهر اﻷيقونة ولن يُضبط لون الخلفية، وبهذا يكون هذا المعامل اختياريًا. لنجرّب اﻵن الشكل المعدّل للدالة ()displayMessage ونغيّر طريقة الاستدعاء من الشكل: displayMessage("Woo, this is a different message!"); لتصبح كالتالي: displayMessage("Your inbox is almost full — delete some mails", "warning"); displayMessage("Brian: Hi there, how are you today?", "chat"); لاحظ فائدة المعاملات في تحسين طريقة عمل الدوال. ملاحظة: إن لم تعمل الشيفرة أو واجهتك المشاكل، بإمكانك موازنة شيفرتك مع الشيفرة الجاهزة على جت-هب (أو تجريها مباشرة) أو طرح أية أسئلة في قسم التعليقات أسفل المقال أو في قسم الأسئلة والأجوبة في أكاديمية حسوب. الخلاصة حاولنا في هذا المقال السير بك تدريجيًا في بناء دالة مخصصة يمكن بقليل من العمل اﻹضافي أن تكون جاهزة للاستخدام في تطبيقات واقعية، ويبقى علينا مناقشة موضوع أساسي آخر يتعلق بالقيم التي يمكن للدوال أن تعيدها، وسنرى ذلك في مقالات قادمة. ترجمة -وبتصرف- للمقال: Build your own function اقرأ أيضًا المقال السابق:الدوال وإعادة استخدام الشيفرة في جافا سكريبت الدوال Functions في جافاسكريبت الدوال العليا في جافاسكريبت تعلم لغة جافا سكريبت من الصفر حتى الاحتراف
-
تسمح لنا فكرة ضبط موقع العناصر بلغة CSS بإخراج عنصر من التخطيط الاعتيادي للمستند وتغيير سلوكه، كأن نقرر بأن يظهر فوق عنصر آخر أو يبقى دائمًا في نفس المكان ضمن نافذة عرض المتصفح. لهذا نشرح في هذا المقال القيم المختلفة للخاصية position وكيفية استخدامها. ملاحظة: يمكنك أن تنجز تمارين المقال على حاسوبك الشخصي، لهذا حاول أن تحضر نسخة من الملف 0_basic-flow.html من مستودع جيت-هاب (حمّل الشيفرة المصدرية أيضًا) واستخدامه كنقطة انطلاق. مدخل إلى تموضع العناصر يسمح لك تحديد مواضع العناصر في صفحة الويب في الحصول على نتائج مثيرة بتجاوز تنسيق الانسياب الاعتيادي. فماذا لو أردت أن تغيّر قليلًا موقع بعض الصناديق عن موقعها الافتراضي في الانسياب الاعتيادي لتمنح المستخدم تجربة خاصة مثلًا؟ سيكون تموضع العناصر الأداة المثالية لك في هذه الحالة. أو تخيّل أنك تريد أن تنشئ كائنًا في واجهة صفحتك يعوم فوق أجزاء أخرى أو أن يبقى دائمًا في مكانه ضمن نافذة المتصفح بصرف النظر عن تمرير المحتوى. سيساعدك توضيع العناصر أيضًا في إنجاز الأمر. توجد أنواع مختلفة لضبط مواضع العناصر، ولتحديد طريقة الضبط التي نحتاجها، سنستخدم الخاصية position ونوضح لك تاليًا خيارات مختلفة لاستخدامها. التموضع الساكن Static وهو التموضع الافتراضي لأي عنصر، ويعني "ضع العنصر في موقعه الطبيعي ضمن الانسياب الاعتيادي". ولكي ترى ذلك (وتحضّر الملف الذي ستتمرن عليه في الأقسام اللاحقة). أضف أولًا الصنف positioned إلى الفقرة النصية <p> الثانية: <p class="positioned">…</p> أضف الآن القاعدة التالية في نهاية شيفرة CSS: .positioned { position: static; background: yellow; } لن تجد عندما تحفظ التغيّرات وتعيد تحميل الصفحة أي اختلاف سوى لون الخلفية الجديد للفقرة النصية الثانية. وهذا بالفعل ما سيحدث فالتموضع الساكن هو التموضع الافتراضي للعنصر. التموضع النسبي Relative وهو أول التموضعات التي سنناقشها، ويشبه كثيرًا التموضع الساكن باستثناء أنه بمجرد أنك وضّعت العنصر في مكانه ضمن الانسياب الاعتيادي، بإمكانك عندها تعديل موقعه النهائي بما في ذلك وضعه فوق عناصر أخرى في الصفحة. لتوضيح الأمر، غيّر التصريح positioned في شيفرتك إلى: position: relative; لن تجد أي شيئ قد تغيّر أيضًا في هذه المرحلة إن حفظت التغيّرات وأعدت تحميل الصفحة، لكن كيف سنغير الموقع النهائي للعنصر؟ سنحتاج إلى استخدام الخاصيات top و bottom و left و right والتي سنشرحها تاليًا. الخاصيات top و bottom و left و right تستخدم هذه الخاصيات مع الخاصية position لتحديد المكان الذي تريد وضع العنصر فيه بدقة. لتجريب الأمر أضف إلى التصريح positioned. الشيفرة التالية: top: 30px; left: 30px; ملاحظة: يمكن أن تأخذ هذه الخاصيات قيمًا بأية واحدات مثل البكسل و mm و rem و %. إن حفظت لتغيرات الآن وأعدت تحميل الصفحة ستكون النتيجة كالتالي: See the Pen positioning-css1 by Hsoub Academy (@HsoubAcademy) on CodePen. لقد توضح الأمر قليلًا، أليس كذلك؟ لكن لا أعتقد أنك توقعت النتيجة. لماذا تحرّكت الفقرة نحو الأسفل واليمين مع أننا حددنا قيم الخاصيتين top و left. عليك أن تتخيل الموضوع كما لو كانت هناك قوة تدفع الفقرة من الجانبين السابقين (أعلى ويسار) وبالتالي ستنقل الفقرة نحو اليمين والأسفل. فلو كان ;top: 30px فيبدو الأمر كقوة تدفع صندوق الفقرة النصية من الأعلى نحو الأسفل وتحركه مقدار 30 بكسل. التموضع المطلق Absolute يأتي التموضع المطلق بنتائج مختلفة جدًا. ضبط الوضع المطلق غيّر قيمة الخاصية position كالتالي: position: absolute; لو حاولت حفظ التغيير وإعادة تحميل الصفحة ستجد النتيجة التالية: See the Pen positioning-css2 by Hsoub Academy (@HsoubAcademy) on CodePen. لاحظ بداية أن المكان المحجوز سابقًا للفقرة النصية الثانية في الانسياب الاعتيادي لم يعد موجودًا، وظهرت الفقرة النصية الثالثة بعد الأولى مباشرة. هذا الأمر صحيح نوعًا ما، فالعنصر الذي يأخذ توضعًا مطلقًا لن يظهر ضمن الانسياب الاعتيادي للعناصر، بل يتوضع ضمن طبقة خاصة به منفصلة عن الطبقة التي تضم عناصر الانسياب الاعتيادي. ولهذا الأمر فائدته، إذ يعني أنه بإمكاننا إنشاء واجهة مستخدم مستقلة لا تتداخل مع التخطيط الذي يضم بقية عناصر الصفحة. وكمثال عن هذه الواجهات نجد الصناديق المنبثقة ولوحات التحكم واللوحات القابلة للطي وعناصر واجهة المستخدم التي يمكن جرها وإفلاتها في أي مكان في الصفحة وغيرها. ونلاحظ ثانيًا أن موضع العنصر قد تغيّر، لأن سلوك الخاصيات top و bottom و left و right قد تغيّر مع التموضع المطلق. فبدلًا من توضيع العنصر بناء على موقعه بالنسبة إلى تخطيط الانسياب الاعتيادي، ستحدد تلك الخاصيات بعد صندوق العنصر عن حواف العنصر الحاوي. أي كأننا نقول في مثالنا أن صندوق الفقرة النصية ذات التموضع المطلق ستبتعد 30 بكسل عن الحافة العليا للعنصر الحاوي و 30 بكسل عن الحافة اليسرى له (إن العنصر الحاوي في حالتنا هي الكتلة الحاوية الأساسية initial containing block). ملاحظة: يمكنك استخدام top و bottom و left و right لإعادة تحديد أبعاد العناصر. جرّب مثلًا القيم التالية: ;top: 0 و ;bottom: 0 و;left: 0 و ; right: 0 و ;margin: 0 على العنصر الذي تحدد موضعه وراقب ما الذي سيحدث! أعد كل شيء إلى حاله عندما تنتهي. ملاحظة: تؤثر الهوامش على نوع العنصر، لكن لا تؤثر به خاصيات الهوامش المنقبضة Margin collapsing. سياق تموضع العناصر من هو العنصر الحاوي لعنصر ذو توضّع مطلق؟ يعتمد هذا الأمر كثيرًا على قيمة الخاصية position للعنصر الأب للعنصر المطلق. فإن لم يكن للعنصر عنصر أب قد حُددت قيمة الخاصية position له صراحةً فسيأخذ العنصر الأب التموضع الساكن. وتكون النتيجة أن يُحتوى العنصر المطلق ضمن الكتلة الحاوية الأساسية. للحاوية الأساسية أبعاد نافذة العرض وهي أيضًا الكتلة التي تحتوي العنصر <html> . وبعبارة أخرى، سيُعرض العنصر المطلق خارج حدود العنصر الأب ويتوضع بالنسبة إلى نافذة العرض. يقع العنصر المطلق ضمن العنصر <body> في شيفرة HTML المصدرية، لكن ستجده في التخطيط النهائي بعيدًا مسافة 30 بكسل عن الحد الأعلى للصفحة. بإمكاننا تغيير سياق توضع العنصر بمعنى كيفية توضعه المطلق وبالنسبة لأية عناصر. ننجز ذلك بضبط قيمة الخاصية position لأحد العناصر الآباء، أي العناصر التي يقع ضمنها العنصر ذو التموضع المطلق (إذ لا يمكن ضبط موقع عنصر بالنسبة إلى عنصر آخر إذا لم يكن ضمن هذا الأخير). لترى التأثير الذي نتحدث عنه، أضف التصريح التالي إلى القاعدة body: position: relative; من المفترض أن تحصل على النتيجة التالية: See the Pen positioning-css3 by Hsoub Academy (@HsoubAcademy) on CodePen. ضُبط موقع العنصر الآن بالنسبة إلى العنصر <body>. الخاصية z-index ما تقدمه فكرة التموضع المطلقة أمر ممتع، لكن هناك ميزة أخرى لم نذكرها بعد. فقد يبرز سؤال مهم عندما تتراكب العناصر فوق بعضها يتعلق بالعنصر الذي سيظهر فوق الجميع. ففي مثالنا حتى الآن، لدينا عنصر واحد قد غيّر موضعه وقد ظهر أعلى جميع العناصر الأخرى لأنها غير موضّعة، فما الذي سيحدث إذًا عندما نحدد موضع أكثر من عنصر؟ أضف شيفرة CSS التالية لتوضّع الفقرة النصية الأولى توضّعًا مطلقًا أيضًا: p:nth-of-type(1) { position: absolute; background: lime; top: 10px; right: 30px; } سترى الفقرة الأولى بعد تطبيق التغييرات الجديدة وقد ظهرت باللون الأخضر الفاتح وانتُزعت من الانسياب الاعتيادي للعناصر ثم وضعت في موقع أعلى من مكانها السابق. لاحظ أيضًا كيف حُشرت تحت الفقرة النصية التي وضّعناها سابقًا حيث تراكبت الفقرتين فوق بعضهما. ولأن الفقرة النصية الثانية (التي تمتلك الصنف positioned. ) قد ظهرت ثانيًا في ترتيب العناصر في الشيفرة المصدرية، فالعناصر الموّضعه التي ترتيبها متأخر عن العناصر الموّضعة الأخرى في الشيفرة المصدرية تربح أولوية الظهور في الأعلى. لكن هل يمكن تغيير الأمر؟ نعم باستخدام الخاصية z-index والتي تُعرف باسم "مؤشر العلو". وهي قيمة مرجعية للمحور الثالث Z الذي نفترض أنه يتجه نحو المستخدم عموديًا على الشاشة. وقد تتذكر من مقالات سابقة كيف استخدمنا المحور الأفقي (المحور X) والمحور العمودي (المحور Y) لتحديد إحداثيات أشياء مثل موقع صور الخلفية وتأثيرات الظل. فمن أجل اللغات التي تكتب من اليسار إلى اليمين، يمثل الإحداثي (0,0) الزاوية العليا اليسرى للصفحة أو العنصر ضمن حاويته. ويتحرك منها الإحداثي X نحو اليمين والإحداثي Y نحو الأسفل. للصفحات أيضًا محور ثالث هو المحور Z وهو كما ذكرنا محور تخيلي ينطلق من سطح الشاشة نحو المستخدم. وتؤثر قيمة محور العلو z-index على موقع العنصر الموّضع على هذا المحور، وكلما كانت قيمة هذه الخاصية أعلى لعنصر ظهر فوق العنصر ذو القيمة الأقل. ولكل العناصر القيمة الافتراضية للخاصية z-index وهي عمليًا القيمة 0. يسمح باستخدام القيم السالبة والتي تجعل العنصر ينزل نحو الأسفل. ولكي تغير ترتيب العناصر المتراكبة، أضف التصريح التالي إلى القاعدة p:nth-of-type(1) : z-index: 1; سترى الآن الفقرة ذات اللون الأخضر الفاتح في الأعلى: See the Pen positioning-css4 by Hsoub Academy (@HsoubAcademy) on CodePen. يجدر الانتباه إلى أن الخاصية z-index تقبل فقط قيمًا بلا وحدات، فلا يمكنك تخصيص قيمة مثل 23px. ودائمًا تأتي القيم الأكبر فوق القيم الأصغر، ولك حرية تخصيص هذه القيم بأي قيم تريدها، فاستخدام قيمتين مثل 2 و 3 يماثل استخدام القيمتين 300 و400. التموضع الثابت Fixed لنلق نظرة الآن على التموضع الثابت للعناصر، والذي يعمل تمامًا مثل التموضع المطلق مع اختلاف جوهري واحد. إذ يثبّت التموضع المطلق موقع العنصر بالنسبة إلى أقرب عنصر أب ضبطت خاصية التموضع له (والكتلة الحاوية الأساسية في حال لا يوجد عنصر سلف مضبوط الموقع)، بينما يثبت التموضع الثابت موقع العنصر بالنسبة إلى الجزء المرئي من نافذة العرض، باستثناء حالة واحدة تحدث إن كان أحد العناصر الأسلاف كتلة حاوية ثابتة نظرًا لتطبيق قيمة للخاصية transform مختلفة عن القيمة الافتراضية none. ويعني ذلك أنه بإمكانك إنشاء عناصر واجهة مستخدم ثابتة في مكانها مثل قوائم التنقل التي تُبقى مرئية دائمًا مهما قمت بتمرير محتوى الصفحة للأسفل. لنعمل سوية على المثال التالي كي تتوضح الصورة. احذف بداية القاعدتين (p:nth-of-type(1 و positioned. من شيفرة CSS. عدّل بعد ذلك القاعدة body واحذف التصريح ;position: relative ثم اجعل الارتفاع ثابتًا كالتالي: body { width: 500px; height: 1400px; margin: 0 auto; } سيُمنح الآن العنصر <h1> موضعًا ثابتًا ;position: fixed ونضبط هذا الموقع ليكون أعلى نافذة العرض بإضافة الشيفرة التالية: h1 { position: fixed; top: 0; width: 500px; margin-top: 0; background: white; padding: 10px; } لا بد من استخدام التصريح ;top: 0 ليبقى العنصر أعلى الشاشة، كما منحنا العنوان اتساعًا يماثل اتساع محتوى العمود وخلفية بيضاء وبعض الهوامش والحشوات كي يظهر المحتوى تحته. عند حفظ التغيّرات وإعادة تحميل الصفحة، سترى أن موقع العنوان سيقى ثابتًا، ويظهر المحتوى وكأنه يظهر ويختفي تحته عن تمريره. لكن تجدر الملاحظة أن بعض المحتوى قد اقتُطع تحت العنوان ولم يعد ظاهرًا، ذلك أن العنوان قد انتزع من الانسياب الاعتيادي وارتفع جزء من المحتوى ليحل مكانه. تُحل هذه المشكلة بدفع الفقرة النصية إلى الأسفل بضبط الهامش العلوي لها مثلًا: p:nth-of-type(1) { margin-top: 60px; } سترى النتيجة كالتالي: See the Pen positioning-css5 by Hsoub Academy (@HsoubAcademy) on CodePen. التموضع اللاصق Sticky هنالك قيمة أخرى للخاصية position هي position: sticky، وهي أحدث من القيم الأخرى نوعًا ما. وهي في الواقع خيار هجين بين الوضعين النسبي والثابت. تسمح هذه القيمة للعنصر بالتصرف وكأنه موضّع نسبيًا حتى تُمرر الصفحة إلى حد معين (مثل 10 بكسل عن أعلى نافذة العرض) ليصبح بعدها ثابتًا. مثال بسيط على Sticky يمكن استخدام الوضع اللاصق مثلًا في إنشاء قوائم تنقل يمكن تمريرها إلى حد معين ومن ثم تبقى في أعلى نافذة العرض. .positioned { position: sticky; top: 30px; left: 30px; } See the Pen positioning-css6 by Hsoub Academy (@HsoubAcademy) on CodePen. فهرس قابل للتمرير من الاستخدامات الشائعة والمهمة للوضع اللاصق هو إنشاء فهرس قابل للتمرير تبقى فيه العناوين ملتصقة بأعلى الصفحة عندما تصل إليها. ولكتابة شيفرة مثال كهذا جرّب ما يلي: <h1>Sticky positioning</h1> <dl> <dt>A</dt> <dd>Apple</dd> <dd>Ant</dd> <dd>Altimeter</dd> <dd>Airplane</dd> <dt>B</dt> <dd>Bird</dd> <dd>Buzzard</dd> <dd>Bee</dd> <dd>Banana</dd> <dd>Beanstalk</dd> <dt>C</dt> <dd>Calculator</dd> <dd>Cane</dd> <dd>Camera</dd> <dd>Camel</dd> <dt>D</dt> <dd>Duck</dd> <dd>Dime</dd> <dd>Dipstick</dd> <dd>Drone</dd> <dt>E</dt> <dd>Egg</dd> <dd>Elephant</dd> <dd>Egret</dd> </dl> تبدو شيفرة CSS قريبة من التالي: تتحرك العناصر <dt> في الانسياب الاعتيادي مع المحتوى عند تمريره، لكن بإضافة الخاصية position: sticky إلى هذه العناصر بالإضافة إلى الخاصية top ستعمل المتصفحات الحديثة على إبقاء العناوين في أعلى نافذة العرض عندما تبلغ هذا الموقع. سيستبدل كل عنوان لاحق العنوان السابق عندما يصل إليه وهكذا. dt { background-color: black; color: white; padding: 10px; position: sticky; top: 0; left: 0; margin: 1em 0; } See the Pen positioning-css7 by Hsoub Academy (@HsoubAcademy) on CodePen. لاحظ كيف تبقى العناصر اللاصقة لاصقةً بالنسبة إلى أقرب عنصر يمتلك آلية تحدد طريقة تمرير محتواه أي بعبارة أخرى له قيمة مخصصة للخاصية position. الخلاصة تعرّفنا في هذا المقال على الأوضاع المختلفة التي يمكن أن يأخذها العنصر وجرّبنا الكثير من الأوضاع، مع ذلك لا تعد هذه الطريقة مناسبًة لتخطيط الصفحات في الويب الحديث بل لها حالات استخدام مخصصة مفيدة كما رأينا. ترجمة -وبتصرف- للمقال: Positioning اقرأ أيضًا المقال السابق: الخاصية float: تعويم عناصر الصفحة في CSS التحكم في تخطيط الصفحة وضبط محاذاة العناصر في CSS أساسيّات التَمَوْضُع على صفحات الويب (CSS Positioning 101) مدخل إلى تَموضُع الخلفيّة Background Positioning في CSS مدخل إلى تخطيط صفحات الويب باستخدام CSS
-
بدأنا في مقال سابق بإنشاء لعبة إلكترونية باستخدام محرك الألعاب جودو، وحان الوقت اﻵن لضم كل شيء معًا وإنشاء مشهد كامل للعبتنا، لهذا سنبدأ في مقال اليوم باستكمال العمل على اللعبة وإنشاء مشهدها الأساسي وإعداده بالطريقة المناسبة. إنشاء عقدة المشهد الرئيسي للعبة لإنشاء مشهد جديد في محرك ألعاب جودو علينا إنشاء عقدة اسمها Main من النوع Node ولا حاجة أن تكون من النوع Node2D لأنها مجرد حاوية لمنطق اللعبة ولن تحتاج فعلًا إلى وظائف هذا النوع من العقد. انقر على زر إنشاء نسخة (أيقونة السلسلة في نافذة المشهد) ثم اختر المشهد "Player.tscn". أضف بعد ذلك العقد التالية كأبناء للعقدة Main وفق اﻷسماء التالية: مؤقت Timer باسم MobTimer للتحكم بمعدل تكاثر الزواحف، واضبط الخاصية Wait Time له على 0.5. مؤقت Timer باسم ScoreTimer لزيادة النتيجة كل ثانية، واضبط الخاصية Wait Time له على 1. مؤقت Timer باسم StartTimer لإضافة تأخير بسيط قبل بدء اللعبة، واضبط الخاصية Wait Time له على 2. عقدة من النوع Marker2D اسمها StartPosition لتحديد موقع البداية بالنسبة للاعب. إضافة إلى ذلك، اضبط الخاصية One Shot للمؤقت StartTimer على "فعّال On" و الخاصية Position للعقدة StartPosition على القيمة التالية(450, 240). تكاثر الأعداء نستخدم المشهد الرئيسي Main في تكاثر الأعداء ونريدهم أن يظهروا في أماكن عشوائية على حواف الشاشة، لهذا أضف عقدة من النوع Path2D باسم MobPath كابن للعقدة الرئيسية. سترى عدة أزرار إضافية في أعلى المحرر عند اختيار اﻷيقونة Path2D: اختر الزر الموجود في المنتصف "إضافة عقدة (في مساحة خالية)" ثم ارسم مسارًا بالنقر ضمن المحرر في النقاط التي يعرضها الشكل التالي. ولكي يكون تحديد النقاط دقيقًا فعّل الخيارين "استخدام المحاذاة للشبكة Use Grid Snap" و "استخدم المحاذاة الذكية Use Smart Snap"، وستجدهما إلى يمين أيقونة "القفل" على شكل مغناطيس بجوار عدة نقاط وآخر بجوار شبكة كما توضح الصورة التالية: ملاحظة مهمة: ارسم المنحني باتجاه عقارب الساعة وإلا ستتكاثر الزواحف إلى الخارج بدلًا من الداخل. بعد تحديد أربع نقاط، انقر على أيقونة "إغلاق المنحني" وسيكتمل هذا المنحني. أضف بعد الانتهاء من رسم المنحني العقدة PathFollow2D كابن للعقدة MobPath وسمها MobSpawnLocation. ستدور هذه العقدة وتتبع المسار عندما تتحرك وبالتالي يمكن استخدامها لاختيار مواقع واتجاهات عشوائية على طول المسار. سيبدو المشهد الرئيسي اﻵن كما يلي: السكربت الرئيسي أضف سكربت (نص برمجي) إلى المشهد Main ثم أضف العبارة export var mob_scene: PackedScene@ التي تسمح لنا باختيار مشهد اﻷعداء الذي نريد صنع نسخة عنه: extends Node @export var mob_scene: PackedScene var score انقر على العقدة Main وستجد الخاصية Mob Scene ضمن الفاحص في لوحة "متغيرات السكربت"، وﻹسناد قيمة إلى هذه الخاصية يمكنك اتباع إحدى الطريقتين التاليتين: اسحب المشهد من حاوية نظام الملفات وأفلته ضمن الخاصية Mob Scene. انقر على زر السهم المجاور للخاصية Mob Scene وانقر "تحميل" ثم اختر mob.tscn. اختر نسخة العقدة Player الموجودة ضمن العقدة Main ثم انقر على حاوية "عقدة" إلى جوار "الفاحص" في الشريط الجانبي لترى قائمة تضم جميع اﻹشارات للعقدة Player. جد اﻹشارة hit وانقر عليها نقرة مزدوجة (أو انقر عليها بالزر الأيمن واختر "توصيل"). ستفتح هذه العملية نافذة جديدة نريد فيها إنشاء دالة جديدة ندعوها game_over تعالج ما نريد فعله عند انتهاء اللعبة. اكتب "game_over" في المربع النصي "الدالة المتلقية Receiver Method" أسفل النافذة ثم انقر زر "وصل Connect". وما سيحدث اﻵن أن اﻹشارة hit التي بثها اللاعب Player سيعالجها السكربت اﻷساسي "Main". أضف اﻵن الشيفرة التالية إلى الدالة الجديدة، وأضف أيضًَا الدالة new_game التي تحضّر كل شيء عند بداية اللعبة: func game_over(): $ScoreTimer.stop() $MobTimer.stop() func new_game(): score = 0 $Player.start($StartPosition.position) $StartTimer.start() صل اﻵن اﻹشارة ()timeout العائدة إلى كل عقدة مؤقت (StartTimer و ScoreTimer و MobTimer) إلى السكربت الرئيسي. وسيشغل المؤقت StartTimer المؤقتين الآخرين ويزيد المؤقت ScoreTimer النتيجة بمقدار 1. func _on_score_timer_timeout(): score += 1 func _on_start_timer_timeout(): $MobTimer.start() $ScoreTimer.start() سننشئ ضمن الدالة نسخة عن العدو (الزواحف)، لهذا، سنختار مكانًا عشوائيًا للبدء على المسار Path2D ونضبط حركة العدو. تدور العقدة تلقائيًا عندما تلحق بالمسار، لهذا سنستفيد منها في اختيار جهة حركة الزاحف وموقعه. وعندما تتكاثر الزواحف سنختار قيمة عشوائية لسرعة حركتها بين 150 و 250. وانتبه إلى أن إضافة نسخة جديدة إلى المشهد تكون من خلال التعليمة add_child: func _on_mob_timer_timeout(): # mob scene أنشئ نسخة من مشهد الزاحف var mob = mob_scene.instantiate() # Path2D اختر مكانًا عشوائيًا على المسار. var mob_spawn_location = get_node("MobPath/MobSpawnLocation") mob_spawn_location.progress_ratio = randf() # اجعل اتجاه الزاحف عمودًا على اتجاه المسار var direction = mob_spawn_location.rotation + PI / 2 # اختر موقعًا عشوائيًًًًًًًًًا للزاحف mob.position = mob_spawn_location.position # أضف بعض العشوائية إلى المسار direction += randf_range(-PI / 4, PI / 4) mob.rotation = direction # اختر سرعة الزاحف var velocity = Vector2(randf_range(150.0, 250.0), 0.0) mob.linear_velocity = velocity.rotated(direction) # إجعل الزواحف تتكاثر بإضافتها إلى الشاشةالرئيسية add_child(mob) ملاحظة هامة: قد تتساءل لماذا نستخدم العدد PI في الدوال التي تتعامل مع الزوايا؟ لأن جودو يستخدم الراديان لقياس الزوايا. تمثّل PI نصف دوره كما يمكن استعمال TAU التي تمثّل دورة كاملة. لكن إن كنت تفضّل العمل مع الدرجات ستحتاج إلى الدالتين ()rad_to_deg و ()deg_to_rad للتحويل بين الدرجات والراديان. اختبار المشهد نختبر اﻵن إذا كانت كل شيء على ما يرام حتى اللحظة، لهذا استدعي الدالة new_game ضمن الدالة ()ready_: func _ready(): new_game() لنجعل أيضًا المشهد Main المشهد الرئيسي في اللعبة، وهو المشهد الذي يعمل تلقائيًا. انقر على زر التشغيل واختر main.tscn عندما يُطلب ذلك. من المفترض أن تكون قادرًا على تحريك اللاعب في جميع الاتجاهات وترى الزواحف تتحرك وتتكاثر، وسترى كيف يختفي اللاعب عندما يصطدم بالعدو. عندما تتأكد أن كل شيء يعمل جيدًا أزل الدالة new_game من الدالة ()ready_. الخلاصة تعلمنا في مقال اليوم كيف ننشئ المشهد الأساسي للعتبنا الإلكترونية وسنكتفي في هذا المقال بهذه المرحلة، ومع ذلك لا تزال اللعبة غير مكتملة وتنقصها بعض اللمسات النهائية وإنشاء واجهة مناسبة للعبة، لذا سنشرح خطوات تعزيز اللعبة بواجهة تتضمن تأثيرات صوتية واختصارات لوحة المفاتيح وغيرها من الخيارات وهذا ما سنراه في المقال التالي من هذه السلسلة. ترجمة -وبتصرف- للمقال: The main game scene اقرأ أيضًا المقال السابق: بناء لعبة ثنائية البعد عبر محرك الألعاب Godot - الجزء الثاني: إنشاء مشاهد اللعبة وبرمجتها إعداد محرك الألعاب جودو Godot للعمل مع قاعدة البيانات SQLite مدخل إلى محرك الألعاب جودو Godot مطور الألعاب: من هو وما هي مهامه
-
نناقش في هذا المقال العمليات الرياضية في جافا سكريبت وكيفية استخدام العوامل الرياضية وغيرها من اﻷفكار للتعامل مع اﻷعداد وصولًا إلى النتيجة المطلوبة. ننصحك قبل المتابعة في قراءة هذا المقال بالاطلاع على بعض المقالات السابقة مثل: أساسيات علوم الحاسب. أساسيات HTML. أساسيات عمل CSS. الرياضيات للجميع بعضنا يحب إجراء الحسابات الرياضية، وبعضنا الآخر يكره الرياضيات منذ تلك اللحظة التي اضطر فيها تعلم جدول الضرب والقسمة المطوّلة في المدرسة والبعض اﻵخر ما بين بين. لكن الجميع متفق على أن الرياضيات أمر أساسي في حياتنا بشكل أو بآخر ولا يمكن الاستغناء عنها، وخاصة إن كنت مهتمًا بتعلم البرمجة. فالكثير من اﻷمور التي ننفذها في أكواد البرمجة تعتمد على معالجة بيانات عددية، أو حساب قيم جديدة. لهذا لا تتفاجأ إن علمت أن لغة جافا سكريبت تضم مجموعة متكاملة من الدوال الرياضية الجاهزة للاستخدام. وما نقدمه في هذا المقال مجرد تعريف بالميزات اﻷساسية التي لا بد من معرفتها. أنواع اﻷعداد في عالم البرمجة ستجد أن النظام العشري البسيط الذي خبرناه جميعًا أكثر تعقيدًا مما تعتقد، لهذا نستخدم مصطلحات مختلفة لوصف اﻷنواع المختلفة للأعداد في نظام العد هذا: الأعداد الصحيحة Integers: وهي أعداد ذات فاصلة عائمة لكن دون أجزاء عشرية (لا أرقام بعد الفاصلة) قد تكون موجبة أو سالبة مثل "34" أو "5-". اﻷعداد ذات الفاصلة العائمة Floating point numbers:ولها خانات عشرية وأجزاء عشرية (تضم أرقام بعد الفاصلة) مثل "12.5" و "23.3331". اﻷعداد المضاعفة Doubles: تمثل نمطًا خاصًا من اﻷعداد العائمة لكنها تمتلك دقة أكبر من اﻷعداد ذات الفاصلة العائمة المعيارية (أي أنها تمثل القيم ذات اﻷجزاء العشرية بدقة أكبر إذ تضم عددًا أكبر من اﻷرقام بعد الفاصلة). كما يوجد أكثر من نظام عد، فالنظام العشري هو نظام أساسه العدد 10 (أي يتكون أي عدد وفق النظام العشري من خانات مكوّنة من أرقام بين 0 إلى 9)، لكن هناك أنظمة أخرى مثل: النظام الثنائي Binary: يستخدم في لغات البرمجة منخفضة المستوى وتتكون أعداده من تتابع رقمين فقط 0 أو 1. النظام الثماني Octal: أساسه 8 أي يستخدم فقط اﻷرقام من 0 إلى 7. النظام الست عشري hexadecimal: أساسه 16 ويستخدم اﻷرقام من 0 إلى 9 والحروف من a إلى f (ربما اطلعت على هذا النظام عند ضبط اﻷلوان في CSS). لكننا كبداية سنلتزم بنظام العد العشري المألوف بالنسب لك خلال سلسلة مقالاتنا التمهيدية هذه، ولن تكون مضطرًا للتفكير ببقية اﻷنظمة. أما الخبر الجيد التالي فهو أن جافا سكريبت تعرّف نوعًا واحدًا للبيانات العددية هو number على عكس لغات برمجة أخرى فاﻷعداد الصحيحة والعائمة والمضاعفة هي مجرد أعداد في جافا سكريبت وتتعامل معها بنفس الطريقة تمامًا. ملاحظة: في الواقع هناك نوع آخر للأعداد في جافا سكريبت غير number وهو النوع BigInt وهو يستخدم للتعامل مع اﻷعداد الضخمة جدًا، لكننا لن نستخدمه في سلسلتنا هذه. كلها أعداد بالنسبة لجافا سكريبت سنجرّب العمل مع بعض اﻷعداد حتى نتعرف على الصياغة الصحيحة، لهذا ادخل إلى طرفية جافا سكريبت في متصفحك (اضغط "Ctrl" + "Shift" + "K" في متصفح فايرفوكس) واتبع الخطوات التالية: صرّح عن المتغيرين التاليين myInt و myFloat (ثابتين باﻷحرى) على الترتيب ثم اكتبهما في الطرفية من جديد للتأكد أن كل شيء على ما يرام: const myInt = 5; const myFloat = 6.667; myInt; myFloat; تُسند القيم العددية للمتغيرات دون أن تُحاط بإشارات تنصيص. تحقق اﻵن أن المتغيران لهما نفس النوع باستخدام العامل typeof يليها اسم المتغير كالتالي: typeof myInt; typeof myFloat; ومن المفترض أن تحصل على النتيجة numberفي الحالتين. هذا أفضل، كي نتعامل مع متغيرين لهما نفس النوع بدلًا من متغيرين من نوعين مختلفين لأن التعامل معهما سيكون مختلفًا. توابع رياضية مفيدة يمثل الكائن Number نموذجًا لجميع اﻷعداد التي تستخدمها في جافا سكريبت ويمتلك هذا الكائن مجموعة من التوابع المفيدة تساعدك على التعامل مع الأعداد. لن نستعرض جميع هذه التوابع في مقالنا ﻷننا نريده بسيطًا ويغطي فقط الأساسيات، لكن مع تقدمك في تعلم جافا سكريبت، لا بد من العودة مرارًا إلصفحة هذا الكائن لتطلع على التوابع اﻷخرى التي يقدّمها. فلكي تقرّب العدد ليضم عددًا محددًا من اﻷرقام بعد الفاصلة استخدم التابع ()toFixed. جرّب ذلك بكتابة السطر التالي في الطرفية: const lotsOfDecimal = 1.766584958675746364; lotsOfDecimal; const twoDecimalPlaces = lotsOfDecimal.toFixed(2); twoDecimalPlaces; التحويل إلى النوع number قد ينتهي بنا اﻷمر أحيانًا إلى مواجهة عدد مخزّن ضمن سلسلة نصية ومن الصعب حينها تنفيذ أية حسابات رياضية عليه. وعادة ما نواجه هذه الحالات عند استخلاص البيانات الموجودة في حقل إدخال نصي <input> ضمن نموذج <form>. وبالطبع هناك طريقة لحل هه المشكلة تتمثل في تمرير هذه القيمة إلى الكائن ()Number الذي يعيد نفس القيمة لكن كعدد وليس كسلسلة نصية. لفهم هذه الطريقة جرّب كتابة اﻷسطر التالية في الطرفية: let myNumber = "74"; myNumber += 3; ستلاحظ أن الجواب سيكون 743 بدلًا من 77، ذلك أن myNumber يعرف في الواقع سلسلة نصية. وللتأكد من ذلك جرّب ما يلي: typeof myNumber; لاحظ ماذا فعلنا لحل المشكلة: let myNumber = "74"; myNumber = Number(myNumber) + 3; سنحصل اﻻن على النتيجة المطلوبة 77. العوامل الحسابية في جافا سكريبت وهي العوامل الحسابية اﻷساسية التي تستخدم لتنفيذ الحسابات اﻷساسية كالجمع والطرح وغيرها: العامل اسمه الغاية منه مثال + الجمع إضافة عددين إلى بعضهما 6+9 - الطرح طرح العدد اﻷيمن من اﻷيسر 6-9 * الضرب ضرب عددين معًا 9*6 / القسمة قسمة العدد اﻷيسر على اﻷيمن 9/6 % باقي القسمة يعيد باقي قسمة العدد اﻷيسر على اﻷيمن 4%9 سيكون الناتج هو 1 ** الرفع إلى قوة يرفع العدد اﻷيمن إلى قوة هي العدد اﻷيسر 4**2 سيكون الناتج هو 16 ملاحظة1: تُدعى اﻷعداد التي تخضع للعمليات الحسابية بالمعاملات operands. ملاحظة2: قد ترى أحيانًا عملية رفع إلى قوة باستخدام التابع اﻷقدم ()Math.pow الذي يعمل بنفس الطريقة فالأمر (7,3)Math.pow يعطي نفس نتيجة الأمر 3**7. لن نعلّمك بالطبع كيف تجري الحسابات الرياضية لكن نريد اختبار فهمك لصياغة هذه العمليات باستخدام جافا سكريبت، لهذا افتح طرفية المتصفح واتبع الخطوات التالية: حاول بداية أن تنفذ بعض العمليات التي تختارها مثل: 10 + 7; 9 * 8; 60 % 3; بإمكانك أيضًا أن تجرب تصريح وتهيئة بعض المتغيرات بقيم عددية من اختيارك، ثم حاول استخدام هذه المتغيرات لتنفيذ عمليات حسابية، إذ ستسلك المتغيرات سلوك قيمها تمامًا. إليك بعض اﻷمثلة: const num1 = 10; const num2 = 50; 9 * num1; num1 ** 3; num2 / num1; حاول أخيرًا أن تختبر بعض العبارات اﻷكثر تعقيدًا مثل: 5 + 10 * 3; (num2 % 9) * num1; num2 + num1 / 8 + 2; قد لا تعطيك بعض الحسابات الأخيرة النتائج التي تتوقعها، وقد تجد جوابًا عن ذلك في القسم التالي. أفضلية العمليات الحسابية لنلق نظرة على المثال السابق ولنفترض أن المتغير num2 يضم القيمة 50 ويضم المتغير num1 القيمة 10 ولننفذ العملية التالية: num2 + num1 / 8 + 2; فقد يقرأ البعض هذه العملية بالشكل 50 زائد 10 يساوي 60 ثم * 8 زائد 2 يساوي 10 وأخيرًا 60 تقسيم 10 يساوي 6. لكن ما يفعله المتصفح هو التالي 10 تقسيم 8 يساوي 1.25 ثم 50 زائد 1.25 زائد 2 يساوي 53.25. السبب في ذلك أن المتصفح يحترم أفضلية العمليات الحسابية فبعض العمليات الحسابية تُتفّذ قبل أخرى. وأفضلية العمليات الحسابية في جافا سكريبت هي نفس اﻷفضلية التي تعلمناها في المدرسة أي الرفع إلى القوة أولًا ثم يأتي الضرب والقسمة بنفس اﻷهمية وأخيرًا الجمع والطرح مع الانتباه أن الحسابات تجري دائمًا من اليسار إلى اليمين. لإلغاء اﻷفضلية السابقة استخدم اﻷقواس فلها أعلى أفضلية، إذ يمكنك إحاطة العملية التي تريد أن تنفذها في البداية بين قوسين. فلكي تحصل على النتيجة 6 في المثال السابق يمكنك تغيير الشيفرة لتصبح كالتالي: (num2 + num1) / (8 + 2); جرب ذلك وسترى! عوامل الزيادة والنقصان قد تتطلب الحسابات أحيانًا إضافة نفس العدد أو طرحه بشكل مستمر من متغير عددي. يمكن أن ننجز ذلك مباشرة باستخدام العامل (++) للزيادة بواحد و (--) للإنقاص بواحد. وتذكر أننا استخدمنا العامل (++) في لعبة "خمّن الرقم" في مقال سابق. حيث زدنا قيمة المتغير بمقدار واحد لتتبع عدد المحاولات التي نفّذها اللاعب. guessCount++; جرّب هذا العوامل بنفسك وستلاحظ أنها لا تعمل مع عدد مباشرة وقد يبدو هذا غريبًا لكن ما يجري أننا نحدّث قيمة المتغيّر ولا ننفذ العملية على القيمة نفسها. سينتج خطأ عن التعليمة التالية: 3++; لهذا لا يمكن استخدام عامل الزيادة أو النقصان إلا مع المتغيرات: let num1 = 4; num1++; وهذا أمر غريب آخر يحدث، فالناتج سيبقى 4! والسبب أن المتصفح سيعيد القيمة الحالية ثم يضيف 1. تأكد من ذلك بطلب قيمة المتغير من جديد: num1; ينطبق اﻷمر أيضًا على عامل اﻹنقاص بواحد: let num2 = 6; num2--; num2; ملاحظة: بإمكانك إضافة أو إنقاص قيمة المتغير ثم عرض النتيجة بوضع عامل الزيادة أو النقصان قبل المتغير مثل num1++. جرّب ذلك! عوامل اﻹسناد وهي عوامل تسند قيمًا إلى المتغيرات وقد استخدمنا منها العامل اﻷساسي = كثيرًا حتى اﻵن، والذي يخزّن القيمة التي على يسار المساواة في المتغير الذي يقع إلى يسارها. let x = 3; //القيمة 3 x يضم المتغير let y = 4; //القيمة 4 y يضم المتغير x = y; //وهي 4 y قيمة المتغير x يتضمن المتغير لكن هناك أيضًا عوامل إسناد اخرى أكثر تعقيدًا وتزود المبرمج بطرق مختصرة لتبقى الشيفرة أكثر أناقة وأكثر فعالية. إليك قائمة بالعوامل اﻷكثر شيوعًا: العامل اسمه الغاية منه مثال اختصار لـ += إسناد وإضافة جمع قيمة المتغير إلى اليسار مع القيمة على اليمين وإسناد الناتج إلى المتغير ويعيد قيمته الجديدة ;x+=4; x=x+4 -= إسناد وطرح طرح القيمة إلى اليمين من قيمة المتغير على اليسار وإسناد الناتج إلى المتغير ويعيد قيمته الجديدة ;x-=4; x=x-4 *= إسناد وجداء جداء قيمة المتغير إلى اليسار مع القيمة على اليمين وإسناد الناتج إلى المتغير ويعيد قيمته الجديدة ;x*=4; x=x*4 /= إسناد وقسمة تقسيم قيمة المتغير إلى اليسار على القيمة على اليمين وإسناد الناتج إلى المتغير ويعيد قيمته الجديدة ;x/=4; x=x/4 جرّب استخدام بعض هذه العوامل في طرفية المتصفح كي تتعرف على طريقة عملها. حاول أن تخمّن في كل مرة القيمة التي ستنتج قبل أن تنتقل إلى سطر آخر. ولاحظ أنه باﻹمكان إسناد متغيرات إلى متغيرات أخرى بكل بساطة، إليك مثالًا" let x = 3; //القيمة 3 x يضم المتغير let y = 4; //القيمة 4 y يضم المتغير x *= y; // القيمة 12 x يتضمن المتغير تطبيق عملي: تحديد أبعاد صندوق رسم سنتعامل في هذا التطبيق مع عوامل حسابية ومعاملات مختلفة لتغيير أبعاد صندوق. يُرسم الصندوق باستخدام واجهة برمجية API تُدعى Canvas API. لا تقلق حاليًا حيال عمل الواجهة وركّز على الرياضيات. يُعرّف اتساع وارتفاع الصندوق عبر المتغيرين xو y وتقدر قيمتهما بالبكسل ويعطى كلاهما القيمة 50 في البداية. ستجد في صندوق الشيفرة القابلة للتعديل في الأعلى سطرين يطلب إليك تعديلهما كي تجعل الصندوق يتمدد ويتقلص إلى حجم محدد باستخدام عوامل محددة أو/و قيم في كل حالة، عليك أن تجرّب التالي: غير السطر الذي يحسب قيمة x كي يبقى اتساع الصندوق 50 بكسل، لكن يجب أن تُحسب القيمة 50 انطلاقًا من عددين هما 43 و 7 وباستخدام عملية حسابية. غير السطر الذي يحسب قيمة y كي يصبح ارتفاع الصندوق 75 بكسل، لكن يجب أن تُحسب القيمة 75 انطلاقًا من عددين هما 3 و 25 وباستخدام عملية حسابية. غير السطر الذي يحسب قيمة x كي يصبح اتساع الصندوق 250 بكسل، لكن يجب أن تُحسب القيمة 250 انطلاقًا من عددين وعملية باقي القسمة. غير السطر الذي يحسب قيمة y كي يصبح ارتفاع الصندوق 150 بكسل، لكن يجب أن تُحسب القيمة 150 انطلاقًا من ثلاثة أعداد وعمليتي طرح وقسمة. غير السطر الذي يحسب قيمة x كي يصبح اتساع الصندوق 200 بكسل، لكن يجب أن تُحسب القيمة 250 انطلاقًا من العدد 4 وعامل إسناد. غير السطر الذي يحسب قيمة y كي يصبح ارتفاع الصندوق 200 بكسل، لكن يجب أن تُحسب القيمة 250 انطلاقًا من العددين 50 و 3 وعمل الضرب وعامل اﻹسناد واﻹضافة. لا تقلق إن خرّبت الشيفرة، بل اضغط على الزر "Reset" في الأسفل ليعود كل شيء إلى ما كان عليه. حاول أن تجرّب أفكارًا أخرى مشابهة إن نجحت في اﻹجابة عن كل الأسئلة السابقة. عوامل الموازنة لا بد في بعض اﻷحيان من كتابة بعض الاختبارات التي تكون نتيجتها صحيح أو خاطئ كي ننفذ شيفرة ما وفقًا لنتيجة الاختبار. لهذا نستخدم عوامل الموازنة: العامل اسمه الغاية منه مثال === مساواة تامة يختبر إن كان القيمتين على يسار ويمين العامل متطابقتين تمامًا 4+3==5 ==! لا مساواة تامة يختبر إن كان القيمتين على يسار ويمين العامل غير متطابقتين تمامًا 2+3=!5 > أصغر من يختبر إن كانت القيمة على يمين العامل أصغر تمامًا من القيمة إلى يساره 10<6 < أكبر من يختبر إن كانت القيمة على يمين العامل أكبر تمامًا من القيمة إلى يساره 6>10 => أصغر من أو يساوي يختبر إن كانت القيمة على يمين العامل أصغر أو تساوي القيمة إلى يساره 3=>2 => أكبر من أو يساوي يختبر إن كانت القيمة على يمين العامل أكبر أو تساوي من القيمة إلى يساره 3=<2 ملاحظة: قد ترى أن البعض يستخدم في اختبارات التساوي == أو عدم التساوي =!، وهي عوامل صحيحة في جافا سكريبت لكنها مختلفة عن ===/==!. فالأولى تختبر إن كانت القيم متساوية لكنها لا تختبر إن كانت القيم من نفس النوع. أما النسخة الثانية اﻷكثر صرامة فتختبر كلًا من القيمة ونوعها وينتج عنها عادة أخطاء أقل لهذا ننصح باستخدامها. إن حاولت تجريب بعض القيم السابقة في الطرفية سيكون الناتج إما true أو false وهي القيم المنطقية booleans التي ذكرناها في المقال السابق. ولهذه القيم الكثير من الفوائد فهي تسمح باتخاذ القرارات في شيفرتنا، إذ تُستخدم في كل مرة تحتاج فيها إلى اتخاذ قرار ما. ويمكن استخدام القيم المنطقي على سبيل المثال في: إظهار العنوان المناسب على زر وذلك إن كانت ميزة ما مفعّلة أو معطّلة. إظهار رسالة "انتهت اللعبة" عندما تنتهي أو رسالة فوز إن ربح اللاعب. عرض التحية المناسبة وفقًا لموسم العطلات. تقريب الخريطة وفقًا لمستوى التقريب الذي تختاره. سنرى لاحقًا كيفية كتابة منطق الاستخدامات السابقة عند المرور على العبارات الشرطية لاحقًا، لكن لا بد أن نلق نظرة اﻵن على مثال سريع: <button>Start machine</button> <p>The machine is stopped.</p> const btn = document.querySelector("button"); const txt = document.querySelector("p"); btn.addEventListener("click", updateBtn); function updateBtn() { if (btn.textContent === "Start machine") { btn.textContent = "Stop machine"; txt.textContent = "The machine has started!"; } else { btn.textContent = "Start machine"; txt.textContent = "The machine is stopped."; } } لاحظ كيف استُخدم عامل المساواة ضمن الدالة ()updateBtn. لا نختبر في هذه الحالة تطابق قيمتي تعبيرين رياضيين، بل نختبر إذا ما كان محتوى عنوان زر يتضمن نصًا محددًا، لكن المبدأ يبقى ذاته. فإن كان عنوان الزر "Start machine" عند النقر عليه سنغيره إلى "Stop machine" ومن ثم نحدّث العنوان أسفله بما يناسب. بينما إن كان عنوان الزر "Stop machine" عند النقر عليه نستبدله مجددًا. ملاحظة: يُشار عادة إلى عملية التبديل تلك باسم "الانتقال toggle". الخلاصة غطينا في هذا المقال الأساسيات التي تحتاجها للتعامل مع الأعداد في جافا سكريبت. ولأنك ستتعامل مع الأعداد كثيرًا جدًا في رحلة تعلم جافا سكريبت، لا بد أن تتعلم هذه الأساسيات على بساطتها وتتقنها بشطكل جيد قبل المُضيّ قدمًا في رحلة تعلم واحتراف لغة جافا سكريبت. ترجمة -وبتصرف- لمقال Basic math in JavaScript-Numbers and Operators اقرأ أيضًا المقال السابق: المتغيرات وتخزين البيانات في جافا سكريبت تجربتك اﻷولى مع جافا سكريبت تعرّف على أساسيات لغة جافا سكريبت من منظور عام تعلم لغة جافا سكريبت من الصفر حتى الاحتراف
-
استُخدمت الخاصية float بشكل أساسي لتعويم الصور ضمن كتل نصية، لكن شاع استخدامها كثيرًا في إنشاء تخطيطات متعددة الأعمدة لصفحات ويب. لكن مع ظهور الخاصيتين flex و Grid عاد استخدام تلك الخاصية إلى الغاية الأساسية منه كما سنوضح في هذا المقال: عليك قبل البدء في قراءة هذا المقال أن: تطلع على أساسيات HTML كما شرحناها في سلسلة المقالات مدخل إلى HTML. تتفهم أساسيات عمل CSS. فكرة عن العناصر المعوّمة وضعت الخاصية كي تسمح لمطوري الويب إنجاز تخطيطات تتضمن صورًا تعوم داخل عمود نصي، يلتف فيه النص إلى يمين أو يسار هذه الصورة، كما هو الحال في أعمدة الصحف. لكن سرعان ما أدرك مطوري الويب إمكانية تعويم أية عناصر وليس الصور فقط، لهذا توسّع استخدام هذه الخاصية لتشمل تأثيرات جمالية على التخطيط مثل كتابة أحرف استهلالية كبيرة. لقد شاع استخدام التعويم لإنشاء تخطيطات كاملة لمواقع الويب تعرض محتوى موزعًا على عدة أعمدة محاذية لبعضها (تظهر الأعمدة افتراضيًا تحت بعضها بنفس ترتيب ظهورها في الشيفرة المصدرية). لكن ظهرت حديثًا تقنيات أفضل لتخطيط الصفحات واعتبر استخدام التعويم لإنشاء هذا النوع من التخطيطات قديمًا. سنركز في مقالنا على الاستخدام الأنسب للعناصر العائمة. مثال عن التعويم نكتشف في هذا المقال استخدامات التعويم وسنبدأ بكتلة نصية عائمة حول عنصر. بإمكانك العمل على هذا المثال بإنشاء ملف يحمل الاسم index.html على جهازك ومن ثم وضع قالب HTML الخاص بمثالنا ضمنه، وبعدها ضع الشيفرة التالية في مكان مناسب ضمن الملف. ستجد في نهاية هذه الفقرة مثالًا حيًا عما تفعله الشيفرة. سنبدأ أولًا بشيفرة HTML، لذلك أضف الشيفرة التالية ضمن العنصر <body> بعد إزالة أية شيفرة موجودة ضمنه: <h1>Float example</h1> <div class="box">Float</div> <p> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla luctus aliquam dolor, eu lacinia lorem placerat vulputate. Duis felis orci, pulvinar id metus ut, rutrum luctus orci. Cras porttitor imperdiet nunc, at ultricies tellus laoreet sit amet. </p> <p> Sed auctor cursus massa at porta. Integer ligula ipsum, tristique sit amet orci vel, viverra egestas ligula. Curabitur vehicula tellus neque, ac ornare ex malesuada et. In vitae convallis lacus. Aliquam erat volutpat. Suspendisse ac imperdiet turpis. Aenean finibus sollicitudin eros pharetra congue. Duis ornare egestas augue ut luctus. Proin blandit quam nec lacus varius commodo et a urna. Ut id ornare felis, eget fermentum sapien. </p> <p> Nam vulputate diam nec tempor bibendum. Donec luctus augue eget malesuada ultrices. Phasellus turpis est, posuere sit amet dapibus ut, facilisis sed est. Nam id risus quis ante semper consectetur eget aliquam lorem. Vivamus tristique elit dolor, sed pretium metus suscipit vel. Mauris ultricies lectus sed lobortis finibus. Vivamus eu urna eget velit cursus viverra quis vestibulum sem. Aliquam tincidunt eget purus in interdum. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. </p> طبّق الآن شيفرة CSS التالية بوضعها ضمن العنصر <style> أو وضعها ضمن ملف css. واربطها بملف HTML من خلال العنصر <link> على النحو التالي: body { width: 90%; max-width: 900px; margin: 0 auto; font: 0.9em/1.2 Arial, Helvetica, sans-serif; } .box { width: 150px; height: 100px; border-radius: 5px; background-color: rgb(207, 232, 220); padding: 1em; } عندما تحفظ التغييرات التي أجريتها على الملفات ثم تعيد تحميل الصفحة، سترى نتيجة مطابقة لما هو متوقع، فالصندوق فوق النص مباشرة وهذا هو الأمر الطبيعي في تخطيط الانسياب الاعتيادي. تعويم الصندوق لتعويم الصندوق أضف الخاصيتين float و margin-right إلى القاعدة box.: .box { float: left; margin-right: 15px; width: 150px; height: 100px; border-radius: 5px; background-color: rgb(207, 232, 220); padding: 1em; } احفظ التغييرات التي أجريتها وأعد تحميل الصفحة وسترى النتيجة التالية: See the Pen float1 by Hsoub Academy (@HsoubAcademy) on CodePen. لنفكر قليلًا كيف تعمل فكرة التعويم. يأخذ العنصر الذي يحمل الخاصية float خارج الانسياب الاعتيادي (العنصر <div> في مثالنا) ويوضع في الناحية اليُسرى من الحاوية (العنصر <body> في حالتنا). ويحيط المحتوى الذي كان في تخطيط الانسياب الاعتيادي تحت العنصر العائم بهذا العنصر شاغلًا المساحة التي تقع على يمينه ابتداءً من أعلى نقطة من نقاط العنصر العائم. إن تعويم العنصر إلى اليمين له التأثير نفسه تمامًا، لكن بطريقة معكوسة، فسيتوضع العنصر العائم إلى يمين العنصر الحاوي ويلتف حوله المحتوى شاغلًا المساحة الواقعة إلى يساره. حاول أن تغيّر قيمة الخاصية float إلى right واستبدل الخاصية margin-right بالخاصية margin-left في آخر القاعدة السابقة وراقب ما الذي سيحدث إظهار التعويم بإمكانك أن تضيف الهوامش إلى العنصر العائم كي يدفع النص بعيدًا عنه قليلًا، لكن لا يمكنك إضافة هوامش إلى النص كي يبتعد عن العنصر العائم. يعود السبب في ذلك أن العنصر العائم يخرج من تخطيط الانسياب الاعتيادي وستُعرض العناصر التالية له خلفه مباشرة. لإظهار الأمر سنغيّر قليلًا في الشيفرة السابقة بإضافة صنف جديد special إلى الفقرة النصية الأولى من النص التي تأتي بعد الصندوق العائم مباشرة. أضف الشيفرة التالية إلى شيفرة CSS والتي تمنح الفقرة النصية المستهدفة لونًا للخلفية: .special { background-color: rgb(79, 185, 227); padding: 10px; color: #fff; } ولكي يبدو التأثير المطلوب أكثر وضوحًا، غير الخاصية margin-right للعنصر العائم لتصبح margin وبالتالي ستحصل على نفس الهوامش لكن من الجوانب الأربعة للعنصر. سترى أن خلفية الفقرة النصية تمتد خلف العنصر العائم كالتالي: See the Pen float2 by Hsoub Academy (@HsoubAcademy) on CodePen. اختُزلت مساحة صندوق النص للعنصر الذي يلي العنصر العائم حتى يلتف النص حوله، لكن صندوق العنصر ككل يحافظ على اتساعه كاملًا لأن العنصر العائم قد انتزع من الانسياب الاعتيادي. إخلاء محيط العناصر العائمة رأينا كيف يُنتزع العنصر العائم خارج تخطيط الانسياب الاعتيادي، وتُعرض العناصر التالية إلى جانبه. لكن إن أردنا تعويم عنصر دون أن يرتفع محتوى العنصر التالي إلى الأعلى، لا بد من إخلاء محيط العنصر العائم باستخدام الخاصية clear. أضف الصنف cleared إلى الفقرة النصية الثانية في ملف HTML السابق وضع ضمنه التصريح التالي: .cleared { clear: left; } See the Pen float3 by Hsoub Academy (@HsoubAcademy) on CodePen. سترى أن الفقرة النصية الثانية قد انتقلت إلى أسفل العنصر العائم مخلّية المساحة إلى يمينه. تقبل الخاصية القيم التالية: left: إخلاء محيط العنصر العائم إلى اليسار. right: إخلاء محيط العنصر العائم إلى اليمين. both: إخلاء محيط العنصر العائم سواءً إلى اليمين أو اليسار. إخلاء الصناديق الملتفة حول عنصر عائم تعلمنا في الفقرة السابقة كيفية إخلاء العناصر التي تلي العنصر العائم، لكن ما الذي سيحدث عندما يكون لدينا عنصر عائم طويل وفقرة نصية قصيرة وكلاهما ضمن نفس الصندوق؟ توضيح المشكلة غير ملف HTML كي يصبح العنصر العائم والفقرة النصية الأولى ضمن حاوية <div> واحدة تمتلك الصنف wrapper: <div class="wrapper"> <div class="box">Float</div> <p> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla luctus aliquam dolor, eu lacinia lorem placerat vulputate. Duis felis orci, pulvinar id metus ut, rutrum luctus orci. Cras porttitor imperdiet nunc, at ultricies tellus laoreet sit amet. </p> </div> ضع القواعد التالية ضمن الصنف wrapper. ثم أعد تحميل الصفحة: .wrapper { background-color: rgb(79, 185, 227); padding: 10px; color: #fff; } أزل أيضًا الصنف cleared.: .cleared { clear: left; } لاحظ كيف عدنا إلى الحالة التي وضعنا فيها خلفية للفقرة النصية الأولى، إذ يمتد الصندوق خلف العنصر العائم: See the Pen float4 by Hsoub Academy (@HsoubAcademy) on CodePen. نذكر مجددًا أن هذا قد حدث نظرًا لانتزاع العنصر من انسيابه الاعتيادي. قد تتوقع أنك بتغليف العنصر العائم والفقرة النصية في حاوية واحدة سيُخلي العنصر التالي (الفقرة الثانية) المساحة حول العنصر العائم تلقائيًا، لكن هذا لم يحدث. وللتعامل مع الأمر عليك استخدام الأسلوب المعياري المُسمى سياق تنسيق الكتل block formatting context واختصارًا (BFC) بالاعتماد على الخاصية display. القاعدة display:flow-root تُستخدم هذه القاعدة فقط لإنشاء سياق لتنسيق الكتل دون أية مشاكل أو تبعات. لإنجاز الأمر، أزل القاعدة overflow: auto من الصنف wrapper. ثم أضف القاعدة display: flow-root وسترى كيف يخلي العنصر التالي المساحة إلى جوار الصندوق العائم: .wrapper { background-color: rgb(79, 185, 227); padding: 10px; color: #fff; display: flow-root; } See the Pen float6 by Hsoub Academy (@HsoubAcademy) on CodePen. خلاصة تعلمنا في هذا المقال كل ما يلزم معرفته حول تعويم العناصر في تصميمات الويب الحديثة، وسنتابع في مقال لاحق استخدامات قديمة أخرى من المهم أن تطلع عليها إن اضطررت للعمل على مشاريع قديمة. ترجمة -وبتصرف- للمقال: Floats اقرأ أيضًا المقال السابق: تخطيط صفحات ويب باستخدام تخطيط الشبكة Grid في CSS مدخل إلى تخطيط صفحات الويب باستخدام CSS تعرف على أساسيات CSS تقنيات كتابة شيفرات CSS احترافية وسهلة الصيانة التحكم في تموضع العناصر في CSS
-
ربما كوّنت فكرة بعد قراءتك للمقالات السابقة حول أساسيات جافا سكريبت عما يمكن لهذه اللغة فعله، وكيفية استعمالها مع بقية تقنيات الويب، وكيف تبدو ميزاتها من منظور عام. لهذا نحاول في هذا المقال الاقتراب قليلًا من اﻷساسيات ونتعلم المزيد حول العمل مع المتغيرات وهي الكتل البرمجية اﻷبسط في جافا سكريبت. ننصحك قبل المتابعة في قراءة هذا المقال بالاطلاع على بعض المقالات السابقة مثل: أساسيات علوم الحاسوب. أساسيات HTML. أساسيات عمل CSS. الأدوات التي تحتاجها سنطلب إليك مع تقدم مقالنا كتابة بعض أسطر الشيفرة لنختر فهمك لما شرحناه. فإن كنت تستخدم متصفح حاسوب مكتبي، ستجد أن أفضل مكان لكتابة الشيفرة هو طرفية جافا سكريبت في المتصفح Web Console التي تمكّنك من التفاعل مع صفحة الويب عن طريق تنفيذ تعبيرات جافا سكريبت في سياق الصفحة (اضغط اﻷزرار "Ctrl" + "Shift" + "K" معًا لفتحها في متصفح فايرفوكس). المتغيّرات في جافا سكريبت يُعرّف المتغير بأنه حاوية تضم قيمة قد تكون عددًا يمكن أن نستخدمه لاحقًا في عملية جمع، أو سلسلة نصية يمكن أن نستخدمها كجزء من جملة. أمثلة عن المتغيرات لنلق نظرة على هذا المثال البسيط: <button id="button_A">Press me</button> <h3 id="heading_A"></h3> const buttonA = document.querySelector("#button_A"); const headingA = document.querySelector("#heading_A"); buttonA.onclick = () => { const name = prompt("What is your name?"); alert(`Hello ${name}, nice to see you!`); headingA.textContent = `Welcome ${name}`; }; See the Pen js-variables 1 by Hsoub Academy (@HsoubAcademy) on CodePen. عند النقر على الزر في هذا المثال سينفذ المتصفح بعض الشيفرة. إذ يعرض السطر اﻷول صندوقًا على الشاشة يطلب من المستخدم إدخال اسمه ومن ثم يخزّن الاسم كقيمة ضمن متغيّر. ويعرض السطر الثاني رسالة ترحيب تتضمن اسم المستخدم وقد أُخذ من المتغيّر في السطر السابق. أما السطر الثالث فيعرض الاسم على الصفحة. كيف سيكون الوضع دون متغيّرات؟ لكي نفهم الفائدة الكبيرة من استخدام المتغيرات، دعونا نفكّر بطريقة لكتابة شيفرة المثال السابق دون استعمال المتغيرات. سينتهي بنا اﻷمر إلى شيفرة من هذا القبيل: <button id="button_B">Press me</button> <h3 id="heading_B"></h3> const buttonB = document.querySelector("#button_B"); const headingB = document.querySelector("#heading_B"); buttonB.onclick = () => { alert(`Hello ${prompt("What is your name?")}, nice to see you!`); headingB.textContent = `Welcome ${prompt("What is your name?")}`; }; See the Pen js-variables 2 by Hsoub Academy (@HsoubAcademy) on CodePen. ربما لن تدرك جيدًا الصياغة التي استخدمناها حاليًا، لكن لا بد وأن تكون قد استوعبت الفكرة. فإن لم يكن لديك متغيرات، ستسأل المستخدم كل مرة عن اسمه إن احتجته في الشيفرة. إذًا من المنطقي استخدام المتغيرات، وستألفها مع تقدمك في تعلم جافا سكريبت. ومن المهم أن تعرف أنك قادر على تخزين أي شيء تقريبًا في المتغيرات وليس فقط النصوص والأرقام. فقد تضم المتغيرات بنى مركّبة من البيانات وحتى دالة بأكملها، وهذا ما ستتعلمه خلال تعلم لغة جافا سكريبت. ملاحظة: لقد أشرنا أن المتغيرات تضم قيمًا أي أن المتغيرات ليست قيمًا بحد ذاتها بل حاويات للقيم. يمكن أن تشبهها بعلبة من الكرتون تضع اﻷغراض بداخلها. التصريح عن المتغيرات لا بد من إنشاء المتغير قبل استخدامه، ندعو هذه العملية "تصريحًا عن المتغيّر". وكي نفعل ذلك في جافا سكريبت، نكتب الكلمة let يليها الاسم الذي تريده للمتغير كما يلي: let myName; let myAge; أنشأنا في الشيفرة السابقة متغيرين وأسميناهما myName و myAge. جرّب أن تكتب هذين السطرين في طرفية المتصفح ثم صرّح عن متغير أو أكثر وسمِّهما بالاسم الذي تريده. ملاحظة: يجب أن تنتهي جميع التعليمات في جافا سكريبت بفاصلة منقوطة (;)، فقد تعمل شيفرتك إن أهملتها سطرًا واحدًا لكنها لن تعمل إن حاولت كتابة عدة أسطر من الشيفرة دونها. لهذا تعود على استخدامها في نهاية كل تعليمة. بإمكانك اختبار وجود المتغيّر في بيئة التنفيذ أم لا بكتابة اسمه: myName; myAge; لا قيم حاليًا للمتغيرين السابقين، بل يمثلان حاويتان فارغتان. وعندما تضغط الزر "Enter" في الطرفية ستحصل على النتيجة undefined، لكن إن لم يكن المتغير موجودًا فستحصل على رسالة خطأ. جرّب أن تكتب: name; ملاحظة: لا تخلط بين متغيّر مصرّح عنه ولم تُسند له قيمة، ومتغير غير موجود أصلًا فهما أمران مختلفان تمامًا. فلو عدنا لمثال الصندوق، سيكون عدم وجود المتغير عدم وجود الصندوق، أما التصريح عن المتغير وعدم إسناد قيمة له فيعني أن الصندوق موجود لكنه فارغ. تهيئة المتغيّر حالما تصرح عن المتغير يمكنك تهيئته بإسناد قيمة له. نفّذ ذلك بكتابة اسم المتغيّر تليه إشارة المساواة (=) ثم القيمة التي تريدها. إليك مثالًا: myName = "Chris"; myAge = 37; جرّب أن تعود إلى طرفية المتصفح واكتب الشيفرة السابقة، ومن المفترض أن ترى بعدها كيف تعيد الطرفية القيمة التي أسندتها إلى المتغير. ونذكرّك بإمكانية عرض قيمة المتغير بمجرد كتابة اسمه في الطرفية. جرّب مجددًا الشيفرة التالية: myName; myAge; باﻹمكان أيضًا التصريح عن المتغير وتهيئته في نفس الوقت كالتالي: let myDog = "Rover"; وهذا ما ستفعله غالبًا لأنها طريقة أسرع. ملاحظة حول المتغيرات قد تصادف أيضًا طريقة مختلفة في التصريح عن المتغيرات وذلك باستخدام التعليمة var كما يلي: var myName; var myAge; لقد كانت هذه الطريقة هي الطريقة الوحيدة للتصريح عن المتغيرات في بدايات جافا سكريبت، وقد وجدت أنها طريقة مربكة أثناء الممارسة لهذا استبدلت لاحقًا بالتعليمة letفي النسخ الأحدث من جافا سكريبت، وهي تعليمة ﻹنشاء المتغيرات بشكل مختلف نوعًا ما عن var وتحل بعض المشاكل التي نتجت عنها. سنشرح تاليًا بعض نقاط الاختلاف بين التعليمتين لكننا لن نخوض بها جميعًا في الوقت الراهن، بل ستكتشف ذلك أثناء تقدمك في تعلم جافا سكريبت. لو كتبنا برنامج جافا سكريبت مكوّن من عدة أسطر تصرّح وتهيئ متغيرًا، بإمكانك استخدام التعليمة var للتصريح عن المتغيّر حتى بعد تهيئته وسيعمل! إليك مثالًا: myName = "Chris"; //تصريح وتهيئة متحول function logName() { console.log(myName); } logName(); var myName; //التصريح عن نفس المتحول من جديد ملاحظة: لن يعمل هذا المثال إن كتبت اﻷسطر السابقة سطرًا سطرًا في الطرفية، بل فقط إن نفذتها معًا في صفحة ويب. تعمل الشيفرة السابقة بسبب عملية تقديم أو رفع الكائن hoisting، لكنها لن تنفع مع التعليمة let. جرّب تبديل var بالتعليمة let في الشيفرة السابقة وستخفق العملية مع رسالة خطأ، وهذا أمر جيد، لأن تصريح متغير بعد تهيئته سابقًا مربك كثيرًا ويولد شيفرة صعبة الفهم. ومن ناحية ثانية، يمكن التصريح عن المتحول نفسه عدة مرات باستخدام var لكنك لن تستطيع ذلك مع let.ستعمل مثلًا الشيفرة التالية: var myName = "Chris"; var myName = "Bob"; لكن الشيفرة التالية ستعطي خطأً في السطر الثاني: let myName = "Chris"; let myName = "Bob"; وعليك أن تعيد كتابة الشيفرة لتصبح بالشكل: let myName = "Chris"; myName = "Bob"; ونؤكد أن وجود letهو قرار لغوي معقول، فلا معنى لتعريف المتغيرات مرة أخرى لأنها تربك القارئ. لهذه اﻷسباب وغيرها ننصحك باستخدام let في الشيفرة إلا إن كنت ستكتب صراحة شيفرة تدعم المتصفحات القديمة لأن جميع المتصفحات الحديثة تدعمها منذ عام 2015. ملاحظة: إن كنت ستجرّب الشيفرة التالية ضمن طرفية المتصفح فانسخها والصقها ككتلة واحدة. وفي متصفح كروم هنالك ميزة تسمح لك بإعادة تصريح المتغيرات باستخدام let وconst. > let myName = "Chris"; let myName = "Bob"; // SyntaxError: Identifier 'myName' has already been declared إن أدخلت الشيفرة سطرًا سطرًا: ستحصل على الخطأ > let myName = "Chris"; > let myName = "Bob"; // As two inputs: both succeed إن أدخلتهما معًا ستنجح العملية تحديث متغيّر بمجرّد أن تهيئ المتغير يمكنك تغيير قيمته مجددًا (تحديثها) بإسناد قيمة أخرى له. جرّب إدخال الأسطر التالية في طرفية المتصفح: Name = "Bob"; myAge = 40; نظرة إلى قواعد تسمية المتغيرات يمكنك أن تسمي متغيرك بأي اسم تريد مع وجود بعض القيود. وعمومًا التزم باستخدام المحارف اللاتينية (0-9, a-z, A-Z) ومحرف الشرطة السفلية (_). لا تستخدم محارف أخرى لأنها قد تسبب أخطاءً أو تجعل الشيفرة صعبة الفهم للقارئين حول العالم. لا تستخدم الشرطة السفلية في بداية اسم المتغيّر لأنه أسلوب مستخدم في بعض بنى جافا سكريبت وله دلالة خاصة. لا تستخدم أرقامًا في بداية الاسم، فهذا غير مسموح ويسبب خطأ. من العادات الآمنة أن تلتزم بحرف صغير في بداية الاسم، وإن أردت ضم عدة كلمات لتعيين اسم المتغير اكتب أحرف الكلمة اﻷولى جمعها بأحرف صغيرة ثم اجعل بدايات الكلمات التالية بأحرف كبيرة. وهذا ما استخدمناه في مقالنا حتى اﻵن. اجعل أسماء المتغيرات واضحة وتصف البيانات التي تخزّنها، ولا تستخدم أحرفًا مفردة أو عبارات طويلة. انتبه إلى أن المتغيرات حساسة لحالة اﻷحرف فالمتغير myAge مختلف عن myage. نقطة أخيرة: تجنب استخدام كلمات جافا سكريبت المحجوزة (التعليمات) كأسماء متغيرات مثل var و function و letو for، فلن يميزها المتصفح كمتغيرات وستظهر اﻷخطاء مباشرة. ملاحظة: إليك قائمة بالكلمات المحجوزة التي لا ينبغي استخدامها كأسماء متغيرات. وإليك بعض اﻷمثلة عن التسمية الجيدة للمتغيرات: age myAge init initialColor finalOutputValue audio1 audio2 وأمثلة عن التسمية السيئة: 1 a _12 myage MYAGE var Document skjfndskjfnbdskjfb thisisareallylongvariablenameman جرّب أن تسمي بعض المتغيرات وفقًا للنقاط التي ناقشناها سابقًا. أنواع المتغيرات توجد عدة أنواع للبيانات التي يمكن أن نخزنها ضمن المتغيرات، وسنناقشها باختصار في هذا القسم ونتعمق بها في مقالات لاحقة. لهذا سنلقي نظرة على أول نوعين. اﻷعداد بإمكانك تخزين اﻷعداد ضمن المتغيرات كاﻷعداد الصحيحة مثل (30) أو الأعداد العشرية مثل (2.234) وتُدعى أيضًا (أعداد عائمة float أو أعداد ذات فاصلة عائمة). ولا حاجة للتصريح عن نوع المتغير في جافا سكريبت على عكس العديد من لغات البرمجة اﻷخرى. وعندما تُخزّن أعدادًا ضمن المتغير لا داعي لإحاطة العدد بعلامتي تنصيص. let myAge = 17; النصوص (السلاسل النصية) السلاسل النصية هي مقاطع من نصوص، وعندما تخزّنها ضمن متغيّر لا بد من إحاطتها بعلامتي تنصيص مفردتين '' أو مزدوجتين "". فإن لم تفعل ذلك، تحاول جافا سكريبت تفسيرها كأسماء لمتغيرات أخرى. let dolphinGoodbye = "So long and thanks for all the fish"; القيم المنطقية وهي إحدى القيمتين true أو false( صحيح أو خاطئ). وتستخدمان عادة في اختبار تحقق شرط ما، ثم تنفيذ شيفرة بناءً على نتيجة الشرط. إليك مثالًا: let iAmAlive = true; لكن ما ستراه في الواقع شيفرة كهذه: let test = 6 < 3; وقد استخدم العامل (<) لاختبار إن كان الرقم 6 أصغر من 3 ثم خُزنت النتيجة false في المتغيّر. سترى ذلك بتفاصيل أكثر لاحقًا. المصفوفات تُعرّف المصفوفة بأنها كائن في جافا سكريبت يضم عدة قيم تفصل بينها فاصلة , ضمن قوسين مرّبعين []. جرّب إدخال الأسطر التالية في الطرفية: let myNameArray = ["Chris", "Bob", "Jim"]; let myNumberArray = [10, 15, 40]; بمجرّد أن تعرّف المصفوفات، تستطيع الوصول إلى أي قيمة من القيم المخزنة ضمنها باﻹشارة إلى موقعها. جرّب ما يلي: myNameArray[0]; //'Chris' تعيد myNumberArray[2]; // 40 تعيد تحدد اﻷقواس المربعة بعد اسم المصفوفة ترتيب القيمة الموافق لموقعها في المصفوفة، وانتبه إلى أن المصفوفات في جافا سكريبت تبدأ من الصفر. الكائنات تمثل الكائنات في لغات البرمجة بنية من الشيفرة تنمذج شيئًا في الواقع. فقد تنمذج كائنًا بسيطًا مثل صندوق يتضمن معلومات مثل طوله وعرضه وارتفاعه، أو قد يمثل الكائن شخصًا ويتضمن اسمه وطوله ووزنه واللغات التي يتكلمها وكيف ستقول "مرحبًا" له وهكذا. جرّب الشيفرة التالية في الطرفية: let dog = { name: "Spot", breed: "Dalmatian" }; وكي تسترجع المعلومات المخزنة في كائن، يمكنك استخدام الصياغة التالية: dog.name; لن نشرح أكثر عن الكائنات هنا، بل سنترك اﻷمر لمقالات أخرى. التحديد التلقائي للنوع تُحدد جافا سكريبت النوع تلقائيًا dynamically typed language ويعني ذلك أنك لن تحتاج إلى تحديد نوع البيانات التي تخزنها (أعداد، سلاسل نصية، مصفوفات) في متغير على عكس لغات أخرى. فإن صرحت عن متغير وأسندت إليه قيمة ضمن إشارتي تنصيص سيفهم المتصفح أن المتغير هو سلسلة نصية. let myString = "Hello"; وحتى لو كان ما داخل إشارتي التنصيص مجرد أرقام سيبقى نوع المتغير نصيًا فانتبه إلى ذلك: let myNumber = "500"; // المتغير من النوع النصي هنا typeof myNumber; myNumber = 500; // اﻵن يصبح المتغير عددًا typeof myNumber; جرّب أن تدخل الأسطر اﻷربعة السابقة في الطرفية سطرًا تلو اﻵخر وراقب النتيجة. لاحظ أننا نستخدم عامل خاص هو typeofيعيد نوع المتغيّر الذي تذكره بعده. فعندما استُدعي لأول مرة أعاد القيمة string لأن المتغير myNumber حتى لحظة كتابته كان نصيًا، لكن عندما استدعيته في المرة الثانية إعادة القيمة number. الثوابت في جافا سكريبت يمكن التصريح عن الثوابت constants في جافا سكريبت كما تصرّح عن المتغيرات لكن باستخدام التعليمة const مع بعض الاستثناءات: عليك تهيئة الثابت عندما تعرّفه. لا يمكن أن تسند لها قيمة أخرى بعد تهيئتها. إذ يمكنك مثلًا التصريح عن المتغير باستخدام letدون أن تهيئه: let count; لكن إن حاولت أن تفعل ذلك باستخدام const سترى رسالة خطأ: const count; يمكنك بعد التصريح عن متغير باستخدام let أن تهيئه في خطوة منفصلة (تُدعى بعملية إعادة اﻹسناد re-assigning): let count = 1; count = 2; لكن إن حاولت ذلك عند استخدام const سترى رسالة خطأ أيضًا: const count = 1; count = 2; وعلى الرغم من أن الثوابت في جافا سكريبت تشير دائمًا إلى نفس القيمة إلا أنه يمكن تغيير محتوى هذه الثوابت في بعض اﻷنواع مثل الكائنات. إليك مثالًا: const bird = { species: "Kestrel" }; console.log(bird.species); // "Kestrel" بإمكانك تحديث أو إضافة أو إزالة خاصيات من كائن حتى لو كان مصرّحًا عنه باستخدام const، فحتى لو تغير المحتوى في هذه الحالة، فسوف يشير الثابت دائمًا إلى نفس الكائن. bird.species = "Striated Caracara"; console.log(bird.species); // "Striated Caracara" متى نستخدم الثوابت والمتغيرات؟ لماذا نستخدم const إن لم تكن تفعل الكثير موازنة بالتعليمة let؟ لأن الواقع يقول أنها ذات فائدة كبيرة. فلو اطلع أي قارئ على شيفرة عُرّف فيها ثابت باستخدام const سيعرف بالتأكيد أن هذه القيمة لن تتغير لاحقًا وسيرتبط اسم هذا الثابت بقيمة ثابتة دائمًا. نتبنى في سلسلة مقالاتنا القاعدة التالية بخصوص let و const وهي كالتالي: استخدم const عندما تستطيع و let عندما يجب عليك ذلك. أي إن كنت تستطيع إسناد قيمة للمتغير عند التصريح عنه ولن تحتاج لتغييرها فصرّح عنه كثابت وإلا صرّح عنه كمتغير. الخلاصة تعرفنا في هذا المقال على المتغيرات والثوابت في لغة جافا سكريبت وأصبحت تمتلك معرفة لا بأس بها حول إنشائها واستخدامها، وسنكمل في مقالات أخرى تفصيل أنواع هذه المتغيرات وطرق استخدامها والتعامل معها. ترجمة -وبتصرف- لمقال Storing the information you need-Variables اقرأ أيضًا المقال السابق: الدوال وإعادة استخدام الشيفرة في جافا سكريبت تجربتك اﻷولى مع جافا سكريبت تعلم لغة جافا سكريبت من الصفر حتى الاحتراف أساسيات لغة جافاسكربت
-
بعد أن أنشأنا في المقال السابق ملفات مشروع لعبة "تفادي الزواحف" ونظمناه، سنبدأ في هذا المقال بالعمل على شخصيات اللعبة (لاعب أساسي وأعداء). إذ سنبني المشهد اﻷول Player (وهو كائن أو عقدة) للاعب وآخر Mob للأعداء، ومن ميزات إنشاء مشهد مستقل لكل منهما هو إمكانية اختبارها بشكل مستقل وقبل أن ننشئ بقية أجزاء اللعبة. هيكلية العقدة نحتاج بداية إلى عقدة جذرية للاعب، وكقاعدة عامة، لا بد أن تعكس العقدة الجذرية للمشهد الوظيفة المرغوبة للكائن وما هو هذا الكائن. لهذا، انقر على زر "عقدة أخرى" وأضف عقدة من نوع Area2D إلى المشهد: سيعرض جودو أيقونة تنبيه إلى جوار العقدة في شجرة المشاهد، تجاهلها اﻵن وسنعود إليها لاحقًا. نتمكن باستخدام Area2D من استشعار الكائنات التي تتداخل أو تعمل ضمن اللاعب، وسنغير اسم العقدة الجديدة إلى Player بالنقر المضاعف عليها. وبعد إنشاء العقدة الجذرية، سنضيف عقدًا إضافية لمنحها مقدرات وظيفية أكبر. لكن قبل ذلك، علينا أن نتأكد من عدم تحريك أو تغيير العقد اﻷبناء عند النقر عليهم. لهذا انقر على اﻷيقونة الواقعة على يسار أيقونة القفل في شريط أدوات المشهد (سيعرض لك وصف اﻷداة عند تمرير مؤشر الفأرة فوقها العبارة "اجعل فروع العقدة المختارة غير قابلة للاختيار"): احفظ المشهد بالنقر على مشهد>حفظ أو اضغط Ctrl + S في ويندوز ولينكس أو Cmd + S في ماك أو إس. سوف نستخدم في مشروعنا أسلوب التسمية المتبع في محرك الألعاب جودو وهو كالتالي حسب لغة البرمجة المستخدمة: في لغة GDScript: نتبع أسلوب باسكال في اﻷصناف (الحرف اﻷول من كل كلمة كبير)، وفي الدوال والمتغيرات أسلوب اﻷفعى (تفصل بين كل كلمتين شرطة سفلية _)، أما الثوابت فتكتب كل حروفها بالشكل الكبير. في لغة #C: تسمى الأصناف والمتغيرات والتوابع بأسلوب باسكال، ونستخدم في تعريف الحقول الخاصة private والمتغيرات المحلية والمعاملات أسلوب سنام الجمل (الحرف اﻷول من كل كلمة كبير ما عدا الكلمة اﻷولى). وتأكد من كتابة أسماء التوابع بشكل دقيق عند ربط اﻹشارات. الرسم المتحرك للشخصية (Sprite) انقر على أيقونة العقدة Player وأضف عقدة ابن من نوع AnimatedSprite2D (استخدم Ctrl + A في ويندوز ولينكس) والتي تتولى أمور مظهر وتحريك اللاعب، ولاحظ وجود إشارة تحذير إلى جانب العقدة. تحتاج العقدة موردًا يُدعى "إطارات السبرايت SpriteFrames"، وﻹنشائه، ابحث عن الخاصية SpriteFrames ضمن النافذة الفرعية Animation في حاوية "الفاحص" ثم انقر على مربع النص empty واختر "جديدة SpriteFrame". انقر مجددًا لفتح لوحة "إطارات-اﻷرسومة". ستجد إلى اليمين قائمة بالرسومات، انقر على الافتراضية وسمها "walk"، ثم انقر على أيقونة إضافة إطار في الزاوية العليا اليمينية وأضف إطارًا آخر سمِّه "up". ابحث بعد ذلك عن الصور المناسبة في المجلد "art" في نظام الملفات وانقل الصور playerGrey_walk[1/2] إلى اﻹطار "walk" بالسحب واﻹفلات، أو بفتح الصورة من خلال أيقونة المجلد وكرر العملية بنقل الصورتين playerGrey_up[1/2] إلى اﻹطار "up". إن أبعاد الصور أكبر من أبعاد نافذة اللعبة، ولا بد من تصغير هذه الصور بالنقر على العقدة AnimatedSprite2D ومن ثم ضبط الخاصية Scale على القيمة Scale. ستجد هذه الخاصية في حاوية الفاحص تحت العنوان Node2D والقائمة "Transform تحويل": أضف أخيرًا عقدة من النوع CollisionShape2D لتكون ابنًا للعقدة Player، وتحدد هذه العقدة "صندوق التصادم" المحيط باللاعب أو حدود منطقة التصادم المحيطة به. وتلائمنا في هذا الصدد كائن من النوع CapsuleShape2D، لهذا انقر في "الفاحص" على المربع إلى جوار العنوان واختر "جديدة CapsuleShape2D". استخدم بعد ذلك مقبضي التحكم بالأبعاد (النقطتين الحمراوين) في نافذة المشهد لتغطية الأرسومة بالغلاف: عندما تنتهي من ذلك سيكون شكل مشهد اللاعب Player كالتالي: تأكد من حفظ المشهد مجددًا بعد هذه التغييرات. سنضيف تاليًا سكربت إلى عقدة اللاعب لتحريكه ثم نُعدّ آلية لترصد التصادم لنعرف إذا ما اصطدم اللاعب بشيء ما. كتابة الشيفرة اللازمة لتحريك اللاعب سنعمل في هذا القسم على كتابة شيفرة لتحريك اللاعب، وإعداده ليترصد التصدامات، لهذا، علينا إضافة بعض الخواص الوظيفية التي لا تقدمها العقد المتوفرة عن طريق إضافة سكربت أو كود برمجي إلى العقدة. انقر على العقدة Player ثم انقر على "إلحاق نص برمجي": لا داعي لتغيير أي شيئ في نافذة إلحاق نص برمجي، اترك كل شيء كما هو وانقر على زر "أنشئ". ملاحظة: إن كنت تريد إنشاء سكربت #C، اختر هذه اللغة من القائمة المنسدلة قبل النقر على "أنشئ". extends Area2D @export var speed = 400 # How fast the player will move (pixels/sec). var screen_size # Size of the game window. تُسمح لنا التعليمة export قبل المتغير speed بضبط قيمته في نافذة الفاحص. ولهذا اﻷمر فائدته إن أردت تعديل قيمة المتغير بالطريقة نفسها التي تعدّل فيها خاصيات أي عقدة موجودة أصلًا في جودو. انقر اﻵن على العقدة Player وسترى الخاصية موجودة ضمن قسم "متغيرات السكربت" في حاوية الفاحص (تحت نفس الاسم الذي يحمله ملف السكربت). وتذكر أن تغيير القيمة في هذا المكان سيلغي القيمة التي يحملها المتغير في السكربت. يتضمن السكربت player.gd تلقائيًا الدالتين ()ready_ و ()process_. فإن لم تختر القالب الافتراضي للسكربت أنشئ هاتين الدالتين. وتُستدعى الدالة ()ready_ عندما تدخل عقدة شجرة المشاهد وهو وقت مناسب لمعرفة أبعاد نافذة اللعبة func _ready(): screen_size = get_viewport_rect().size بإمكاننا اﻵن استخدام الدالة ()process_ لتحديد ما يفعله اللاعب، وتُستدعى هذه الدالة من أجل كل إطار ونستخدمها لتحديث العناصر في لعبتنا والتي نتوقع أن تتغير أحيانًا. فمن أجل لاعبنا لا بد من: التحقق من وجود دخل. تحريكه في الاتجاه المطلوب. تشغيل الرسوم المتحركة المناسبة. كما ذكرنا علينا بداية التحقق من الدخل، أي هل يضغط اللاعب على زر معين مثلًا؟ ففي لعبتنا هناك عناصر إدخال لأربعة اتجاهات علينا أن نتحقق منها. عُرّفت إجراءات الدخل في إعدادات المشروع تحت عنوان "خريطة الإدخال". وفيها نستطيع تعريف أحداث مخصصة وتعيين أزرار مختلفة، وأحداث تتعلق بالفأرة وغيرها من المدخلات. انقر على المشروع، ثم إعدادات المشروع لتفتح نافذة اﻹعدادات، ثم انقر على النافذة الفرعية "خريطة الإدخال" في الأعلى. اكتب بعد ذلك "move_right" (تحرك يمينًا) في الشريط العلوي وانقر الزر "أضف" ﻹضافة الإجراء move_right. علينا اﻵن أن نربط اﻹجراء بزر معين، لهذا انقر على أيقونة "+" إلى اليسار كي نفتح نافذة "تهيئة الحدث event configuration". كل ما عليك الآن هو النقر على الزر الذي تريد ربطه بالحدث سواء زر لوحة مفاتيح أو زر الفأرة. انقر الآن مفتاح السهم اليميني على لوحة المفاتيح وسيظهر الخيار تلقائيًا في مربع "يتم رصد المدخلات.." انقر بعد ذلك على "حسنًا" لتعيين المفتاح. كرر نفس الخطوات لربط الحركات الثلاث الباقية كالتالي: اربط move_left بالسهم اليساري. اربط move_up بالسهم للأعلى. اربط move_down بالسهم للأسفل. يجب أن تظهر خارطة المدخلات كالتالي: انقر اﻵن على "إغلاق" ﻹغلاق إعدادات المشروع. ملاحظة: ربطنا مفتاح واحد بكل إجراء دخل، لكنك تستطيع أن تربط أكثر من مفتاح أو زر عصا تحكم أو زر فأرة بإجراء الدخل نفسه. تستطيع أن تقرر إذا ما ضُغط زر باستخدام العبارة ()Input.is_action_pressed التي تعيد القيمة true إذا ضُغط الزر وfalse إن لم يُضغط. func _process(delta): var velocity = Vector2.ZERO # The player's movement vector. if Input.is_action_pressed("move_right"): velocity.x += 1 if Input.is_action_pressed("move_left"): velocity.x -= 1 if Input.is_action_pressed("move_down"): velocity.y += 1 if Input.is_action_pressed("move_up"): velocity.y -= 1 if velocity.length() > 0: velocity = velocity.normalized() * speed $AnimatedSprite2D.play() else: $AnimatedSprite2D.stop() بدأنا بضبط قيمة velocity على (0, 0) فلن يتحرك اللاعب افتراضيًا، وبعد ذلك تحققنا من كل المدخلات وأضفنا إلى المتغير velocity أو طرحنا منه للحصول على الاتجاه. فلو ضغطنا على السهم اليميني واليساري في نفس الوقت ستكون نتيجة المتجه velocity هي (1, 1)، وفي هذه الحالة نكون قد أضفنا حركة أفقية وعمودية في نفس الوقت، وسيتحرك اللاعب بشكل أسرع بالاتجاه القطري موازنة بالحالة التي يتحرك فيها أفقيًا فقط. لكن بإمكاننا منع حدوث هذا اﻷمر بتسوية قيمة السرعة بأن نضبط قيمتها على 1 ثم نضربه بالقيمة المطلوبة ولن تكون السرعة في الاتجاه القطري عندها أكبر. كما علينا أن تحقق فيما لو تحرّك اللاعب كي نستدعي الدالتين ()play و ()stop في AnimatedSprite2D يُعيد $ عقدة معينة إن كانت موجودة في نفس المسار النسبي ويعيد null إن لم تكن موجودة في هذا المسار. وطالما أن AnimatedSprite2D هي عقدة ابن للعقدة الحالية، بإمكاننا استخدام AnimatedSprite2D$. وطالما حددنا الآن اتجاه الحركة، بإمكاننا تحديث موقع اللاعب. كما نستطيع باستخدام الدالة ()clamp منع اللاعب من مغادرة الشاشة وتقييده ضمن مجال محدد. أضف الشيفرة التالية إلى أسفل الدالة ()process_ (انتبه إلى أن الشيفرة غير منزاحة تحت else? position += velocity * delta position = position.clamp(Vector2.ZERO, screen_size) انقر على الزر "شغل المشهد الحالي" (F6 أو Cmd+R في ماك أو إس) وتأكد من قدرتك على تحريك اللاعب ضمن المشهد في جميع الاتجاهات. اختيار الرسوم المتحركة بإمكاننا تحريك اللاعب الآن، لكننا نحتاج إلى تغيير الرسم المتحرك الذي يمثّل الكائن وفقًا لاتجاهه. ليدنا الرسم "تحرّك" والذي يعرض اللاعب وهو يتحرك يمينًا، ولا بد من قلبه أفقيًا حتى يعبّر عن التحرك نحو اليسار باستخدام الخاصية flip_h. وكذلك لدينا الرسم "up" الذي يجب أن يُعكس عموديًا لتمثيل الحركة نحو اﻷسفل باستخدام الخاصية flip_v. لهذا عليك اضافة الشيفرة التالية إلى أسفل الدالة ()process_: if velocity.x != 0: $AnimatedSprite2D.animation = "walk" $AnimatedSprite2D.flip_v = false # اطلع على املاحظة التالية بخصوص اﻹسناد المنطقي $AnimatedSprite2D.flip_h = velocity.x < 0 elif velocity.y != 0: $AnimatedSprite2D.animation = "up" $AnimatedSprite2D.flip_v = velocity.y > 0 ملاحظة: يُعد استخدام طريقة اﻹسناد المنطقي في هذه الشيفرة اختصارًا شائعًا. فما نفعله هو اختبار موازنة (منطقي) وإسناد قيمة منطقية، لهذا يمكننا تنفيذ اﻷمرين معًا. وما يفعله هذا الاختصار مطابق لعمل الشيفرة التالية: if velocity.x < 0: $AnimatedSprite2D.flip_h = true else: $AnimatedSprite2D.flip_h = false شغّل المشهد وتأكد من تغيّر الرسم مع تغير اتجاه الحركة. عندما تتأكد أن كل شيء يعمل كما يجب، أضف السطر التالي إلى الدالة ()ready_ كي يختفي اللاعب في بداية اللعبة. hide() إعداد التصادمات نريد من اللاعب Player أن يعرف متى يستطدم بالعدو، لكننا لم نصنع اﻷعداء بعد! لا بأس بذلك لأننا سنستخدم حاليًا إشارات جودو لننجز اﻷمر. أضف اﻷسطر التالية إلى أعلى السكربت. فإن كنت تستخدم GDScript، أضفها بعد العبارة extends Area2D، وإن كنت تستخدم لغة #C ضعها بعد العبارة public partial class Player: Area2D. signal hit تُعرّف التعليمة السابقة إشارة خاصة باسم "hit" يبثها اللاعب (يرسلها) عندما يتصادم مع عدو. وسنستخدم الكائن Area2D لالتقاط هذه اﻹشارة. اختر العقدة Player وانقر على النافذة الفرعية "عقدة" ضمن لوحة "الفاحص" كي تعرض قائمة اﻹشارات التي يمكن للاعب بثها: لاحظ وجود إشارتنا المخصصة "hit" أيضًا ضمن تلك القائمة. وطالما أن العدو سيكون عقدة من النوع RigidBody2D، سنحتاج إلى الإشارة body_entered(body: Node2D). أوجد تلك اﻹشارة في القائمة ثم انقر عليها بالزر اليميني واختر "يتصل" لتظهر نافذة "قم بوصل اﻹشارة إلى دالة". لا حاجة لتغيير أي شيء، بل انقر فقط على "وصل" وسيوّلد جودو تلقائيًا الدالة المناسبة في الشيفرة: لاحظ اﻷيقونة الخضراء إلى يسار الشيفرة المخصصة للإشارة وتدل على أن إشارة متصلة مع هذه الدالة. أضف اﻵن الشيفرة التالية إلى الدالة: func _on_body_entered(body): hide() # يختفي اللاعب بعد أن يصطدم. hit.emit() # Must be deferred as we can't change physics properties on a physics callback. $CollisionShape2D.set_deferred("disabled", true) في كل مرة يصدم بها العدو اللاعب ستُرسل اﻹشارة، ولا بد من تعطيل التصادم الخاص باللاعب كي لا نفعّل اﻹشارة hit أكثر من مرة. ملاحظة: قد ينتج عن تعطيل غلاف التصادم الخاص بالمنطقة خطأ إن حدث اﻷمرأثناء معالجة المحرّك للتصادمات. لهذا استخدم الدالة ()set_deferred ﻹخبار المحرّك ألا يعطّل غلاف التصادم حتى يرى أن اﻷمر آمن. أضف أخيرًا دالة نستدعيها ﻹعادة ضبط اللاعب عندما تبدأ لعبة جديدة func start(pos): position = pos show() $CollisionShape2D.disabled = false إنشاء شخصية العدو حان الوقت اﻵن ﻹنشاء اﻷعداء الذي يجب على اللاعب تفاديهم. ولن يكون سلوكهم معقدًا جدًا بل سيتحركون عشوائيًا على أطراف الشاشة، يأخذون اتجاهًا عشوائيًا ويتحركون وفق خط مستقيم. نبدأ عملنا بإنشاء مشهد باسم Mob يشكل الأساس الذي نشتق منه أي عدد نحتاجه من هذه الكائنات في لعبتنا. إعداد العقدة انقر على مشهد>مشهد جديد ثم أضف العقد التالية وفق الترتيب المبين: RigidBody2D: AnimatedSprite2D CollisonShape2D VisibleOnScreenNotifier2D ولا تنسَ ضبط العقدة اﻷم كي لا يمكن اختيار اﻷبناء كما فعلنا سابقًا عند بناء شخصية اللاعب. اختر بعد ذلك العقدة Mob ثم اضبط قيمة الخاصية Gravity Scale على 0، وذلك في قسم RigidBody2D ضمن الفاحص. يمنع هذا اﻷمر الأعداء من السقوط للأسفل. افتح المجموعة "Collision" الموجودة في اللوحة "CollisionObject2D" تحت "RigidBody2D" ضمن الفاحص. الغ بعد ذلك تفعيل الخيار 1 ضمن الخاصية Mask بالنقر عليه كي لا تتصادم اﻷعداء فيما بينها. اضبط العقدة كما فعلنا في مشهد اللاعب، وهنا نستخدم ثلاث رسومات هي fly و swim و walk، وهنالك صورتان لكل مشهد في المجلد "art". تُضبط الخاصية Animation Speed (سرعة التحريك) لكل رسم متحرك على حدى، لهذا اضبط كلًا منها على 3: بإمكانك اﻵن النقر على الزر "تشغيل الرسم المتحرك" إلى يسار "سرعة التحريك" لعرض الرسوم المتحركة. سنختار إحدى هذه الرسوم عشوائيًا حتى يكون للأعداء أشكال مختلفة، وكما هو الحال مع رسومات اللاعب لا بد من تصغير هذه الرسومات، وذلك بضبط الخاصية Scale على (0.75, 0.75) (راجع فقرة إنشاء اللاعب لتتذكر كيفية العمل). علينا اﻵن أن نضيف غلاف CapsuleShape2D من أجل التصادمات كما فعلنا مع اللاعب. ولكي يتماشى الغلاف مع الرسم المتحرك لا بد من تدويره بضبط الخاصية Rotation Degrees على 90 (تحت لوحة "Node2D" والقائمة "Transform" ضمن الفاحص). كتابة شيفرة تحريك العدو أضف سكربت إلى العقدة Mob كما فعلنا سابقًا: extends RigidBody2D نشغّل باستخدام الدالة ()ready_ الرسومات ونختار عشوائيًا أحد اﻷنواع الثلاث لهذه الرسوميات كالتالي: func _ready(): var mob_types = $AnimatedSprite2D.sprite_frames.get_animation_names() $AnimatedSprite2D.play(mob_types[randi() % mob_types.size()]) ما تفعله هذه الشيفرة هو الحصول على أسماء الرسومات من الخاصية frames للعقدة AnimatedSprite2D، وستكون النتيجة مصفوفة تضم اﻷنواع الثلاث: ["walk", "swim", "fly"]. ثم نختار عشوائيًا رقمًا بين 0 و 2 لاختيار أحد اﻹطارات الثلاث من المصفوفة السابقة (يبدأ العدد في المصفوفات من 0) بتطبيق التعليمة randi() % n، والتي تختار عددًا صحيحًا عشوائيًا بين 0 و n-1. وأخيرًا نحتاج إلى شيفرة كي يحذف العدو نفسه عندما يغادر شاشة اللعبة. ولتنفيذ ذلك صل الإشارة ()Screen_exited العائدة للعقدة ()VisibleOnScreenNotifier إلى العقدة Mob (راجع فقرة وصل إشارة اللاعب التي نفّذناها سابقًا) ثم أضف الأمر ()queue_free إلى الدالة التي تظهر في السكربت كالتالي: func _on_visible_on_screen_notifier_2d_screen_exited(): queue_free() وهكذا سيكتمل مشهد العدو. الخلاصة بهذا نكون قد وصلنا لنهاية مقالنا الحالي الذي أنشأنها فيه مشهدين من مشاهد اللعبة ثنائية الأبعاد في محرك جودو، تابع معنا المقال التالي الذي سنقوم فيه بضم المشهدين معًا والسماح للأعداء بالتكاثر على الشاشة والحركة لتحويل المشاهد إلى لعبة تعمل كما خططنا لها. ترجمة -وبتصرف- للمقالات: Creating the Player scene و Coding the player و Creating the enemy اقرأ أيضًا المقال السابق: بناء لعبة ثنائية البعد عبر محرك الألعاب Godot - الجزء الأول: تجهيز الملفات وضبط الإعدادات كيف تحصل على أفكار ألعاب فيديو ناجحة تعرف على أشهر لغات برمجة الألعاب مدخل إلى محرك الألعاب جودو Godot
-
تُعد الدوال Functions من المفاهيم اﻷساسية في كتابة الشيفرة. إذ تسمح لك الدوال بتخزين عدة أسطر أو تعليمات تنفذ مهمة معينة ضمن كتلة معرّفة مسبقًا، وعندما تحتاج هذه الشيفرة في أي مكان تستدعي هذه الكتلة عبر تعليمة واحدة مختصرة، بدلًا من كتابة هذه الشيفرة عدة مرات. نتعرف إذًا في هذا المقال على المفاهيم الأساسية التي تعتمد عليها الدوال مثل الصياغة وطريقة التعريف والاستدعاء ومجال الرؤية والمعاملات. ننصحك قبل أن تبدأ العمل معنا في هذه السلسلة أن تطلع على: أساسيات علوم الحاسب. أساسيات HTML. أساسيات عمل CSS أساسيات جافا سكريبت كما شرحناها في سلسلة المقالات السابقة. أين تجد الدوال؟ ستجد الدوال أينما نظرت في جافا سكريبت، وقد استخدمنا في الواقع الدوال في جميع مقالاتنا السابقة، لكننا لم نتحدث عنها بالتفصيل. لهذا سنبدأ في هذا المقال حديثنا عن الدوال ونستكشف صياغتها. في كل مرة تستخدم بنية في جافا سكربت يليها قوسين () (باستثناء البنى الأصلية في اللغة مثل حلقة for أو حلقة while أو for...else) فأنت تستخدم دالة. الدوال اﻷصلية المدمجة في المتصفح استخدمنا سابقًا دوال كثيرة جاهزة في المتصفح، وذلك في كل مرة تعاملنا فيها مع السلاسل النصية: const myText = "I am a string"; const newString = myText.replace("string", "sausage"); console.log(newString); // سلسلة نصية مصدرية replace() تأخذ الدالة // وأخرى هدف وتستبدل السلسلة المصدرية //بالسلسلة الهدف وتعيد السلسلة النصية الجديدة أو في كل مرة تعاملنا فيها مع مصفوفات: const myArray = ["I", "love", "chocolate", "frogs"]; const madeAString = myArray.join(" "); console.log(madeAString); // مصفوفة وتضم جميع عناصرها join() تأخذ الدالة // في سلسلة نصية جديدة وتعيد هذه السلسلة أو عندما ولدنا أعدادًا عشوائية: const myNumber = Math.random(); // عددًا عشوائيًا بين random() تولد الدالة //الرقم 0 و الرقم 1 (ما عدا 1) ملاحظة: جرّب الشيفرة السابقة في متصفحك (في طرفية جافا سكريبت) كي تتعود على استخدامها إن اقتضى اﻷمر. تضم جافا سكريبت الكثير من الدوال الجاهزة المدمجة معها لتساعدك على إنجاز الكثير من المهام دون أن تفكر في كتابة شيفرتها بنفسك. وحقيقة اﻷمر أن الكثير من الدوال المدمجة التي تستخدمها لا يمكن كتابتها باستخدام جافا سكريبت، ويستدعى العديد منها شيفرة خلفية للمتصفح كتبت عمومًا بلغات منخفضة المستوى مثل ++C وليس باستخدام لغات الويب مثل جافا سكريبت. وتذكر دائمًا أن الكثير من الدوال المدمجة مع المتصفح ليست جزءًا من نواة جافا سكريبت. فبعضها معرّف كأجزاء من الواجهة البرمجية للمتصفح مبنية على لغات أخرى لتأمين مستوى معين من الوظائف. الدوال والتوابع تُدعى الدوال اﻷعضاء في كائن ما توابعًا methods. ولا حاجة بالطبع هنا إلى التعمق في عمل كائنات جافا سكريبت، لأننا سنتستعرضها لاحقًا في مقالات أخرى، وكل ما نريده هو إزالة الالتباس الذي قد يحصل بين الدوال والتوابع لأنك ستواجه كلا المصطلحين عندما تبحث في المصادر المختلفة على الويب. فالشيفرة المدمجة الجاهزة التي تعاملنا معها سابقًا تضم الدوال والتوابع، وبإمكانك الاطلاع على هذه الدوال الجاهزة والكائنات الجاهزة في جافا سكربت مع توابعها من خلال توثيق جافا سكريبت في موسوعة حسوب. كما رأيت أيضًا في مقالاتنا السابق العديد من الدوال المخصصة، وهي دوال عرفناها ضمن الشيفرة وليست مدمجة مع المتصفح. فعندما ترى اسمًا مخصصًا يليه قوسين ستكون أمام دالة مخصصة. وكمثال عليها تجد الدالة ()draw المخصصة التي استخدمناها ضمن الملف random-canvas-circles.html (انظر الشيفرة المصدرية) في مقال استخدام الحلقات في جافا سكريبت والذي يبدو كالتالي: function draw() { ctx.clearRect(0, 0, WIDTH, HEIGHT); for (let i = 0; i < 100; i++) { ctx.beginPath(); ctx.fillStyle = "rgba(255,0,0,0.5)"; ctx.arc(random(WIDTH), random(HEIGHT), random(50), 0, 2 * Math.PI); ctx.fill(); } } ترسم الدالة 100 دائرة عشوائية ضمن العنصر <canvas>، وفي كل مرة نريد تكرار اﻷمر، نستدعي هذه الدالة كالتالي: draw(); بدلًا من إعادة كتابة الشيفرة من جديد كل مرة. ويمكن أن تضم الدوال أية شيفرة تريدها كما يمكنها استدعاء أي دالة أخرى، فالدالة السابقة تستدعي الدالة ()random المعرّفة كالتالي ثلاث مرات: function random(number) { return Math.floor(Math.random() * number); } لقد احتجنا الدالة السابقة لأن الدالة اﻷصلية المدمجة ()Math.random مع المتصفح تولد أرقامًا عشوائية عشرية بين 0 و 1 فقط، لكننا نريد رقمًا عشوائيًا صحيحًا بين 0 وقيمة معينة. استدعاء الدالة قد يكون مفهوم الدالة واضحًا بالنسبة لك، لكن نذكرك أن استخدام الدالة فعليًا يكون من خلال استدعائها. ويُنفّذ اﻷمر بكتابة اسم الدالة في المكان الذي تريده ضمن الشيفرة يليه قوسين: function myFunction() { alert("hello"); } myFunction(); // يستدعي الدالة مرة واحدة ملاحظة: تُدعى هذه الطريقة في إنشاء الدوال "تصريحًا عن الدالة"، وبإمكانك استدعاء الدالة قبل أن تصرح عنها وستعمل الشيفرة جيدًا. معاملات الدالة تحتاج بعض الدوال إلى معاملات parameters عند استدعائها، وهي قيم ينبغي وضعها ضمن قوسي الدالة كي تعمل الدالة بالشكل المطلوب. ملاحظة: تُدعى المعاملات أحيانًا "وسائطًا arguments" أو "خاصيات properties" أو "سمات attributes". وكمثال على ذلك، نجد دالة المتصفح ()Math.random التي لا تأخذ أية معاملات وتعيد دومًا عددًا عشوائيًا بين 0 و1: const myNumber = Math.random(); بينما تأخذ الدالة ()replace معاملين هما النص الذي تريد إيجاده ضمن السلسلة الرئيسية والنص البديل: const myText = "I am a string"; const newString = myText.replace("string", "sausage"); ملاحظة: يُفصل بين المعاملات ما بين القوسين بفواصل من الشكل ,. المعاملات الاختيارية قد تكون المعاملات اختيارية في بعض اﻷحيان ولا حاجة لوضع قيم لها. فإن لم تفعل ذلك تتبنى الدالة نوعًا من القيم الافتراضية. وكمثال عن هذه الدوال نجد دالة المصفوفات ()join: const myArray = ["I", "love", "chocolate", "frogs"]; const madeAString = myArray.join(" "); console.log(madeAString); //'I love chocolate frogs' تعيد الدالة const madeAnotherString = myArray.join(); console.log(madeAnotherString); //'I,love,chocolate,frogs'تعيد الدالة فإن لم تخصص في الدالة المعامل الذي يمثل محرف الفصل أو الوصل، تستخدم الدالة الفاصلة افتراضيًا. المعاملات الافتراضية إن كنت في صدد إنشاء دالة وأردت أن تجعل لأحد المعاملات قيمة افتراضية، بإمكانك تخصيص هذه القيمة بإضافة اﻹشارة = بعد اسم المعامل تليها قيمة المعامل الافتراضية: function hello(name = "Chris") { console.log(`Hello ${name}!`); } hello("Ari"); // Hello Ari! hello(); // Hello Chris! الدوال غير المسماة والدوال السهمية ما تعلمناه حتى اﻵن هو دالة من الشكل: function myFunction() { alert("hello"); } لكن بإمكانك أيضًا إنشاء دالة بلا اسم: (function () { alert("hello"); }); تُدعى هذه الدوال بالدوال غير المسماة anonymous functions. وترى هذه الدوال عادة عندما تأخذ دالة ما دالة أخرى كمعامل لها، عندها يمرر المعامل كدالة غير مسماة. ملاحظة: يُدعى هذا الشكل من تعريف الدوال بالشكل التعبيري تمييزًا له عن الشكل التصريحي ولا يمكن استباق الدالة التعبيرية أي استخدامها في الشيفرة قبل أن تكتبها. مثال عن الدوال غير المسماة Anonymous Functions لنفرض أنك تريد تنفيذ شيفرة معينة عندما يطبع المستخدم بعض اﻷحرف في صندوق نصي. وﻹنجاز اﻷمر بإمكانك استدعاء الدالة ()addEventListener العائدة للصندوق النصي، والتي تتوقع أن تمرر لها على اﻷقل معاملين: اسم الحدث الذي تترصده وهو في حالتنا الضغط على المفتاح keydown. دالة كي تُنفَّذ عندما يقع الحدث. عندما يضغط المستخدم على مفتاح، يستدعي المتصفح الدالة التي استخدمتها كمعامل ويمرر لها على شكل معامل أيًضًا معلومات عن الحدث بما في ذلك المفتاح الي ضغطه المستخدم: function logKey(event) { console.log(`You pressed "${event.key}".`); } textBox.addEventListener("keydown", logKey); وبدلًا من استخدام دالة منفصلة مثل ()logkey، بإمكانك تمرير دالة غير مسماة إلى ()addEventListener على النحو التالي: textBox.addEventListener("keydown", function (event) { console.log(`You pressed "${event.key}".`); }); الدالة السهمية Arrow functions إن مررت دالة إلى دالة أخرى على شكل دالة غير مسماة، ستجد طريقة أخرى لذلك تُدعى الدالة السهمية <=() بدلًا من ()function: textBox.addEventListener("keydown", (event) => { console.log(`You pressed "${event.key}".`); }); فإن أخذت الدالة السهمية معاملًا واحدًا، تستطيع حينها حذف اﻷقواس المحيطة بالمعامل: textBox.addEventListener("keydown", event => { console.log(`You pressed "${event.key}".`); }); وأخيرًا إن احتوت الدالة على سطر واحد فقط يتضمن العبارة return، بإمكانك عندها حذف اﻷقواس المعقوصة للدالة والتعليمة return. لاحظ كيف استخدمنا تابع المصفوفة ()map لمضاعفة كل عدد في المصفوفة اﻷصلية: const originals = [1, 2, 3]; const doubled = originals.map(item => item * 2); console.log(doubled); // [2, 4, 6] يأخذ التابع ()map كل عنصر من عناصر المصفوفة بدوره ويمرر إلى دالة محددة ثم يعيد نتيجة تنفيذ هذه الدالة ويضيفها إلى المصفوفة الجديدة. فالعبارة البرمجية item => item * 2 إذًا هو شكل مختصر للدالة السهمية ويكافئ تمامًا الدالة التصريحية التالية: function doubleItem(item) { return item * 2; } بإمكانك استخدام اﻷسلوب السابق لإعادة كتابة الدالة addEventListener: textBox.addEventListener("keydown", (event) => console.log(`You pressed "${event.key}".`), ); تُعاد قيمة التابع ()console.log (والتي هي قيمة غير محددة undefined) ضمنًا كنتيجة لاستدعاء الدالة. ننصحك باستخدام الدوال السهمية لأنها تختصر الشيفرة وتسهّل قراءتها، بإمكانك العودة إلى توثيق جافا سكريبت في موسوعة حسوب للاطلاع اكثر على الدوال السهمية. مثال مباشر عن استخدام الدالة السهمية إلك مثالنا السابق عن التقاط ضغطة مفتاح: شيفرة HTML: <input id="textBox" type="text" /> <div id="output"></div> شيفرة جافا سكريبت: const textBox = document.querySelector("#textBox"); const output = document.querySelector("#output"); textBox.addEventListener("keydown", (event) => { output.textContent = `You pressed "${event.key}".`; }); إليك النتيجة: See the Pen functions by Hsoub Academy (@HsoubAcademy) on CodePen. نطاق الدوال والتعارضات عند تفسير الشيفرة لنلق نظرة أقرب إلى موضوع مجال أو نطاق دالة scope، وهو مفهوم هام جدًا عند التعامل مع الدوال. فعندما تنشئ دالة ستكون المتغيرات وغيرها من اﻷشياء المعرّفة داخل الدالة ضمن نطاق مخصص لها، أي أنها غير مرئية ولا معروفة من قبل الشيفرة الموجودة خارج الدالة. بينما يكون كل ما يُعرّف خارج نطاق الدالة ضمن النطاق العام global scope ويمكن الوصول إليها من أي مكان في الشيفرة. أُعدت جافا سكريبت بهذه الطريقة لعدة أسباب أهمها اﻷمان والتنظيم. فقد لا ترعب أحيانًا بالوصول إلى متغير من أي مكان في الشيفرة. فالسكربتات التي تستدعيها من مصادر خارجية، قد تعبث بشيفرتك وتسبب المشاكل لأنه قد يصدف وتحمل أسماء مشابهة لمتغيراتك لكن لأغراض أخرى مما يسبب تعارضًا في تفسير الشيفرة. وقد يحدث هذا اﻷمر عرضيًا أو بشكل مقصود. لنقل مثلًا أن لديك ملف HTML يستدعي ملفي جافا سكريبت خارجيين، ويضم كلا الملفين متغيرًا ودالة لهما الاسم ذاته: ملف HTML: <!-- Excerpt from my HTML --> <script src="first.js"></script> <script src="second.js"></script> <script> greeting(); </script> ملف جافا سكريبت الأول: // first.js const name = "Chris"; function greeting() { alert(`Hello ${name}: welcome to our company.`); } ملف جافا سكريبت الثاني: // second.js const name = "Zaptec"; function greeting() { alert(`Our company is called ${name}.`); } تحمل الدالتين اللتين تريد استدعاءهما الاسم ()greeting، لكنك لن تصل إلى إلى الدالة في الملف اﻷول (ستهمل الدالة في الثاني). إضافة إلى ذلك سيتوّلد خطأ إن حاولت (في الملف الثاني) إسناد قيمة جديدة إلى المتغير name لأنه عّرف مسبقًا على أنه ثابت const ولا يمكن إعادة إسناد قيمة له. وهكذا فإن الاحتفاظ بأجزاء من شيفرتك ضمن الدوال بعيدًا عن بقية الشيفرة يجنبك العديد من المشاكل، ويُعد من الممارسات العملية الجيدة. اﻷمر مشابهة قليلًا لحديقة حيوان فيها أسد ونمر وحمار وحشي وبطريق وكل منها في قفصه الخاص ويمكنه الوصول فقط إلى اﻷشياء الموجودة في أقفاصها بشكل يشابه نطاق الدوال. فلو أمكن وصول إلى منها إلى قفص اﻵخر ستقع المشاكل بالتأكيد. وعلى اﻷقل لن تشعر بعض الحيوانات بالراحة مع سلوك اﻷخرى، فلن يتكيف اﻷسد والنمر مع بيئة البطريق الرطبة والباردة، وقد بيحدث اﻷسوء فقد يحاول اﻷسد أو النمر افتراس البطريق. ومشرف الحديقة سيلعب دور النطاق العام، فبإمكانه الوصول إلى أي قفص ﻹيصال الطعام ومعالجة الحيوان المريض. تطبيق عملي: التعامل مع نطاقات الرؤية لنلق نظرة على مثال واقعي يوضح مفهوم المجالات. احفظ نسخة من ملف التمرين على حاسوبك. ويتضمن الملف دالتين هما ()a و ()b وثلاثة متغيرات x و y و z اثنان منهما معرّفان ضمن الدالتين واﻷخير متغير عام. كما يتضمن الملف دالة ثالثة تُدعى تأخذ معاملًا واحدًا وتطبعه ضمن فقرة نصية في الصفحة. افتح التمرين ضمن المتصفح وضمن المحرر النصي. افتح طرفية جافا سكريبت ضمن أدوات مطوري ويب الخاصة بالمتصفح، ثم اكتب في الطرفية اﻷمر التالي: output(x); ينبغي أن ترى قيمة المتغير x قد طُبعت على الشاشة. حاول اﻵن إدخال التالي في الطرفية: output(y); output(z); ينبغي أن ترمي كلا اﻷمرين خطأً (y غير معرّف y is not defined ). إن السبب في ذلك هو مجال الدالة. فكل من y و Z معرفان ضمن الدالة ()a و ()b فلا يمكن للدالة ()output الوصول إليهما من النطاق العام. لكن ما الذي سيحدث عندما نستدعيهما من داخل الدالة؟ حاول أن تغيّر الدالتين ()a و ()b كالتالي: function a() { const y = 2; output(y); } function b() { const z = 3; output(z); } احفظ الشيفرة وأعد تحميل الصفحة ضمن المتصفح وحاول بعدها استدعاء الدالتين ()a و ()b من الطرفية: a(); b(); من المفترض أن ترى قيمتي y و z قد طبعتا على شاشة المتصفح، وسيعمل اﻷمر طالما أن الدالة ()output قد استدعيت من داخل الدالتين ()a و ()b أي في نفس النطاق الذي عُرفت فيه المتغيرات وتبقى ()output متاحة فيأي مكان طالما أنها ضمن النطاق العام جرّب أن تغيّر الشيفرة لتصبح كالتالي: function a() { const y = 2; output(x); } function b() { const z = 3; output(x); } احفظ التغييرات وحاول أن تعيد تحميل الصفحة وجرّب ما يلي مجددًا في الطرفية: a(); b(); ستطبع الدالين الدالتين ()a و ()b قيمة x في المتصفح. سيعمل اﻷمر جيدًا لأنه وعلى الرغم من أن استدعائي الدالة لا ينتميان إلى نفس مجال x لكن x معرّف كمتغير عام، فهو متاح في أي مكان من الشيفرة جرّب أخيرًا تحديث الشيفرة لتصبح كالتالي: function a() { const y = 2; output(z); } function b() { const z = 3; output(y); } احفظ التغييرات وحاول أن تعيد تحميل الصفحة وجرّب ما يلي مجددًا في الطرفية: a(); b(); سيلقي اﻵن استدعاء الدالتين ()a و ()b نفس الخطأ (ReferenceError: variable name is not defined) في الطرفية لأن استدعاءات الدالة ()output والمتغيرات التي تريد طباعة قيمتها لا ينتميان إلى نفس مجال أو نطاق الرؤية للدالة فهما غير مرئيان للاستدعاءات. ملاحظة: لا تُطبق نفس قواعد النطاق على الحلقات (مثل {}()for) والجمل الشرطية (مثل {}()if) فقد تبدوان مشابهتين للدوال لكنهما أمران مختلفان. انتبه إلى ذلك. ملاحظة: الخطأ ReferenceError: "x" is not defined من أكثر اﻷخطاء شيوعًا. ففي حال واجهت هذا الخطأ وأنت متأكد من أنك عرّفت المتغير، عليك في هذه الحالة مراجعة مجالات الرؤية. الخلاصة تعرفنا في هذا المقال على المفاهيم اﻷساسية للدوال كي نمهد لك الطريق لمقالات قادمة في تعلم جافا سكريبت والتعرف على عملية إنشاء دوال مخصصة تلائم احتياجاتك. ترجمة -وبتصرف- للمقال Functions- reusable bloacks of code اقرأ أيضًا المقال السابق: الحلقات في جافا سكريبت الدوال في جافاسكربت الدوال Functions في جافا سكريبت كائنات الدوال Function object وتعابير الدوال المسماة NFE في جافاسكربت الدوال العليا في جافاسكريبت
-
تخطيط الشبكة Grid هو أسلوب تخطيط ثنائي الاتجاه يستخدم لترتيب عناصر صفحة الويب، إذ يسمح بتوضع المحتوى ضمن أسطر وأعمدة ويقدم ميزات عدة تسمح بتنفيذ التخطيطات المعقدة بأسلوب مباشر. وسنقدم لك في هذا المقال كل ما تحتاجه لتبدأ العمل على تخطيط الصفحات باستخدامه. قبل البدء في قراءة هذا المقال يتوجب عليك أن: تطلع على أساسيات HTML كما شرحناها في سلسلة مقالات مدخل إلى HTML. تتفهم أساسيات عمل CSS. ما هو تخطيط الشبكة Grid Layout؟ الشبكة هي مجموعة من الخطوط الأفقية والعمودية التي تشكل نموذجًا لترتيب العناصر ضمنه. يمكّننا هذا التخطيط من إنشاء تخطيطات لا تقفز فيها العناصر أو تغير اتساعها عند الانتقال من صفحة إلى أخرى مما يمنح موقع الويب تناسقًا أفضل. وتتكون الشبكة تقليديًا من أعمدة وصفوف، وفراغات بين كل سطر وكل عمود، وتُعرف هذه الفراغات بالأقنية gutters كما في الصورة التالية: إنشاء شبكة باستخدام CSS إن قررت أن تخطيط الشبكة هو ما يحتاجه تصميم صفحة الويب الخاصة بك، يمكنك استخدام لغة CSS لإنجاز الأمر. سنلقي نظرة على الميزات الأساسية لتخطيط الشبكة أولًا، ثم نستكشف كيفية إنشاء تخطيط شبكة بسيط لمشروعك. تحديد الشبكة بداية حمًل وافتح هذا الملف الذي سيكون نقطة الانطلاق للعمل ضمن محرر الكود وضمن المتصفح. يعرض هذا المثال حاوية تضم عدة عناصر أبناء لها تخطيط الانسياب الاعتيادي افتراضيًا، إذ تظهر تحت بعضها. سنتعامل مع هذا الملف في القسم الأول من مقالنا، ونطبق بعض التغييرات لاستيعاب سلوك تخطيط الشبكة. ولتعيين شبكة نستخدم القيمة grid للخاصية display. ستفعّل هذه القيمة تخطيط الشبكة، وستتحول جميع العناصر ضمن هذه الحاوية إلى عناصر شبكة grid item. لهذا ضع التصريح التالي ضمن الملف: .container { display: grid; } وعلى خلاف الصندوق المرن، لن تجد اختلافًا مباشرًا في توضع العناصر عند تطبيق القاعدة display: grid، لأن هذا التصريح سيضع العناصر ضمن شبكة من عمود واحد وستبقى فوق بعضها البعض كما هو الحال في الانسياب الاعتيادي. ولترى شيئًا أقرب إلى الشبكة، لا بد من إضافة أعمدة جديدة إليها. لهذا سنضع ثلاث أعمدة لكل منها اتسع مقداره 300 بكسل. يمكنك اختيار أي واحدة طول أو نسبة مئوية لضبط اتساع هذه الأعمدة. .container { display: grid; grid-template-columns: 200px 200px 200px; } أضف التصريح الثاني إلى قاعدة CSS ثم أعد تحميل الصفحة وسترى كيف رتبت العناصر نفسها ليحتل كل منها مكانًا في الشبكة. See the Pen grid1 by Hsoub Academy (@HsoubAcademy) on CodePen. الشبكات المرنة واستخدام الواحدة fr يمكنك تعيين الشبكات بوحدات fr إضافة إلى وحدات الطول والنسب المئوية. وتمثل هذه الوحدة جزءًا من المساحة المتاحة ضمن حاوية الشبكة وتتيح مرونة في تحجيم الأسطر والأعمدة. غيّر القاعدة السابقة كي ننشئ ثلاث أعمدة اتساع كل منها 1fr أي جزء من كل: .container { display: grid; grid-template-columns: 1fr 1fr 1fr; } تتميز الشبكة الآن بوجود مسارات مرنة، إذ توزع الوحدة fr المساحة المتوفرة بشكل تناسبي. وبإمكانك طبعًا استخدام أية قيم موجبة للمسارات في شبكتك كالتالي: .container { display: grid; grid-template-columns: 2fr 1fr 1fr; } إذ يأخذ المسار (عمود هنا) جزئين 2fr من أصل أربعة أجزاء كلية من المساحة المتوفرة بينما يأخذ كل من المسارين التاليين جزءًا واحدًا 1fr وبالتالي سيكون العمود الأول أكثر اتساعًا. وبإمكانك المزج بين الوحدة fr ووحدات الأطوال الثابتة، وفي هذه الحالة تحجز المساحة اللازمة للمسارات ثابتة الاتساع ثم يجري توزيع المساحة الباقية على المسارات التي تحمل قيمًا نسبية. See the Pen grid2 by Hsoub Academy (@HsoubAcademy) on CodePen. ملاحظة: تتوزع المساحات المتاحة فقط بين المسارات عند استخدام الوحدة fr وليس كامل المساحة، بمعنى أنه إذا شغل مسار مساحة أكبر لأن محتواه أكبر سيقل الفراغ الذي تتقاسمه المسارات. الأقنية بين المسارات لإنشاء فراغات بين المسارات، نستخدم الخاصيات التالية: column-gap: للفراغات (الأقنية) بين الأعمدة. row-gap: الفراغات بين الأسطر. gap: خاصية تختصر الخاصيتين السابقتين. .container { display: grid; grid-template-columns: 2fr 1fr 1fr; gap: 20px; } يمكن أن تُقدّر أبعاد الأقنية بأي وحدة قياس ثابتة أو نسبة مئوية ما عدا الوحدة التناسبية fr. See the Pen grid3 by Hsoub Academy (@HsoubAcademy) on CodePen. ملاحظة: سبُقت الخاصيات السابقة في المواصفات الأقدم بالبادئة -grid لكن المواصفات الجديدة ألغتها. مع ذلك يبقى استخدامها صحيحًا كاسم بديل. لهذا كي تبقى في مأمن من المشاكل وتُبقي شيفرتك منيعة استخدم كلتا النسختين من الخاصية: .container { display: grid; grid-template-columns: 2fr 1fr 1fr; grid-gap: 20px; gap: 20px; } تكرار المسارات الموجودة بإمكانك تكرار جميع مسارات الشبكة أو جزء منها باستخدام الدالة ()repeat التي تقدمها CSS. لترى ذلك، غير طريقة جدولة المسارات في التصريح السابق إلى الشكل التالي: .container { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; } ستكون النتيجة ظهور ثلاثة أعمدة متناسبة 1fr من حيث الاتساع كما سبق. إذ يشير المعامل الأول من الدالة ()repeat إلى عدد مرات التكرار بينما يشير المعامل الثاني إلى عدد المسارات، فقد ترغب بتكرارها أكثر من مسار. الشبكات الصريحة والمضمنة لقد خصصنا في الشبكة أعمدةً فقط حتى اللحظة، بينما ظهرت الأسطر تلقائيًا لتناسب المحتوى وهذا مثال عن الشبكات الصريحة مقابل الشبكات الضمنية. وإليك الفرق: الشبكات الصريحة: تُنشأ باستخدام الخاصيتين grid-template-columns أو grid-template-rows. الشبكات الضمنية: توسّع الشبكة الصريحة عندما لا تستطيع الشبكة احتواء المحتوى كأن يظهر ضمن أسطر جديدة. تنشأ المسارات في الشبكات الضمنية بأبعاد تلقائية auto افتراضيًا، وهذا يعني عمومًا بأنه واسعة كفاية لتضم المحتوى. ولمنح الشبكات الضمنية مسارات بأبعاد مخصصة، بإمكانك استخدام الخاصيتين grid-auto-rows و grid-auto-columns. فإن خصصت القيمة 100px للخاصية grid-auto-rows سترى بأن ارتفاع الصفوف التي تنشأ هو 100 بكسل. .container { display: grid; grid-template-columns: repeat(3, 1fr); grid-auto-rows: 100px; gap: 20px; } See the Pen grid4 by Hsoub Academy (@HsoubAcademy) on CodePen. الدالة ()minmax لن يكون ارتفاع 100 بكسل كافيًا في حالتنا السابقة إن أردنا وضع محتوى أطول من 100 بكسل، وبالتالي يحدث عندها الطفحان overflow. لهذا من الأفضل أن يكون ارتفاع المسارات 100 بكسل على الأقل وتكون قادرة على التوسع أكثر عند إضافة محتوى أكبر. ومن المعروف عمومًا في تصميم الويب أنه من الصعب توقع ارتفاع أي عنصر وخاصة عند إضافة محتوى أو عند تغيير حجم الخط مما قد يسبب مشاكل في التصميمات التي تحاول أن تجعل التصميم مثاليًا من كل النواحي. تتيح لنا الدالة ()minmax تحديد الحجم الأدنى والأقصى للمسار، فالصيغة minmax(100px, auto) للدالة تحدد حجمًا أدنى للمسار مقداره 100 بكسل، بينما حددت قيمة الحجم الأقصى ليكون تلقائيًا auto وتعني أن العنصر سيتوسع حتى يستوعب المحتوى. جرّب ضبط قيمة الخاصية grid-auto-rows باستخدام هذه الدالة: .container { display: grid; grid-template-columns: repeat(3, 1fr); grid-auto-rows: minmax(100px, auto); gap: 20px; } فإن أضفت محتوى أكبر سترى كيف يتوسع المسار حتى يستوعب المحتوى الجديد. لاحظ كيف يحدث التوسع أفقيًا مع السطر. أعمدة بقدر ما تتسع له المساحة بإمكاننا تطبيق الأفكار التي تعلمناها سابقًا حول ترتيب المسارات والتكرار والدالة ()minmax لإنشاء نماذج مفيدة. فمن الجيد أحيانًا أن تكون الشبكة قادرة على إنشاء أعمدة بقدر ما تتسع لها الحاوية. ولإنجاز ذلك، نضبط قيمة الخاصية grid-template-columns باستخدام الدالة ()repeat لكن بدل أن نمرر لها المعامل الأول عددًا، نمرر لها القيمة auto-fill. كما نستخدم الدالة ()minmax كمعامل ثاني ونحدد فيها قيمة أصغر قياس نريده للأعمدة، ونجعل قيمة أكبر قياس هو 1fr. جرّب ذلك بكتابة الشيفرة التالية: .container { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); grid-auto-rows: minmax(100px, auto); gap: 20px; } See the Pen Untitled by Hsoub Academy (@HsoubAcademy) on CodePen. نجح الأمر لأن الشبكة ستنشئ أكبر عدد ممكن من الأعمدة التي اتساعها 200 بكسل يمكن للحاوية استيعابها ومن ثم تقسم المساحات الفارغة الباقية بين جميع الأعمدة من خلال القيمة 1fr التي رأينا أنها توزّع المساحة بالتساوي بين المسارات. توزيع المحتوى وفقًا للأسطر ننتقل الآن من إنشاء الشبكات إلى وضع المحتوى ضمنها، وقد رأينا أن للشبكة أسطر، وهذه الأسطر مرقمة ابتداءً من 1 وتتعلق بنمط الكتابة في الصفحة. إذ يُكتب السطر الأول من العمود في الإنكليزية (التي تُكتب من اليسار إلى اليمين) انطلاقًا من الناحية اليسرى للشبكة ويكون هذا السطر في الأعلى، أما في العربية التي تُكتب من اليمين إلى اليسار فستبدأ كتابة المحتوى في العمود من يمين الشبكة. بإمكاننا ترتيب الأشياء وفقًا لهذه لأسطر الكتابة بتحديد بداية ونهاية السطر من خلال الخاصيات التالية: grid-column-start grid-column-end grid-row-start grid-row-end يمكن لجميع الخاصيات أن تأخذ رقم السطر كقيمة لها، كما يمكن استخدام الخاصيات المختصرة التالية: grid-column grid-row التي تتيح لك تحديد بداية ونهاية السطر مباشرة وتفصل بين القيمتين الشرطة المائلة /. لتجريب الأمر، نزّل الملف الذي يضم شيفرة المثال التالي (يمكنك أيضًا الاطلاع على كيفية عملها مباشرة على جيت-هاب). ويضم الملف شبكة ومقال بسيط. لاحظ كيف توضِّع القيمة كل عنصر ضمن خلية مخصصة من الشبكة، ولترتيب جميع العناصر في الشبكة باستخدام أسطر الشبكة، أضف القواعد التالية غلى نهاية شيفرة CSS: header { grid-column: 1 / 3; grid-row: 1; } article { grid-column: 2; grid-row: 2; } aside { grid-column: 1; grid-row: 2; } footer { grid-column: 1 / 3; grid-row: 3; } See the Pen grid6 by Hsoub Academy (@HsoubAcademy) on CodePen. ملاحظة: بإمكانك أيضًا استخدام القيمة 1- كي تستهدف نهاية العمود أو السطر ثم اعدد ابتداءً من النهاية إلى البداية باستخدام القيم السالبة. وتذكر أن الأسطر تُعدّ دائمًا من حافة الشبكة الصريحة وليست الضمنية. ترتيب العناصر باستخدام الخاصية grid-template-areas توجد طريقة بديلة لترتيب العناصر في الشبكة باستخدام الخاصية grid-template-areas وإعطاء أسماء للعناصر المختلفة في التصميم. لتقف على الأمر، أزل شيفرة ترتيب العناصر وفق الأسطر في المثال السابق ثم أضف الشيفرة التالية بدلًا عنها: .container { display: grid; grid-template-areas: "header header" "sidebar content" "footer footer"; grid-template-columns: 1fr 3fr; gap: 20px; } header { grid-area: header; } article { grid-area: content; } aside { grid-area: sidebar; } footer { grid-area: footer; } أعد تحميل الصفحة وسترى أن العناصر قد رُتبّت بالطريقة السابقة نفسها دون أن تستخدم أية أرقام للأسطر. See the Pen grid7 by Hsoub Academy (@HsoubAcademy) on CodePen. إليك قواعد الخاصية grid-template-areas: عليك ملئ كل خلية من خلايا الشبكة. كرر الاسم لتضم خليتين في خلية واحدة. استخدم النقطة . لتترك الخلية فارغة. لا بد أن تكون مساحة الشبكة مربّعة. فلا يمكنك مثلًا تصميم شبكة على شكل حرف L. لا يمكن تكرار المساحات في أماكن مختلفة. بإمكانك تجريب عدة خيارات في تخطيط الشبكة بهذه الطريقة، حاول مثلًا وضع التذييل تحت المقالة فقط وأن تجعل الشريط الجانبي يمتد للأسفل. هذا الطريقة في تخطيط الشبكة جميلة جدًا لأنها واضحة، وبمجرد النظر إلى شيفرة CSS ستعرف تمامًا ما الذي سيحدث. إطارات العمل مع الشبكة في شبكات CSS تميل إطارات العمل مع الشبكات grid frameworks لتكون شبكات مكونة من 12-16 عمود. ولا حاجة بالطبع إلى أدوات أخرى سوى شبكة CSS لتنفيذ إطار العمل هذا، فهي موجودة فعلًا في المواصفات. نزّل الملف الذي يضم شيفرة المثال التالي وهي عبارة عن حاوية مكونة من شبكة تتكون من 12 عمودًا، وتبقى شيفرة HTML نفسها التي استخدمناها في المثال السابق. بإمكاننا الآن استخدام التوضع المبني على الأسطر لتوزيع المحتوى ضمن شبكتنا ذات 12 عمودًا: header { grid-column: 1 / 13; grid-row: 1; } article { grid-column: 4 / 13; grid-row: 2; } aside { grid-column: 1 / 4; grid-row: 2; } footer { grid-column: 1 / 13; grid-row: 3; } See the Pen grid8 by Hsoub Academy (@HsoubAcademy) on CodePen. إن استخدمت الأداة Firefox Grid Inspector لتوضح خطوط الشبكة في تصميمك، سترى كيف تعمل تمامًا شبكتنا ذات 12 عمودًا: الخلاصة تجولنا في مقالنا على الميزات التي يقدمها تخطيط الشبكة Grid Layout في لغة CSS، واستعرضنا أمثلة مختلفة على استخدامه في ترتيب عناصر صفحة الويب وتنفيذ التخطيطات المعقدة بسهولة كبيرة، ومن المفترض أن تصبح قادرًا الآن على استخدام الشبكات بفعالية ضمن تصميماتك. ترجمة -وبتصرف- للمقال: Grids اقرأ أيضًا المقال السابق: تخطيط الصندوق المرن Flexbox في صفحات الويب مدخل إلى تخطيط صفحات الويب باستخدام CSS تعرف على أساسيات CSS تقنيات كتابة شيفرات CSS احترافية وسهلة الصيانة التحكم في تموضع العناصر في CSS
-
ننقلك في سلسلة المقالات التالية خطوة بخطوة لإنشاء لعبة كاملة ثنائية البعد باستخدام محرّك اﻷلعاب جودو Godot. وفي نهاية السلسلة ستكون قد أنجزت لعبة بسيطة كتلك الموضحة في الصورة التالية: وسنتعلم من خلال هذه السلسلة كيفية عمل محرّر محرك الألعاب جودو Godot، وكيفية هيكلة المشروع، ومن ثم ستتعلم خطوات بناء اللعبة بشكل عملي. ملاحظة: هذا المقال هو مدخل إلى محرك اﻷلعاب جودو، ويفترض أنك تتمتع ببعض الخبرات البرمجية لاستخدام لغات برمجة الألعاب وبرمجة لعبتك الإلكترونية من خلالها. فإن كنت جديدًا في عالم البرمجة. ننصحك بالعودة إلى أكاديمية حسوب التي تضم عددًا كبيرًا من المقالات والمواضيع التي تناسب القادمين الجدد وتقدم أفضل الدورات التعليمية للبرمجة من الصفر وحتى الاحتراف. سوف نطلق على لعبتا اسم "تفادي الزواحف Dodge the creeps"، ومن المفترض أن تبتعد شخصية اللعبة عن اﻷعداء قدر اﻹمكان. ستتعلم من خلال هذه السلسلة كيف تقوم بما يلي: تنشئ لعبة مكتملة ثنائية البعد باستخدام محرك الألعاب جودو. تهيكل مشروع لعبة بسيطة. تحرك شخصية اللعبة وتغيير شكلها. تنشر أعداء عشوائيًا. تعيد نتيجة اللعبة. لماذا نطور لعبة ثنائية البعد 2D؟ إن كنت جديدًا في تطوير اﻷلعاب أو لا تألف بيئة جودو، ننصحك ان تبدأ بتعلم تصميم اﻷلعاب ثنائية البعد، فهي تسمح لك في تعلم بيئة العمل وترتاح فيها قبل أن تبدأ اﻷلعاب ثلاثية الأبعاد التي تميل أكثر إلى التعقيد. خُصِّصَ هذا المقال والمقالات اللاحقة للمبتدئين الذين لديهم أساسيات في التعامل مع محرك جودو، فإن كنت جديدًا في البرمجة ويصعب عليك كتابة الأكواد من الصفر، بإمكانك الاطلاع على الشيفرة المصدرية للعبة عبر جيت-هب وفهمها. كما حضرنا مسبقًا بعض الملحقات التي تحتاجها، لهذا سنقفز مباشرة إلى الشيفرة التي يمكنك تحميلها من المستودع المخصص على جيت-هب. إعداد المشروع سنُعّد في هذا المقال مشروعنا وننظمه، لهذا شغّل محرك ألعاب جودو وأنشئ مشروعًا جديدًا. ليس عليك سوى تحديد مسار مناسب لتخزين المشروع وبإمكانك ترك بقية القيم كما هي. بعدها عليك تحميل الأرشيف الذي يتضمّن كافة ملفات الصور والمقاطع الصوتية التي سنستخدمها في صنع اللعبة ثنائية الأبعاد الخاصة بنا، ثم استخرج محتوياته وانقل المجلدين /art و /fonts إلى مجلد اللعبة. يجب أن يبدو مجلد اللعبة مشابهًا للقطة الشاشة التالية: صُممت هذه اللعبة لنمط العرض الشاقولي portrait، لهذا لا بد من تعديل قياس نافذة اللعبة. انقر على المشروع ثم اختر إعدادات المشروع لفتح نافذة اﻹعدادات ثم افتح في العمود اليميني القائمة "عرض display" ثم انقر على "نافذة window". واضبط بعد ذلك اتساع نافذة العرض viewport width على 480 وارتفاعها على 720. وتحت الخيار "تمدد Stretch " اضبط "الوضع Mode" على القيمة canvas_items ونسبة العرض على القيمة keep، حيث يساهم هذان الخياران في اتساق العرض على شاشات مختلفة الأبعاد. تنظيم المشروع سنصنع في هذا المشروع ثلاثة مشاهد مستقلة هي Player و Mob وHUD بحيث تجتمع كلها في المشهد الرئيسي Main. ومن اﻷفضل في المشاريع اﻷكبر أن تنشئ مجلدًا يضم المشاهد المختلفة والسكربتات الملحقة بها، لكن لمشروع صغير كهذا، بإمكانك تخزينها في المجلد الجذري للمشروع الذي يُعرّف بالعنوان //:res . وستجد مجلد المشروع في حاوية "نظام الملفات" في الزاوية اليسارية السفلى. الخلاصة تعرفنا في مقال اليوم على أولى مراحل برمجة لعبة ثنائية الأبعاد في محرك الألعاب جودو Godot، وبدأنا بتحديد الشخصيات والمشاهد التي تضمها اللعبة، وحددنا طريقة تنظيم ملفاتها، وندعوك لمتابعة السلسلة التالية من هذه المقالات لتتعرف على الخطوات التالية العملية لإنجاز اللعبة وبرمجتها. ترجمة -وبتصرف- للمقالين: your first 2D game و Setting up the project اقرأ أيضًا المقال السابق: تعلم الميزات الجديدة في محرك الألعاب جودو وطرح الأسئلة حوله إعداد محرك الألعاب جودو Godot للعمل مع قاعدة البيانات SQLite مدخل إلى محرك الألعاب جودو Godot مطور الألعاب: من هو وما هي مهامه
-
لربما صادفتك بعض المشاكل عندما بنيت في مقال سابق لعبة تخمين الرقم الصحيح، أو وجدت أن هذه اللعبة لا تعمل بالشكل المطلوب! لهذا سنفرد هذا المقال لمساعدتك في البحث عن المشكلات البرمجية التي قد تصادفك وإيجاد حل لها، من خلال تزويدك ببعض التلميحات والنصائح عن كيفية إيجاد وإصلاح اﻷخطاء. ننصحك قبل المتابعة في قراءة هذا المقال بالاطلاع على بعض المقالات السابقة مثل: أساسيات علوم الحاسوب أساسيات HTML أساسيات عمل CSS أساسيات جافا سكريبت أنواع اﻷخطاء البرمجية عندما ترتكب خطأ عند كتابة الشيفرة ستواجه عمومًا نوعين من اﻷخطاء: أخطاء في الصياغة (قواعدية) syntax error: وتنتج عن الخطأ في كتابة التعليمات مما يسبب توقف عمل البرنامج تمامًا أو توقف جزء منه، وستظهر عادة بعض رسائل الخطا أيضًا. وهي عادة قابلة للإصلاح إن كنت تألف العمل على اﻷدوات المناسبة للتنقيح وتعرف ما تعنيه رسالة الخطأ. أخطاء منطقية logic errors: في هذه الحالة تكون الصياغة صحيحة، لكن الشيفرة لا تعمل كما ينبغي، أي أن البرنامج يُنفّذ بنجاح لكنه يعطي نتائج غير صحيحة. وهي أخطاء أصعب إصلاحًا لعدم توفر رسائل أخطاء توجهك إلى مصدر الخطأ. فالعملية إذًا ليست بهذه السهولة. وما ستراه عندما تتعمق في البرمجة وجود تفاصيل أخرى لأنواع اﻷخطاء، لكن التصنيف السابق هو كل ما تحتاجه في هذا المستوى المبكر من مسيرتك، وسنعمل على هذين النوعين في هذا المقال. مثال عن خطأ حتى نبدأ العمل، عليك أن تعود إلى لعبة خمّن الرقم الصحيح، لكن ما سنفعله أننا سنعود إلى نسخة أخرى من اللعبة ارتُكبت فيها أخطاء عمدًا. لهذا حمّل نسخة منها. افتح الملف ضمن المحرر النصي وضمن المتصفح. جرّب أن تلعب، وسترى أن اللعبة لا تعمل عند النقر على زر "Submit guess". ملاحظة: ربما قد حصلت بالفعل على نسخة لا تعمل عند تجريب كتابة الشيفرة بنفسك، لكننا نريد منك أن تعمل على نسختنا في هذا المقال كي تتعلم التقنيات التي نقدمها لحل المشاكل ثم يمكنك العودة بعدها لتصحيح أخطاء نسختك. ما سنفعله حاليًا هو الانتقال إلى "طرفية المطوّر" لنرى إن كانت تعرض أية أخطاء صياغة كي نحاول إصلاحها، وهذا ما ستتعلمه تاليًا. إصلاح أخطاء الصياغة قد تكون كتبت بعض أوامر جافا سكريبت في طرفية جافا سكريبت ضمن أدوات مطوري ويب DevTools، لكن الفائدة الأكبر من كتابة الشيفرة في أدوات المطور هي رسائل أخطاء الصياغة التي تعرضها لك الطرفية عند وقوع الخطأ. لنبدأ باصطياد اﻷخطاء إذًا! 1.انتقل إلى النافذة التي ظهرت number-game-errors.html ثم افتح طرفية جافا سكريبت وسترى رسالة خطأ التالية: 2. السطر اﻷول من الرسالة هو التالي Uncaught TypeError: guessSubmit.addeventListener is not a function number-game-errors.html:86:15` يخبرنا الجزء الأول عن سبب الخطأ ويخبرنا الجزء الثاني عن موقع الخطأ في الشيفرة وهو السطر 86، المحرف 15 في الملف "number-game-errors.html". 3. إن ألقينا نظرة على موقع الخطأ في السطر 86 سنجد السطر التالي: guessSubmit.addeventListener("click", checkGuess); تحذير: قد لا يكون الخطأ موجودًا لديك في السطر 86 في حال كنت تستخدم موسّعًا يُشغّل خادمًا على جهازك، فقد يسبب ذلك إقحام شيفرة إضافية إلى شيفرتك، لهذا قد تشير أدوات مطوري ويب إلى مكان للخطأ مختلف عن السطر 86. 4. ينص الخطأ على أن الشيفرة لا تمثل دالة، وهذا يعني أن الدالة التي نستدعيها لم يميزها مفسّر جافا سكريبت. وغالبًا ما يكون السبب خطأ في كتابة شيء ما. فإن لم تكن متأكدًا من الكتابة الصحيحة لتعليمة أو صياغة معينة فمن الأفضل مراجعة توثيق جافا سكريبت على موسوعة حسوب والبحث عن "addeventListener". 5. بالعودة إلى التوثيق السابق نرى أن الخطأ كان في تهجئة اسم الدالة التي تُكتب بالشكل addEventListener وليس بالشكل addeventListener ومن مميزات لغة جافا سكريبت أنها حساسة لحالة الأحرف، وبالتالي تصحيح كتابة اسم الدالة سيحل المشكلة. نفّذ ذلك وتأكد من حل المشكلة. جولة ثانية على أخطاء الصياغة 1. احفظ التغييرات على صفحتك وحدّث المتصفح وسترى أن الخطأ قد زال. 2. لو حاولت مجددًا تخمين رقم ثم النقر على زر اﻹرسال سترى خطأً آخر: 3. الخطأ هذه المرة هو التالي: Uncaught TypeError: can't access property "textContent", lowOrHi is null قد تجد رسالة مختلفة هنا وفقًا للمتصفح الذي تستخدمه. فالرسالة السابقة هي ما يعرضه متصفح فايرفوكس، أما رسالة خطأ متصفح كروم مثلًا هي: Uncaught TypeError: Cannot set properties of null (setting 'textContent') الخطأ ذاته لكن كل متصفح يصفه بطريقة مختلفة عن اﻵخر. ملاحظة: لا يُعرض هذا الخطأ فور تحميل الصفحة لأنه يحدث ضمن الدالة كتلة ()checkGuess. وكما سنرى في مقالات قادمة أن الشيفرة داخل الدوال تنفذ في سياق منفصل عن بقية الشيفرة، وبهذه الحالة لن تعمل الشيفرة ضمن الدالة ولن يقع الخطأ حتى تّنفّذ الدالة ()checkGuess في السطر 86. 4. ألق نظرة على الخطأ في السطر 80 وسترى الشيفرة التالية: lowOrHi.textContent = "Last guess was too high!"; 5. تحاول الشيفرة في هذا السطر إسناد سلسلة نصية إلى الخاصية textContent للمتغير lowOrHi لكن اﻷمر لا ينجح لأن المتغيّر لا يتضمن ما يُفترض أنه يتضمنه. لهذا حاول أن تبحث عن ورود آخر لهذا المتغّير وستجده في السطر 49: const lowOrHi = document.querySelector("lowOrHi"); نحاول في هذا السطر أن نسند إلى lowOrHi مرجعًا إلى عنصر من عناصر صفحة HTML، لنلقي إذًا نظرة على ما يحتويه هذا المتغير بعد هذه العملية من خلال كتابة الأمر التالي في السطر 50: console.log(lowOrHi); تطبع هذه التعليمة قيمة المتغير lowOrHi على الطرفية. 6. احفظ التغييرات وحدّث المتصفح وسترى نتيجة التعليمة ()console.log على النحو التالي: إن قيمة هذا المتغير هي null لهذا عرض متصفح رسالة الخطأ مشيرًا إلى أن lowOrHi is null. لهذا هنالك خطأ بالتأكيد في السطر 49. إذ تعني القيمة null لاشيء أو لا قيمة، وأخفقت في النهاية عملية إسناد مرجع إلى التغيّر. 7. لنفكر بسبب حدوث مشكلة السطر 49، إذ يحتوي هذا السطر التابع ()document.querySelector للحصول على مرجع إلى عنصر من خلال البحث عن محدد CSS المطابق. ولو بحثا عن العنصر المطلوب لوجدنا أنه الفقرة النصية <p class="lowOrHi"></p> 8. إذا ما نريده هو محدد صنف class selector يبدأ سمه بنقطة .، لكن المحدد الذي مُرر إلى التابع ()querySelector في السطر 49 كان بلا نقطة، وقد يكون ذلك سبب المشكلة لهذا جرّب إصلاح اﻷمر بتحويل lowOrHi إلى lowOrHi. 9. جرّب حفظ التغييرات وتحديث الصفحة، ومن المفترض حينها أن تكون نتيجة التعليمة ()console.log هي العنصر <p>، وهكذا نكون قد أصلحنا خطأ آخر. يمكنك اﻵن حذف السطر ()console.log أو إبقاءه كمرجع -اﻷمر يعود إليك-. جولة ثالثة على أخطاء الصياغة 1. إن حاولت تجريب اللعبة مجددًا ستجد أن إصلاحاتك ناجحة وستعمل بشكل رائع حتى اللحظة التي تنهي فيها اللعبة بأن تخمّن الرقم الصحيح أوتنتهي محاولاتك. 2. ستخفق اللعبة مجددًا في هذه المرحلة وسيظهر من جديد الخطأ الأول "TypeError: resetButton.addeventListener is not a function"، لكن مصدره هذه المرة السطر 94. 3. بالعودة إلى هذا السطر والتدقيق فيه نجد أن الخطأ المرتكب هو نفسه الخطأ المرتكب في المرة اﻷولى، لهذا غيّر addeventListener إلى addEventListener. الخطأ المنطقي يجب أن تعمل اللعبة جيدًا بعد تطبيق اﻹصلاحات السابقة جميعها، لكن بعد أن تجرب اللعبة عدة مرات ستلاحظ بلا شك أن الرقم العشوائي الذي تختاره اللعبة وعليك تخمينه هو دائما (1). وبالتأكيد لا نريد أن تكون اللعبة بهذا الشكل! لا شك إذًا أن هناك خطأ ما في منطق اللعبة لأنها لا تعيد أية أخطاء لكنها في المقابل لا تعطي النتيجة المتوقعة منها. 1. ابحث عن المتغيّر randomNumber والأسطر التي ضُبط فيها بداية وستجد في السطر 45 تقريبًا التصريح عن المتغّير وإسناد قيمة له: let randomNumber = Math.floor(Math.random()) + 1; 2. ستجد في السطر 113 الأمر الذي يوّلد الرقم العشوائي قبل كل بداية لعبة randomNumber = Math.floor(Math.random()) + 1; 3. للتحقق أن هذه اﻷسطر هي مصدر المشكلة، سنحاول استخدام التعليمة ()console.logأسفل كل من السطرين السابقين كالتالي: console.log(randomNumber); 4. احفظ التغييرات وحدّث المتصفح ثم جرّب أن تلعب بعض الجولات وسترى أن قيمة المتغيّر randomNumber تُسجّل (1) دائمًا في الطرفية. العمل على إصلاح اﻷخطاء المنطقية لنتأمل كيفية عمل التابع ()Math.random في محاولة فهم الخلل، إذ يوّلد هذا التابع رقمًا عشريًا عشوائيًا بين 0 و 1 مثل 0.5463723462. ثم نمرر العدد العشوائي الناتج إلى تابع آخر هو ()Math.floor والذي يقرّب قيمة العدد العشري إلى العدد الصحيح اﻷقل منه مباشرة، ثم نضيف إلى الناتج العدد 1. Math.floor(Math.random()) + 1; إن تقريب عدد عشري بين 0 و 1 إلى أقرب عدد صحيح أصغر منه مباشرة يعطي الرقم 0 دائمًا وعند إضافة الرقم 1 سيكون الناتج 1 دائمًا! لهذا من الواضح أن علينا ضرب العدد العشوائي بالعدد 100 للحصول على عدد عشري بين 0 و 100 ثم نقرّبه إلى أقرب عدد صحيح أصغر منه فستكون النتيجة عدد عشوائي صحيح بين 0 و 99: Math.floor(Math.random() * 100); نضيف بعد ذلك الرقم 1 لنحصل على عدد بين 1 و100 Math.floor(Math.random() * 100) + 1; جرّب أن تعدّل السطرين اللذان يضمان المنطق السابق ثم احفظ التغييرات وحدّث المتصفح وسترى أن اللعبة تعمل اﻵن كما نريد. أخطاء شائعة أخرى ستقع في العديد من الأخطاء الشائعة عند كتابة شيفرتك، لهذا سنلقي نظرة على أهمها في الأقسام التالية. الخطأ SyntaxError: missing ; before statement يشير هذا الخطأ عمومًا إلى أنك أغفلت الفاصلة المنقوطة ; في نهاية أحد أسطر الشيفرة، وقد يكون الخطأ أحيانًا أكثر غموضًا. فلو بدلنا السطر التالي ضمن الدالة ()checkGuess const userGuess = Number(guessField.value); إلى السطر التالي const userGuess === Number(guessField.value); سيعرض المتصفح الخطأ السابق لأنه يعتقد بأن تحاول عمل شيء مختلف. لهذا عليك الانتباه إلى عدم الخلط بين عامل الإسناد = الذي يُسند قيمة إلى متغير ما، وبين عامل المساواة المنطقي === الذي يختبر تساوي قيمة مع قيمة أخرى ثم يعيد قيمة منطقية قيمتها صحيح true أو خطأ false. تخبرك اللعبة أنك فزت دائمًا سواء كان تخمينك صحيحًا أو خاطئًا قد يكون هذا الخطأ نتيجة للخلط بين عاملي اﻹسناد والمساواة. فلو غيرنا السطر التالي ضمن الدالة ()chsckGuess if (userGuess === randomNumber) { إلى السطر التالي: if (userGuess = randomNumber) { سيعيد الاختبار في هذه الحالة القيمة true دائمًا وستخبرك اللعبة عندها أنك فائز دائمًا. انتبه لذلك! الخطأ SyntaxError: missing ) after argument list هذا الخطأ بسيط نوعًا ما، ويعني عمومًا أنك أغفلت كتابة قوس الإغلاق في نهاية دالة أو تابع عند استدعائه. الخطأ: SyntaxError: missing : after property id يرتبط هذا الخطأ عادة بإعداد كائن جافا سكريبت بطريقة غير صحيحة، لكننا حاولنا إظهاره هنا بتبديل هذا السطر: function checkGuess() { بالسطر التالي function checkGuess( { إذ سيعتقد المتصفح انك تحاول تمرير محتوى دالة كوسيط إلى دالة أخرى. عليك الانتباه إلى اﻷقواس جيدًا! الخطأ: SyntaxError: missing } after function body وهو خطأ بسيط يشير إلى إغفال أحد القوسين المعقوصين في بنية دالة أو كتلة شرطية، يمكنك توليد هذا الخطأ بحذف القوس المعقوص من نهاية الدالة ()checkGuess الخطأ: '*SyntaxError: expected expression, got '*string أو SyntaxError: unterminated string literal تعني هذه اﻷخطاء عمومًا أنك أغفلت أحد علامات التنصيص التي تغلق سلسلة نصية أو تبدؤها. ففي الخطأ اﻷول يستبدل المتصفح القيمة string بالقيمة غير المتوقعة التي وجدها بدلًا من إشارة التنصيص في بداية السلسلة النصية. أما الخطأ الثاني فيعني أنك لم تنهي السلسلة النصية بإشارة تنصيص. وفي جميع اﻷخطاء التي قد تواجهك، فكّر بالطريقة التي اتبعناها في أمثلتنا لحل اﻷخطاء. فعندما يقع الخطأ، انتقل إلى السطر الذي وقع فيه والذي تشير إليه طرفية أدوات مطوري ويب وحاول أن تتفحص ما يمكن أن يكون خاطئًا. وتذكر دائمًا أنه ليس من الضرورة أن تكون اﻷخطاء في نفس السطر، وأن الخطأ قد لا ينتج بالضرورة عن نفس اﻷسباب التي تحدثنا عنها في مقالنا. الخلاصة هكذا نكون قد تعرفنا على أساسيات تتبع اﻷخطاء البسيطة في جافا سكريبت. وتذكر أنه ليس من السهل دائمًا اكتشاف الخطأ في الشيفرة البرمجية، لكن ما قدمناه في هذه المقالة قد يوفر عليك ساعات من العناء ويسرّع وتيرة اكتشافك اﻷسباب المحتملة للأخطاء وخاصة في بداية رحلتك في تعلم البرمجة. ترجمة -وبتصرف لمقال What went wrong troubleshooting JavaScript اقرأ أيضًا المقال السابق: تجربتك اﻷولى مع جافا سكريبت مبادئ كتابة جافا سكريبت متسقة ومفهومة كيفية التعامل مع الأخطاء البرمجية الزلات البرمجية والأخطاء في جافاسكريبت
-
إن وجودك على رأس الفريق أمر مجزٍ، لكنه كأي عمل مهم محفوف بالتحديات، وكجزء منها إجراء حوارات مع موظفيك. فهل لديك القدرة على خوض نقاشات صعبة؟ سواءً كان النقاش عن ضعف الأداء أو خلافات ضمن الفريق أو مشاكل شخصية، تابع قراءة هذا المقال لتتعرف على طريقة الخوض في نقاشات صعبة مع أعضاء فريقك. ما الذي عليك فعله وما الذي لا ينبغي فعله عند خوض نقاشات صعبة لا تبدأ حوارًا دون أن تفكر فيه أولًا. لا تجعل احترافية العمل تطغى على الجانب الإنساني. لا تصل الاجتماع وقد جهزت قائمةً بالمتطلبات أو مواعيد نهائية للآخرين. لا تطرق بيدك على جانبك من طاولة الحوار. فكرّ بما ستقوله وكيف ستقوله. تمتع بالليونة وعبر بانفتاح عما تشعر به. كن جزءًا من الحل واتخذ بعض القرارات. تحقق عند نهاية الحوار أنك على نفس الضفة التي يقف عليها فريقك. حضر لنقاش منتج إن كنت تعرف جيدًا أنك مقبل على نقاش جاد وبناء مع موظفيك فخذ وقتًا كافيًا في التخطيط لما ستقوله. ابدأ بوضع مواعيد لقاءات فردية وثبت النقاط التي ستتحدث فيها ضمن جدول اللقاء كي يتضح للطرفين ما يناقشانه. استخدم جدول لقاء مشترك مع الموظفين لكي يتمكنوا من إضافة نقاط تخصهم للحديث بشأنها. احرص أن تصل إلى الاجتماع مستعدًا، وامنح موظفيك وقتًا ليستعدوا بدورهم. دوّن النقاط المفتاحية التي تريد معالجتها وتدرّب على طريقة تقديمها، وحاول البحث عن شخص موثوق كي تتمرن أمامه قبل خوض النقاش (لكن انتبه إلى طبيعة المعلومات التي تشاركها معه)، إذ من السهل أن نسترسل في الحديث وننسى التفكير في وقعه على الآخرين. ضع نفسك مكانهم: تصور نفسك لبرهة أنك في الطرف المقابل، ثم فكر بثلاث أو أربع أحاسيس قد تنتاب الشخص الذي تخاطبه، الخيبة؟ الإحباط؟ الغيرة؟ الإحراج؟ وتذكر أنك لا تعرف كيف يفكر أو بماذا يشعر، ثم ادخل بعدها إلى الاجتماع سعيًا لفهم الموضوع. ثلاثة نصائح عند خوض نقاشات صعبة إليك ثلاثة نصائح مهمة لتتبعها عند خوض النقاشات الصعبة. 1. ركز على الوقائع لا على العواطف كجزء من الخطة عليك أن تعزل الوقائع المتعلقة بالقضية عن مشاعرك اتجاهها. وهذا الأمر يساعد على بقاء الحوار مركزًا ومضبوطًا. قد تحضر معك أيضًا بعض الملاحظات التي تساعدك في الإجابة على نقاط مثل: ما الذي طرأ حتى اضطررنا إلى هذا الحوار؟ ما هو تأثيره؟ ما الذي ينبغي تغييره أو ضبطه؟ ما الذي سيحدث إن لم نتصرف؟ عندما تعرف الأجوبة على هذه الأسئلة ستتجنب الكلام الافتراضي أو الانجراف بعيدًا عن الموضوع. وهكذا ستتكلم في النقاط المهمة دون أن تصل إلى جدال عقيم حول شيء موضوعي. لا تتجاهل أحاسيسك: من الطبيعي أن تطغى العواطف في حالات التوتر أو وجود مسألة هامة على المحك، لكن عندما تتفهم الحقائق التي بين يديك فستسهل عليك السيطرة على مشاعرك وتبقي الحوار في المسار الصحيح. 2. كوِّن جوًا من الصراحة عندما يدخل أحد الطرفين أو كلاهما في النقاش مع تحفظات أو سلوك سلبي، فسيعيق ذلك الوصول إلى نتيجة إيجابية. لهذا عليك أن تبدأ الحوار بنية إيجابية، سواءً لحل نزاع أو لفهم الموضوع أو لإنشاء خطة عمل. يمكنك أن محاولة طرح أسئلة توجيهية في اللقاءات الفردية مع أعضاء فريقك لتعطيهم الفرصة في التعبير عن أنفسهم. من الجيد أيضًا تعزيز ثقافة الصراحة والإنفتاح مع موظفيك باستمرار، إذ تتيح اللقاءات الفردية المتكررة مع أفراد الفريق التواصل مع القواعد ومناقشة الأمور الأكثر أهمية. 3. حاول أن يكون الحل جماعيا لا بدّ في نهاية أي حوار صعب أن تؤسس أرضيةً مشتركةً مع موظفيك لتوضيح التوقعات قبل المتابعة. قد تكون التصرفات اللازمة محضرةً مسبقًا في بعض الأحيان، فإن لم تكن جاهزة، فيُفضَّل مناقشة الإمكانيات المتاحة مع فريقك والاتفاق على ما هو منطقي ومجدٍ منها. بتعبير آخر، لا تُظهر أية دلالات على معرفتك المسبقة بالخيار الصحيح، أو أنّ هذا الخيار سيأتي من تلقاء ذاته ضمن تقرير المدير. من أفضل الطرق التي تجعل منك مثالًا للقيادة هي ظهورك كمدير يجعل نفسه جزءًا من الحل. وعندما يرى موظفوك أنك تنجز تغييرات ملموسة فسيتّبعونك. اطرح أي فكرة تخطر في بالك وكن منفتحًا على آراء موظفيك ومستعدًا لتحمل مسؤولية بعض القرارات التي عليك اتخاذها. متابعة تنفيذ نتائج الحوارات الصعبة لا فائدة من وضع نقاط للعمل إن لم نتابع تنفيذها؟ فالمتابعة الدقيقة لها أهمية خاصة في التأكد من حل المشكلة. لهذا عليك أن تضع موضوعًا للحوار الشخصي لتتأكد من أنّ هذا الحديث سيقود إلى النتيجة المطلوبة. بعيدًا عن متابعة الأمور العملية، احرص على متابعة الأمر على المستوى الشخصي أيضًا، فقد يكون الحديث في مواضيع حساسة أو مثيرة للجدل مزعجًا أو محرجًا، ومن المفيد أن تلتقي بموظفيك مجددًا بعد أن تهدأ الأمور. الأسئلة التي قد تطرحها عند متابعة نتائج الحوار: هل نبلي حسنًا في تنفيذ النقاط التي اتخذناها آخر مرة؟ هل يتبادر أي شيء إلى ذهنك فيما يتعلق بآخر لقاء شخصي؟ هل تتبادر إلى ذهنك أية أفكار أو أسئلة حول آخر نقاش؟ ما هو شعورك منذ آخر لقاء بيننا؟ أمثلة من الواقع عن حوارات صعبة نطبق في هذه الأمثلة أفضل ما يمكن تطبيقه في حالات مأخوذة من سيناريوهات حقيقية. حل النزاعات بين الزملاء "لقد استشعرت بعض التوتر في جلسة عصف الدماغ التي عقدها الفريق وأشعر بفضول لأسمع وجهة نظرك حول الموضوع. من الطبيعي أننا لا نرى بعضنا دائمًا وجهًا لوجهٍ، لكن الاحترام قيمة لا تفاوض عليها. لذلك أريد أن أتاكد من إنهاء أية احتكاكات قبل أن تؤثر سلبًا على تعاوننا". التعامل مع سلوك صعب "لاحظت أنك لا تقدم دائمًا حلًا بديلًا عندما تعارض أفكار الآخرين. لا بأس ألا تملك أجوبة، لكنك تبدو وكأنك تُخرس الآخرين. أردت طرح هذا الموضوع معك لأني أعرف أنك مهتم بنجاح الفريق ككل. هل بإمكاننا جعل هذه الحوارات أكثر فائدة؟". مخاطبة موظف ضعيف الأداء "لم تستطع تحقيق الأهداف الموكلة إليك مؤخرًا، وأرغب في التأكد من توفر كل ما تحتاجه لإنجازها. هل توجد أية نقاط تحتاج إلى مراجعتها سويًا؟ هل تعتقد أنك تحتاج إلى جلسات تدريبية؟ لنطلع على المهام الموكلة إليك كي نتأكد من قدرتك على تحديد أولوية المهام الأكثر تأثيرًا". التخلي عن أحد أعضاء الفريق ليس من السهل إطلاقًا عندما تدير فريقًا التخلي عن أحد أعضاءه. ولمواجهة حالة كهذه قد تتصرف على النحو التالي: "يؤسفني أن أقول بأننا سنستغني عن خدماتك في الفريق لأن [اذكر السبب]. أشكرك على كل ما قدمته خلال [اذكر الفترة التي قضاها في هذه الوظيفة] ونقدّر كثيرًا العمل الذي قدمته لفريقك وللشركة". مناقشة موظف في أمر شخصي "أخبرني إن كنت لا ترغب في مناقشة الأمر، لكنني لاحظت بأنك تبدو محبطًا هذه الأيام. هل تشعر بأنك توازن بين حياتك الخاصة وعملك؟ هل هناك شيء ما ينبغي أن أعرفه أو بإمكاني مساعدتك به؟ تأكد أنني بجانبك دائمًا كمدير وكصديق". إنّ مناقشة مواضيع صعبة في العمل أمر مرهق لأي مدير، وكلما كنت مرتاحًا أكثر، كانت نتائج هذه النقاشات أكثر نجاحًا. إن استطعت أن تبقى منفتحًا وأن تعالج الحوارات الشخصية المحرجة بشيء من الفضول، فستضع موظفيك على طريق النجاح كفريق بكل تأكيد. ترجمة -وبتصرف- للمقال Having difficult conversations a manager’s guide to tough talks لصاحبته Nora St-Aubin. اقرأ أيضًا دليل مختصر لتحسين المحادثات الثنائية مع الموظفين في شركتك الاجتماعات الفردية: دليلك الشامل لإجراء محادثات فعالة الدليل الشامل للمديرين حول كيفية إعطاء الملاحظات للموظفين نزاعات الفرق: أربع طرق لحل الخلاف الذي يدمر فريقك
-
يزداد اعتماد البشر على اﻵلات يومًا بعد يوم، ويبتكر اﻹنسان وسائل مختلفة لتطوير هذه اﻵلات وتسهيل قيادتها والتعامل معها. وقد راكم البشر معارفهم في بناء اﻵلات وتسخيرها منذ القدم، لكننا سنقفز في الزمن إلى أواخر القرن التاسع عشر وبداية القرن العشرين، إذا ساهمت جهود العديد من المخترعين وعلى رأسهم أديسون وتيسلا في وصول الطاقة الكهربائية (التي اكتشفت واستخدمت سابقًا) بشكل محدود إلى المنازل واخترعت المحركات الكهربائية. وقد تمّيزت هذه الطاقة بنظافتها وسهولة التحكم بها، وتوجيهها نحو المكان المطلوب بالكمية المطلوبة، وإمكانية وصل وفصل التجهيزات التي تعمل عليها عن مصادر التغذية بشكل آني. والكهرباء هي اﻵن بلا شك العماد اﻷساسي لحضارتنا الراهنة بكل تفاصيلها. وقد رافق الاستخدام المتزايد لهذه الطاقة نشوء العديد من العلوم القائمة عليها كعلم اﻹلكترونيات، وعلوم توليد الطاقة الكهربائية ونقلها وتوزيعها، وعلوم التحكم اﻵلي اﻹلكتروني، وصولًا إلى بناء الروبوتات. ومع تطور تلك العلوم ظهرت الحاجة إلى فنيين ومختصين ومهندسين وعلماء لصيانة المنظومات التي تعتمد على الطاقة الكهربائية وتطويرها، وتطوير أساليب التحكم بالتجهيزات الكهربائية من أجل قيادتها بالشكل اﻷمثل واﻷكثر فعالية. ويمر هؤلاء بمسارات تعليمية وتقنية مختلفة لبلوغ المستوى العلمي والفني المناسب لتأدية عملهم. وحتى لو تباعدت هذه المسارات التعليمية عن بعضها في مرحلة ما نظرًا لتشعب علوم الطاقة الكهربائية وتجهيزاتها، فهنالك مسارات أساسية لا بد أن يمر بها جميع المهتمين في هذا المجال والتي تشكل حجر اﻷساس في مسيرتهم العلمية والفنية وهذا ما سنناقشه في مقالنا. هذا المقال موجّه إلى كل الطلاب اليافعين الراغبين في احتراف علوم اﻹلكترونيات والتحكم باﻵلات والروبوتات وإلى أهاليهم. إذ سنناقش فيه المسارات التعليمية التي توصلهم إلى المكان المطلوب وما الذي عليهم معرفته، وما الفوائد المتوقعة من هذه المسارات وكيف نجنيها؟ حتى تتكون لديهم صورة واضحة عن هذا الاختصاصات ونضعهم على الطريق الصحيح للانطلاق. المسارات الرئيسية لتعلم التحكم المبرمج والروبوتات نستعرض سريعًا في هذه الفقرة أهم المسارات التعليمية وفق التسلسل المنطقي الصحيح، ثم نفصّل فيها في فقرات تالية: مسار تعلم اﻹلكترونيات. مسار التحكم الصناعي وقيادة اﻵلة. مسار اﻹلكترونيات المبرمجة والمتحكمات المصغّرة. مسار وحدات التحكم المتكاملة والحواسب المصغّرة. مسار علوم الروبوت. مسار تعلم اﻹلكترونيات علم اﻹلكترونيات مجال واسع وشديد التشعب ودراسته ليست بالأمر بالسهل وهو يحمل الكثير من التحديات، لكنه مع ذلك علم ممتع وتخصصاته مطلوبة في كل مجالات حياتنا المعاصرة. ما هو علم اﻹلكترونيات يُعرف علم اﻹلكترونيات بأنه علم يدرس ويبحث في إنتاج عناصر كهربائية قادرة على التحكم بالتيار الكهربائي وتوجيهه نحو عناصر محددة في المكان المحدد والتوقيت المحدد. ولكل عنصر إلكتروني وظيفة محددة: فمنها ما يخفض شدة التيار الكهربائي، ومنها يرفعه، ومنها ما يمتص الطاقة الكهربائية ويحوّلها إلى ضوء، وأخرى تمتص الضوء وتحوّله إلى تيار كهربائي. لكن ما الفائدة من كل ذلك؟ للإجابة عن هذا السؤال سنتخيل أنك تريد أن تتحكم بشدة الإضاءة في غرفتك، فقد تريدها أن تكون قوية عندما تجلس إلى طاولة الدراسة وأن تكون منخفضة جدًا عندما تجلس إلى شاشة الحاسب لتلعب إحدى ألعابك المفضلة، فكيف يساعدنا علم اﻹلكترونيات؟ اﻷمر بغاية البساطة. إذ تتعلق شدة اﻹضاءة بزيادة شدة التيار الكهربائي الي يصلها، وهكذا نستخدم مثلًا عنصرًا يُدعى "مقاومة متغيرة Variable resistor". يأتي هذا العنصر على شكل مفتاح يمكن تدويره. فعندما يدور هذا المفتاح باتجاه معين يزيد تدفق التيار عبره إلى اﻹضاءة وتزيد شدتها وإن أدرته بالاتجاه المعاكس يعيق تدفق التيار الكهربائي وتقل شدة اﻹضاءة. هل ذكرّك هذه اﻷمر بشيء ما؟ تمامًا صنبور المياه! لكن ماذا لو أردت أن تفعل ذلك دون أن تضطر إلى مغادرة اللعبة التي تستمتع بها كثيرًا وتصل إلى المفتاح المثبّت على جدار الغرفة، هل هناك حل في عالم اﻹلكترونيات؟ بالتأكيد يوجد حل! مخطط لدائرة إلكترونية كيف تبدأ رحلتك في تعلم اﻹلكترونيات عليك في المرحلة اﻷولى أن تتعلم بعض الأساسيات التي سنلخصها في النقاط التالية: التعرف على مفاهيم ضرورية مثل الجهد الكهربائي، والتيار الكهربائي، وحركة التيار في اﻷسلاك. إجراء بعض الحسابات الكهربائية البسيطة لتحديد العناصر المناسبة لدائرتك اﻹلكترونية. تصميم بعض الدوائر البسيطة التي تهدف إلى تعليمك التعامل مع العناصر اﻹلكترونية اﻷساسية. اختيار مصادر التغذية الكهربائية التي تلزمك والطريقة الصحيحة في توصيلها مع الدائرة. تعلّم قراءة مواصفات العناصر اﻹلكترونية اﻷساسية، والطريقة الصحيحة في توصيلها مع مصدر التغذية الكهربائية. استكشاف اﻷخطاء الناتجة في الدوائر اﻹلكترونية البسيطة. استخدام بعض أجهزة القياس اﻷساسية مثل "المقياس متعددة الوظائف Multi-meter" الذي تستخدمه في قياس الكثير من المقادير الكهربائية مثل الجهد، وشدة التيار، ومقاومة العناصر، وتفقد صحة التوصيل بين عنصرين، وتحديد نقاط الانقطاع في الدائرة وغيرها. أما في المرحلة الثانية فيُفترض بك أن تتعلم العمل مع المفاتيح اﻹلكترونية بأنواعها المختلفة، وهي عناصر تمنع أو تسمح للتيار الكهربائي بالمرور وفق شروط كهربائية خاصة، ولها أهمية كبيرة في التحكم بدوائرك. ثم تتعرف في المرحلة الثالثة على العناصر اﻹلكترونية المتكاملة وهي دوائر إلكترونية كاملة لها وظيفة أو عدة وظائف، تُصنّع ضمن غلاف واحد لا يُرى منها سوى بضعة أرجل تربطها مع بقية العناصر. ومع تقدمك في هذا المسار ستتعلم أسماء ووظائف العديد منها وتتعلم طريقة استخدامها. تساعدك الكثير من البرامج الحاسوبية الخاصة والتي تُدعى بالمحاكيات في تصميم الدوائر اﻹلكترونية، والتأكد من سلامة عملها قبل أن تنجزها في الواقع. إذ يسهّل عليك تعلم أحد هذه البرامج إجراء الحسابات وتجربة العناصر التي تعتقد أنها مناسبة قبل أن تتخذ قرارك النهائي. ما الذي تتوقعه من تعلم اﻹلكترونيات؟ ستكون في نهاية هذا المسار قادرًا على: البحث عن العناصر التي تحتاجها من خلال شبكة اﻹنترنت. فهم ميزات العناصر اﻹلكترونية، وطريقة توصيلها من خلال القراءة الصحيحة، وفهم ورقة المواصفات الخاصة data sheet بالعنصر والتي تقدمها الجهة المصنعة له. تنظيم مجموعتك الخاصة من العناصر اﻹلكترونية التي أتقنت استخدامها وألفت طريقة توصيلها وحل مشاكلها. فهم الكثير من الدوائر اﻹلكترونية المختلفة وتحليل عملها للاستفادة من اﻷفكار المطبقة ضمنها. التواصل الفعّال مع أعضاء المجتمعات الافتراضية التي تهتم باﻹلكترونيات على شبكة اﻹنترنت لتجد حلولًا للمشكلات التي تعترضك. تحليل وتطبيق الكثير من المشاريع المميزة الجاهزة والتعلم منها لتطوير أفكارك. مسار التحكم الصناعي وقيادة اﻵلة بعد أن تتعلم أساسيات اﻹلكترونيات وربما في نهاية المرحلة اﻷولى وبداية الثانية، ستكون قادرًا على الانطلاق في هذا المسار المميز على الصعيدين العلمي والمهني. وإن كنت ملمًا بأساسيات الطاقة الكهربائية وتوصيل العناصر الكهربائية، يمكنك الانطلاق في هذا المسار على التوازي مع مسار تعلم اﻹلكترونيات. ماذا نقصد بالتحكم وقيادة اﻵلات؟ نقصد بقيادة اﻵلات القدرة على تشغيلها في الوقت المناسب ﻹنجاز عمل محدد وخلال فترة محددة، تخيّل مثلًا آلة تغليف ألواح الشوكولا، ما الذي قد يحدث إن لم تكن حركة هذه اﻵلة دقيقة؟ وما الذي قد يحدث إن لم تنجز تغليف اللوح قبل وصول اللوح التالي؟ ماذا لو كان عملها مرتبطًا بعمل آلة تسبقها؟ إن اﻹجابة عن هذه اﻷسئلة هو جوهر هذا المسار التعليمي. تتكون اﻵلات على مختلف أنواعها من محرّكات وأجزاء متحركة أخرى ترتبط بها، وتتكامل هذه اﻷجزاء لأداء وظيفة معينة، وعندما تجتمع عدة آلات تشكل خطًا آليًا، وهكذا سيكون التحكم بهذه اﻵلات وقيادتها بالطريقة الصحيحة أمرًا جوهريًا في نجاح هذا الخط اﻵلي. وللتحكم الصناعي نوعان: اﻷول تقليدي يعتمد على عناصر كهربائية مخصصة لأداء كل وظيفة من وظائف اﻵلة، والثاني مبرمج ترتبط فيه هذه اﻵلات بوحدات خاصة تُدعى وحدات التحكم المبرمجة تقودها معًا عن طريق برمجيات قيادة خاصة تطوّر خارج وحدات التحكم ثم تنقل إليه، وباﻹمكان تعديلها في أي وقت دون الحاجة إلى إيقاف خط اﻹنتاج لفترات طويلة. إذًا فالتحكم الصناعي هو قيادة اﻵلات واﻷجزاء المتحركة اﻷخرى ﻷداء وظائفها بالشكل اﻷنسب، أما كيف ستنطلق في تعلم هذه المسار، فهذا ما نناقشه تاليًا. كيف تبدأ رحلتك في مسار التحكم الصناعي وقيادة اﻵلات؟ عليك في المرحلة اﻷولى أن تتعلم بعض النقاط اﻷساسية ونلخصها كالتالي: التعرف على نوعي التيار الكهربائي المستخدمان في تغذية اﻵلات، وهما التيار المستمر والتيار المتناوب والتعرف على خصائصهما ومجالات استخدامهما. الاطلاع على أنواع المحركات المختلفة، وطريقة تشغيلها وإطفائها، ونوع التغذية الكهربائية التي تحتاجها. التعرّف على عناصر فصل ووصل أجهزة الحركة وعناصر التوقيت واﻷزرار والمبدلات وغيرها من العناصر اﻷساسية في دوائر التحكم الصناعي. تعلم توصيل دوائر بسيطة للتحكم في تشغيل محركات صغيرة أو منظومات إضاءة بسيطة. تعلم قراءة مخططات دوائر التحكم والتمييز بينها وبين مخططات دوائر التغذية الكهربائية. تعلم استخدام عناصر حماية الدوائر مثل قواطع الحماية والمنصهرات fuse. ستألف في نهاية هذه المرحلة الكثير من اﻷفكار التي تتعلق بالمحركات وأساليب فصل ووصل الطاقة عنها وعن اﻷجزاء اﻷخرى من اﻵلة وطريقة التحكم بها. توسّع المرحلة الثانية معارفك من خلال الاطلاع على تجهيزات مراقبة التغذية الكهربائية وعلى الحساسات الصناعية التي تعطيك معلومات هامة عن وضع اﻵلة أو أجزائها مثل حساسات تقدير المسافة، وحساسات اكتشاف الحركة وغيرها. وهي عناصر غاية في اﻷهمية ولها دور مفصلي في تحقيق التشغيل المتوازن للآلة. وستطلع في المرحلة الثالثة على أسلوب التحكم المبرمج، وفهم آلية عمل وحداته وطرق توصيلها واستثمارها مثل وحدات "الدوائر المنطقية القابلة للبرمجة PLC". أما تعلّم برمجة هذه الوحدات فهذا أمر اختياري ويتعلق بمدى جديتك في تطوير مهنتك. فقد تطلب من مختصين أن يبرمجوا لك هذه الوحدات لتنفيذ الوظائف التي تريدها ثم تقوم بتركيبها ووصلها، وقد تحاول أن تتعلم برمجتها بنفسك وهنا لابد من مرحلة رابعة تتعلم فيها أساسيات البرمجة عمومًا ثم تنتقل إلى اﻷساليب البرمجية الخاصة بالوحدات المبرمجة. وحدة دائرة منطقية قابلة للبرمجة PLC ما الذي تتوقعه من تعلم التحكم الصناعي وقيادة اﻵلات؟ ستكون قادرًا عند إكمال هذا المسار التعليمي على: فهم أساليب التحكم باﻵلات وقيادة اﻷجزاء المتحركة. تمييز مخططات التحكم عن مخططات التغذية الكهربائية للآلات وقرائتها جيدًا. تمييز معظم عناصر التحكم الكهربائي التقليدي في الدوائر الكهربائية. اكتشاف أخطاء دوائر التحكم وحل مشاكلها. إنجاز دوائر تحكم خاصة بك لقيادة منظومات آلية محدودة. استخدام وحدات التحكم المبرمج وتوصيلها بالشكل الصحيح. فهم طريقة برمجة وحدات التحكم. كتابة برامج كاملة لقيادة اﻵلات إن تابعت حتى نهاية المرحلة الرابعة من هذا المسار. مسار اﻹلكترونيات المبرمجة والمتحكمات الصغرية يتابع في هذا المسار كل من يريد تحقيق الأهداف التالية: يريد التعمق في التحكم اﻵلي المبرمج. يرغب في فهم تفاصيل تصميم وحدات التحكم المبرمج. أكمل مسار اﻹلكترونيات ويريد تهيئة نفسه للعمل على الروبوتات دون المرور بمسار التحكم الصناعي. أكمل المسارين السابقين ويريد تعزيز قدراته في التحكم باﻵلات ومخاطبتها وصولًا إلى العمل مع الروبوتات وبناء أنظمة انترنت اﻷشياء. ما هي اﻹلكترونيات المبرمجة والمتحكمات الصغرية؟ هي دوائر إلكترونية متكاملة يمكن أن تتغير وظيفتها وفقًا لبرنامج مخصص يُكتب خارجها ثم ينقل إليها. وتقسم هذه الدوائر عمومًا إلى صنفين عامين أولهما دوائر متكاملة تُبرمج كي تؤدي وظيفة إلكترونية محددة مثل "مصفوفة البوابات القابلة للبرمجة FPGA"، وثانيهما دوائر تتتحكم ببقية العناصر الكهربائية واﻹلكترونية وتتبادل المعلومات مع محيطها وتُدعى بالمتحكمات الصغرية Micro-controller وهي بمثابة دماغ مصغّر يتحكم بالدائرة اﻹلكترونية الخارجية كما نريد. تبدأ هنا ملامح التخاطب مع اﻵلة بالظهور، فهذه المتحكمات قادرة على التواصل مع محيطها وتحسس التغيرات فيها. وستتمكن من استخدام هذه المتحكمات مثلًا في تصميم دوائر تفهم إشارات يدك أو تحلل صوتك لتأمر بعض الطرفيات المتصلة بدائرتها بتنفيذ عمل معين مثل فتح باب أو تشغيل جهاز ما. لوحة تشغيل متحكم صغري يظهر المتحكم في وسطها كيف تبدأ رحلتك في تعلم اﻹلكترونيات المبرمجة؟ عليك في المرحلة اﻷولى تعلّم البرمجة، والتفكير كمبرمج، فلن تستطيع إكمال هذا المسار دون أن تتقن أساسيات البرمجة والتفكير المنطقي، ستجد الكثير من لغات البرمجة والكثير من اﻵراء المتضاربة حول اختيار اللغة الأفضل، لكني أنصحك بتعلم لغة بايثون إذ تُعد عمومًا من اللغات القوية وسهلة التعلم نتيجة لصياغتها القريبة من صياغة الجمل في اللغة اﻹنكليزية، وإن أردت فعلًا احتراف هذا المجال من أوسع أبوابه فعليك بلغتتي C أو ++C فهما أكثر اللغات استخدامًا في هذا المجال واﻷمر يعود إليك أولًا وأخيرًا. تحتاج في المرحلة الثانية إلى التعرّف على أقسام المتحكمات الصغرية وميزاتها المختلفة، وما الذي تقدّمه لك كل شركة مصنعة وكل عائلة من خلال أوراق المواصفات الخاصة بهذه المعالجات. وهكذا ستكون قادرًا على اختيار المعالج الذي يناسب مشروعك. ولا بد في هذه المرحلة من تفهم طريقة توصيل المتحكم مع بقية عناصر الدائرة اﻹلكترونية وتتدرب على كتابة برامج بسيطة لهذا المتحكم وتعرف كي تنقلها إليه. ولا تنس أن مسار تعلّم اﻹلكترونيات ضروري جدًا في هذه المرحلة. وعليك في المرحلة الثالثة تعلّم طريقة وصل بعض الطرفيات اﻷساسية إلى المتحكم مثل لوحات المفاتيح وبعض أنواع شاشات العرض لتتمكن من إدخال بعض القيم وإخراج نتائج مرئية لما ينفذه برنامجك. وغالبًا ما تستفيد في هذه المرحلة من مكتبات برمجية جاهزة للتحكم بأكثر الطرفيات شيوعًا وكل ما عليك حينها هو دمجها مع شيفرتك وتعلم كيفية استخدامها. أما المرحلة الرابعة واﻷخيرة فهي مرحلة تعلّم العمل على المحاكيات، وهي برمجيات تقرأ شيفرتك وتريك نتيجة تنفيذها قبل ترحيلها إلى المتحكم وبالتالي ستوفّر عليك الوقت والجهد وخاصة عند تصميم دوائر أكثر تعقيدًا. هذا المسار شديد الخصوصية، فلكل متحكم طريقة مختلفة في البرمجة وطريقة مختلفة في نقل البرنامج إليه وبيئات عمل حاسوبية مختلفة لبرمجته، لكنها تتشابه إجمالًا في الخطوط العريضة ولن يصعب عليك في نهاية هذا المسار من تعلم برمجة متحكمات أخرى وبأقل جهد. ما الذي تتوقعه من تعلم برمجة المتحكمات الصغرية؟ ستكون قادرًا عند إكمال هذا المسار التعليمي من: اختيار المتحكم الصغري الملائم لمشروعك. تصميم الدائرة اﻹلكترونية اللازمة لاستثمار وتشغيل المتحكم. التعامل مع الحساسات والتحكم بتشغيل الكثير من التجهيزات الكهربائية. التعرف على بروتوكولات نقل المعطيات بين اﻷجهزة واستخدامها للتواصل مع التجهيزات المختلفة. كتابة برمجيات صحيحة ونقلها إلى المتحكمات لتنفيذها. مسار وحدات التحكم المتكاملة والحواسب المصغّرة يأتي هذا المسار متممًا ومكملًا للمسارين السابقين، وهو موجّه لمين يريد احتراف بناء أنظمة تحكم متكاملة سواء على الصعيد الصناعي كأنظمة إدارة المعامل أو على الصعيد التقني مثل بناء الروبوتات والتجهيزات اﻹلكترونية الذكية وأجهزة القياس والتحليل اﻵلي (وتُدعى أنظمة هذه التجهيزات باﻷنظمة المدمجة embedded systems) ما هي الوحدات المتكاملة وما هي الحواسب المصغّرة؟ تُعرّف وحدة التحكم المتكاملة control module بأنها دائرة إلكترونية لها تصميم محدد تضم متحكمًا أو معالجًا مصغرًا يحلل البيانات التي تصل إليه عبر نقاط محددة تُدعى نقاط الدخل ويصدر بيانات أو إشارات إلى نقاط أخرى تُدعى نقاط الخرج. لن تحتاج في الوحدات المتكاملة إلى الغوص في تفاصيل المعالج أو دائرته بل كل ما عليك فعله هو معرفة كيفية التعامل مع نقاط الدخل والخرج وكتابة برامج للتعامل معها مباشرة. نذكر من هذه الوحدات على سبيل المثال لوحات أوردوينو Arduino الشهيرة. تختلف الحواسب المصغّرة عن وحدات التحكم المتكاملة بأنها تمتلك نظام تشغيل حاسوبي وتستطيع الارتباط بطرفيات الحاسوب مثل شاشات العرض ومكبرات الصوت، كما تُكتب برامجها وتُنفّذ كما تكتب برامج الحاسوب تمامًا. لكنها تختلف عن الحواسب بوجود نقاط الدخل والخرج التي يمكنك التحكم فيها من خلال برامجك وربطها بما تشاء من الطرفيات وفق أسس محددة. من أشهر الأمثلة عليها الحاسوب المصغّر راسبيري باي Raspberry pi. الحاسوب المصغّر راسبيري باي 4 كيف تبدأ مسار الوحدات المتكاملة والحواسب المصغّرة؟ لابد أولًا أن تكمل مسار علم اﻹلكترونيات فهو مسار أساسي جدًا، ثم تنتقل بعد ذلك إلى مسار الإلكترونيات المبرمجة والمتحكمات الصغرية، إذ يساعدك هذا المسار على فهم طريقة كتابة البرامج المخصصة لوحدات التحكم المتكاملة والحواسب المصغّرة دون أدنى جهد. لكن إن قررت تخطي هذا المسار فلا بد على اﻷقل من تعلم إحدى لغتي البرمجة بايثون أو ++C، لكنك ستبذل جهدًا كبيرًا لفهم النقاط التي تتعلق ببرمجة المتحكمات الصغرية. إن العمل مع وحدات التحكم المتكاملة أكثر صعوبة من العمل مع الحواسب المصغّرة لأنه عليك في الواقع أن تبني نظام القيادة الخاص بها بنفسك بينما ستتعامل حرفيًا مع نظام تشغيل جاهز ومتطور في الحواسب المصغّرة. ننصحك بداية باقتناء لوحة أوردوينو (أيًا كان طرازها) ثم تتعلم طريقة توصيل الطرفيات إليها وكيفية كتابة برامج لها ونقلها إلى المتحكم الذي يقود اللوحة. بإمكانك التعرف على اللغة المستخدمة في برمجة هذه اللوحة من خلال موسوعة حسوب التي تقدم توثيقًا عربيًا متكاملًا لها. تساعدك أيضًا المحاكيات الحاسوبية في التأكد من صحة شيفرتك قبل نقلها إلى الوحدة. ويمكنك البحث في اﻹنترنت عن مشاريع جاهزة بسيطة والاشتراك في المناقشات وطرح اﻷسئلة ضمن مجتمعات أوردوينو الافتراضية وستجد بالتأكيد حلولًا لمعظم مشاكلك. وإن قررت العمل مع الحواسب المصغرة، بإمكانك شراء حاسوب راسبيري باي ثم العمل ضمن سلسلة مقالات "دليل راسبيري باي" التي تقدمها أكاديمية حسوب عن طريقة استخدام هذا الحاسوب من الصفر وحتى مرحلة تطبيق المشاريع المختلفة، كما ستجد في اﻷكاديمية كمًا جيدًا من المقالات ومقاطع الفيديو العملية التي تساعدك على التعلم بصورة أفضل. ما الذي تتوقعه من تعلم برمجة وحدات التحكم المتكاملة والحواسب المصغّرة؟ ستكون قادرًا عند إكمال هذا المسار التعليمي من: اختيار وحدة التحكم المتكاملة أو الحاسوب المصغّر الذي يلبي احتياجك. وصل الطرفيات إلى وحدات التحكم بالشكل الصحيح. كتابة برامج وتطبيقات لتنفيذ مختلفة اﻷفكار التي تتعلق بالتحكم بالوسط المحيط وتبادل البيانات معه. تصميم وتحريك روبوتات بسيطة. تعلم الأساسيات اللازمة لبناء منظومات إنترنت اﻷشياء IoT والروبوتات المتقدمة. مسار الروبوتكس يشمل علم الروبوتات علوم الحركة واﻹلكترونيات واﻵلات والبرمجة، فغايته اﻷساسية التخاطب الفعال مع آلة تستطيع من تلقاء نفسها تنفيذ أعمال متكاملة عالية التعقيد بناء على برمجيات صممت خصيصًا لإنجاز تلك اﻷعمال. كيف تبدأ مسار تعلم تصميم الروبوتات وبرمجتها عليك في المرحلة اﻷولى أن تكمل مسار علم اﻹلكترونيات ثم تنتقل إلى مسار التحكم الصناعي وقيادة اﻵلة فالروبوتات في معظمها آلات متحركة ولا بد من التعامل مع المحركات والحساسات التي تساعدها على التوجه والحركة. أتقن في المرحلة الثانية مسار المتحكمات المصغّرة وكيفية التخاطب مع الطرفيات المختلفة مثل المحركات الصغيرة والحساسات. أما إذا أردت استخدام طرفيات جاهزة مثل أنظمة قيادة محرّك متكاملة أو أنظمة حساسات جاهزة، فعليك الانتقال إلى مسار الوحدات المبرمجة المتكاملة وستجد كمًا هائلًا من الطرفيات التي تساعدك على بناء الروبوتات. ولا بد في المرحلة الثالثة من الإطلاع على طريقة عمل بعض الروبوتات الجاهزة واﻷجزاء المكوّنة منها وطريقة برمجتها ثم تجريب بعض المشاريع الجاهزة كي تطلع على التجهيزات الأكثر استخدامًا وطريقة توصيلها وعملها، وسيكسبك ذلك خبرة عملية ويساعدك في تجنب الكثير من اﻷخطاء التي يقع فيها المبتدئين. في الواقع تتشعب علوم الروبوتات وأنواعها كثيرًا ولكل منها طريقة عمل محددة وأسلوب برمجة محددة، وللاطلاع أكثر على هذا الموضوع عُد إلى مقال "دليلك الشامل إلى برمجة الروبوت" فستجد فيه كل ما تحتاجه حول تعلم برمجة الروبوت. صورة تمثيلية لروبوت متحرك ماذا تتوقع من تعلم الروبوتكس ستكون قادرًا عند إكمال هذا المسار التعليمي من: فهم آلية عمل الروبوتات بأنواعها. استخدام الروبوتات بفعالية أيًا كان نوعها. برمجة رويوت معين لينفذ أية أعمال تريدها. تصميم روبوتات بسيطة إلى متوسطة التعقيد من الصفر وبرمجتها. الخلاصة قدمنا في هذا المقال فكرة عن المسارات التعليمية التي تلزم أي شخص يرغب في احتراف العمل مع الأنظمة المدمجة والروبوتات وشرحنا فيها بشكل مبسط ما يضمه كل مسار وماهي المراحل التي يمر بها والنتيجة المتوقعة لما تعلمّه في نهاية كل مسار. مع ذلك، ليس من الضرورة التقيد تمامًا بالترتيب الذي اقترحناه لكنه النهج اﻷسلم لليافع الذي لا يمتلك شيئًا سوى العزيمة والالتزام! اقرأ أيضًا تجميع راسبيري باي والتحضير لاستخدامه إنشاء كتاب تفاعلي باستخدام سكراتش وحاسوب راسبيري باي تصميم وتنفيذ لعبة حسية تفاعلية باستخدام لوحة راسبيري باي بيكو تصميم وتنفيذ آلة موسيقية باستخدام لوحة راسبيري باي بيكو برمجة الروبوت: الدليل الشامل
-
تظهر أهمية لغات البرمجة في إنجاز مهام مكررة وبسرعة مثل الحسابات المتعددة، أو في الحالات التي ينبغي فيها إنجاز الكثير من اﻷعمال المتشابهة. لهذا سنخصص هذا المقال للحديث عن الحلقات في جافا سكريبت التي تعالج تلك اﻷمور. ننصحك قبل أن تبدأ العمل معنا في هذه السلسلة أن تطلع على: أساسيات علوم الحاسب أساسيات HTML أساسيات عمل CSS أساسيات لغة جافا سكريبت ما هي فائدة الحلقات؟ تتمحور فكرة الحلقة على تكرار الشيء ذاته باستمرار. قد تختلف الشيفرة قليلًا في كل جولة أو قد تبقى الشيفرة كما هي وتتغير قيمة المتغيرات فيها. مثال عن شيفرة تستخدم الحلقات لنفرض أنك تريد رسم 100 دائرة عشوائية ضمن العنصر <canvas> انقر الزر "Update" لتنفيذ الشيفرة مرة تلو اﻷخرى لترى مجموعات مختلفة من الدوائر): See the Pen Looping code1 by Hsoub Academy (@HsoubAcademy) on CodePen. إليك شيفرة جافا سكريبت التي تنفّذ المطلوب: const btn = document.querySelector("button"); const canvas = document.querySelector("canvas"); const ctx = canvas.getContext("2d"); document.addEventListener("DOMContentLoaded", () => { canvas.width = document.documentElement.clientWidth; canvas.height = document.documentElement.clientHeight; }); function random(number) { return Math.floor(Math.random() * number); } function draw() { ctx.clearRect(0, 0, canvas.width, canvas.height); for (let i = 0; i < 100; i++) { ctx.beginPath(); ctx.fillStyle = "rgba(255,0,0,0.5)"; ctx.arc( random(canvas.width), random(canvas.height), random(50), 0, 2 * Math.PI, ); ctx.fill(); } } btn.addEventListener("click", draw); مع أو بدون حلقة ليس عليك فهم الشيفرة بأكملها اﻵن، لكن ألق نظرة على الجزء الذي يرسم 100 دائرة في الشيفرة: for (let i = 0; i < 100; i++) { ctx.beginPath(); ctx.fillStyle = "rgba(255,0,0,0.5)"; ctx.arc( random(canvas.width), random(canvas.height), random(50), 0, 2 * Math.PI, ); ctx.fill(); } تُعيد الدالة random المعرّفة سابقًا رقمًا صحيحًا محصورًا بين الصفر و 1-x. أما الفكرة العامة فهي أننا نستخدم حلقة لتكرار الشيفرة السابقة 100 مرة، وكل مرة ترسم الشيفرة دائرة في مكان عشوائي من الصفحة. وتبقى كمية الشيفرة اللازمة لرسم الدائرة نفسها سواءً رسمنا 10 أو 100 أو 1000 دائرة، وما يتغير فقط هو عدد مرات التكرار، ولو لم نستخدم الحلقة كان لا بد من كتابة الشيفرة التالية في كل مرة نريد رسم دائرة: ctx.beginPath(); ctx.fillStyle = "rgba(255,0,0,0.5)"; ctx.arc( random(canvas.width), random(canvas.height), random(50), 0, 2 * Math.PI, ); ctx.fill(); وسيكون الأمر مملًا وصعب التنقيح والصيانة. استخدام الحلقات للتنقل ضمن مجموعات غالبًا ما تستخدم الحلقة للتنقل بين مجموعة من العناصر لتنفيذ أمر ما على كل عنصر. ومن اﻷمثلة على المجموعات نجد المصفوفات، إضافة إلى بُنى أخرى في جافا سكريبت مثل المجموعات Sets و الخرائط Maps. الحلقة for...of تعد هذه الحلقة أساسية في النقل ضمن عناصر مجموعة: const cats = ["Leopard", "Serval", "Jaguar", "Tiger", "Caracal", "Lion"]; for (const cat of cats) { console.log(cat); } في مثالنا هذا، تنص العبارة البرمجية for (const cat of cats) على ما يلي: إذا كانت cats مجموعة من العناصر استخرج العنصر اﻷول منها. إسناد قيمة هذا العنصر إلى المتغير cat ومن ثم نفّذ الشيفرة الموجودة ضمن القوسين المعقوصين {}. استخراج العنصر التالي وكرر الخطوة 2 حتى تصل إلى نهاية المجموعة. التابعين filter و map تقدّم جافا سكريبت حلقات مخصصة أيضًا للتنقل ضمن المجموعات، وسنشير إلى اثنتين منهما. الحلقة اﻷولى يمثلها التابع ()map الذي ينفّذ شيئًا محددًا على كل عناصر المجموعة ويعيد مجموعة جديدة تضم العناصر المتغيرة: function toUpper(string) { return string.toUpperCase(); } const cats = ["Leopard", "Serval", "Jaguar", "Tiger", "Caracal", "Lion"]; const upperCats = cats.map(toUpper); console.log(upperCats); // [ "LEOPARD", "SERVAL", "JAGUAR", "TIGER", "CARACAL", "LION" ] مررنا في مثالنا السابق دالة إلى التابع ()cats.map الذي يستدعيها مرة لكل عنصر من عناصر المصفوفة، ثم يمرر هذا العنصر إلى الدالة ويجمّع القيم العائدة عنها بعد كل استدعاء ضمن مصفوفة جديدة تحتوى عناصر المصفوفة القديمة بعد تغيير حالة أحرف هذه العناصر. [ "LEOPARD", "SERVAL", "JAGUAR", "TIGER", "CARACAL", "LION" ] أما الحلقة الثانية فيقدمها التابع ()filter الذي يختبر كل عنصر من عناصر المجموعة ويعيد مصفوفة جديدة تضم العناصر التي تحقق شرطًا معينًا: function lCat(cat) { return cat.startsWith("L"); } const cats = ["Leopard", "Serval", "Jaguar", "Tiger", "Caracal", "Lion"]; const filtered = cats.filter(lCat); console.log(filtered); // [ "Leopard", "Lion" ] يبدو الأمر مشابهًا للتابع ()map، ما عدا أن القيمة التي تعيدها الدالة التي نمررها هي قيمة منطقية. فإذا كانت true يُضمّن العنصر في المصفوفة الجديدة وإلا لن يُضمَّن. أما وظيفة الدالة في هذا المثال فهي اختبار العناصر التي تبدأ بالحرف "L"، لذا ستكون النتيجة كالتالي: [ "Leopard", "Lion" ] ولاحظ كيف يُستخدم التابعان ()map و ()filter في بعض اﻷحيان مع دوال وهذا ما سندرسه لاحقًا. وباستخدام الدوال يمكن إعادة كتابة الشيفرة السابقة كالتالي: const cats = ["Leopard", "Serval", "Jaguar", "Tiger", "Caracal", "Lion"]; const filtered = cats.filter((cat) => cat.startsWith("L")); console.log(filtered); // [ "Leopard", "Lion" ] حلقة forالمعيارية في مثالنا السابق الذي يدور حول رسم دوائر، لم يكن عليك التنقل بين عناصر مجموعة بل تكرار نفس الشيفرة 100 مرة. عليك في حالات كهذه استخدام الحلقة for التي لها الصياغة التالية: for (initializer; condition; final-expression) { // code to run } لدينا هنا: الكلمة المحجوزة for. ضمن القوسين التاليين ثلاثة عناصر تفصل بينها فاصلة: مهييء initializer: وهو متغيّر له قيمة عددية محددة سوف تزداد لتعدد المرات التي تثنفَّذ فيها الحلقة. ويُشار إليها أحيانًا بالعداد. شرط condition: ويحدد متى ستنتهي الحلقة. وهو عادة عبارة برمجية تستخدم عامل موازنة وتختبر إذا ما تحقق الشرط أم لا. عبارة إنهاء final-expression:وتُنفّذ أو تُقيَّّم في كل مرة تنهي الحلقة أحد التكرارات. وتُستخدم عادة لتزيد (أو تنقص) العداد لتقربه من النقطة التي لا يتحقق بعدها الشرط (العنصر الثاني). أقواس معقوصة تضم كتلة من الشيفرة التي تُنفَّذ في كل تكرار للحلقة. مثال عن حلقة for: حساب مربعات أعداد لنلق نظرة على مثال حقيقي لنتصوّر اﻷمر بوضوح أكثر: const results = document.querySelector("#results"); function calculate() { for (let i = 1; i < 10; i++) { const newResult = `${i} x ${i} = ${i * i}`; results.textContent += `${newResult}\n`; } results.textContent += "\nFinished!"; } const calculateBtn = document.querySelector("#calculate"); const clearBtn = document.querySelector("#clear"); calculateBtn.addEventListener("click", calculate); clearBtn.addEventListener("click", () => (results.textContent = "")); تُعطي الشيفرة السابقة الخرج التالي: See the Pen Looping code2 by Hsoub Academy (@HsoubAcademy) on CodePen. تحسب الشيفرة السابقة مربعات الأعداد من 1 إلى 9 وتطبع النتيجة، وتُستخدم الحلقة for كأساس لإجراء الحسابات. لنحلل بشيء من التفصيل العبارة for (let i = 1; i < 10; i++): المهيء (let i = 1) يبدأ العداد i من القيمة 1. ولاحظ ضرورة استخدام التعليمة let لأننا نعيد إسناد قيمة له عند كل تكرار. الشرط (i < 10) ويعني أن تستمر الحلقة في تكرار نفسها طالما أن i أصغر من 10. عبارة اﻹنهاء (++i) وتضيف واحد إلى العداد عند إتمام كل تكرار. نحسب داخل الحلقة مربع القيمة الحالية للمتغير i بالشكل :i*i، وننشئ عبارة نصية تضم العملية التي أجريناها ونتيجتها. كما نضيف السلسلة n\ إلى آخر السلسلة النصية كي تُطبع نتيجة التكرار التالي على سطر جديد. وما سيحدث اﻵن هو: خلال التكرار الأول ستكون i=1، وستكون النتيجة 1x1=1. خلال التكرار الثاني ستكون i=2، وستكون النتيجة 2x2=4. تستمر الحلقة بهذا الشكل. عندما تصبح i=10 تتوقف الحلقة عند تنفيذ الشيفرة ما داخل القوسين المعقوصين وتنتقل إلى الشفرة التي تلي الحلقة وتطبع الرسالة !finished على سطر جديد. التنقل بين عناصر مجموعة باستخدام الحلقة for بإمكانك استخدام الحلقة for للتنقل بين عناصر مجموعة بدلًا من for...of. لنعد إلى مثال for...of السابق: const cats = ["Leopard", "Serval", "Jaguar", "Tiger", "Caracal", "Lion"]; for (const cat of cats) { console.log(cat); } سنكتب الشيفرة كالتالي: const cats = ["Leopard", "Serval", "Jaguar", "Tiger", "Caracal", "Lion"]; for (let i = 0; i < cats.length; i++) { console.log(cats[i]); } نبدأ هذه الحلقة عند قيمة i=0 وننهيها عندما تصل قيمة i إلى طول المصفوفة. ونستخدم i ضمن الحلقة للوصول إلى كل عنصر من عناصرها. ستعمل الحلقة جيدًا وقد استخدمت قبل ظهور وكانت الطريقة المعيارية للتنقل بين عناصر مصفوفة، لكن قد تزداد الفرصة عند استخدامها إلى ظهور ثغرات في الشيفرة مثل: أن تبدأ i من القيمة 1 وتنسى أن تعداد المصفوفات يبدأ من 0. قد تنهي الحلقة عند i<=cats.length وتنسى أن نآخر دليل في المصفوفة هو length-1. لأسباب كهذه، يُفضّل استخدام for...of إن استطعت ذلك، لكنك ستحتاج في بعض اﻷحيان إلى for عند النقل بين عناصر مصفوفة. فإن أردت مثلًا طباعة رسالة تضم قائمة بأسماء القطط باستخدام الشيفرة التالية: const cats = ["Pete", "Biggles", "Jasmine"]; let myFavoriteCats = "My cats are called "; for (const cat of cats) { myFavoriteCats += `${cat}, `; } console.log(myFavoriteCats); // "My cats are called Pete, Biggles, Jasmine, " لن تكون النتيجة (من ناحية تكوين الجملة في اﻹنكليزية) مصاغة بالكل الصحيح: My cats are called Pete, Biggles, Jasmine, لهذا يجب أن تتعامل الحلقة مع آخر اسم بشكل مختلف حتى تكون النتيجة بالشكل: My cats are called Pete, Biggles, and Jasmine. ولفعل ذلك، ينبغي أن نعرف دليل آخر عنصر ونحتاج عندها إلى الحلقة for لتفحّص قيمة i: const cats = ["Pete", "Biggles", "Jasmine"]; let myFavoriteCats = "My cats are called "; for (let i = 0; i < cats.length; i++) { if (i === cats.length - 1) { // We are at the end of the array myFavoriteCats += `and ${cats[i]}.`; } else { myFavoriteCats += `${cats[i]}, `; } } console.log(myFavoriteCats); // "My cats are called Pete, Biggles, and Jasmine." الخروج من الحلقة باستخدام التعليمة break إن أردت الخروج من حلقة باكرًا قبل انتهاء جميع التكرارات، استخدم التعليمة break التي رأيناها في مقال العبارات الشرطية عند التعامل مع البنية switch، إذ تنهي التعليمة break البنية switch في الحالة case التي تحقق تطابقًا مع الشرط. يتكرر اﻷمر ذاته مع الحلقات، إذ يمكن إنهاء الحلقة مباشرة باستخدام break وجعل المتصفح يقفز إلى الشيفرة التي تلي الحلقة. فلو أردنا مثلًا تطبيقًا للبحث عبر مصفوفة تضم جهات اتصال وأرقام هواتف ونريد فقط رقمًا محددًا، سنحتاج إلى شيفرة HTML تضم عنصر إدخال نصي <input> لإدخال الرقم الذي نبحث عنه وزر <button> لتنفيذ العملية وفقرة نصية <p> لعرض النتيجة: <label for="search">Search by contact name: </label> <input id="search" type="text" /> <button>Search</button> <p></p> ستكون شيفرة جافا سكريبت كالتالي: const contacts = [ "Chris:2232322", "Sarah:3453456", "Bill:7654322", "Mary:9998769", "Dianne:9384975", ]; const para = document.querySelector("p"); const input = document.querySelector("input"); const btn = document.querySelector("button"); btn.addEventListener("click", () => { const searchName = input.value.toLowerCase(); input.value = ""; input.focus(); para.textContent = ""; for (const contact of contacts) { const splitContact = contact.split(":"); if (splitContact[0].toLowerCase() === searchName) { para.textContent = `${splitContact[0]}'s number is ${splitContact[1]}.`; break; } } if (para.textContent === "") { para.textContent = "Contact not found."; } }); See the Pen Looping code3 by Hsoub Academy (@HsoubAcademy) on CodePen. نعرّف أولًا بعض المتغيّرات منها مصفوفة جهات الاتصال المؤلفة من عناصر نصيّة تحتوي على اسم ورقم هاتف تفصل بينهما نقطتان فوق بعضهما : نربط بعدها مترصّد أحداث إلى الزر btn لتشغيل الشيفرة التي تنفّذ البحث وتعيد النتيجة عند النقر عليه. نخزّن القيم المدخلة إلى مربع اﻹدخال في المتغيّر searchName قبل أن نفرّغه ونعيد إليه تركيز الدخل من جديد. ولاحظ استخدام التابع ()toLowerCase كي يحول النص المدخل إلى حروف صغيرة وبالتالي لن يكون البحث حساسًا لحالة الحروف. نأتي إلى الجزء الذي يضم الحلقة for...of: نفصل بداية جهة الاتصال عند النقطتين المتعامدتين ونخزّن القيمتين الناتجتين عن عملية الفصل في مصفوفة تُدعى . نستخدم بعد ذلك عبارة شرطية لاختبار إن كانت قيمة splitContact[0] (وتمثّل اسم المستخدم المحوّل إلى حروف صغيرة) متطابقة مع القيمة المخزّنة في المتغيرsearchName فإن وجد التطابق نعرض في الفقرة النصية الرقم الموافق ثم نستخدم break ﻹنهاء الحلقة. إن لم نجد جهة اتصال مطابقة للبحث لن تظهر أي قيمة في الفقرة النصية، لهذا نضع ضمنها النص "Contact not found." بمعني أننا لم نجد جهة الاتصال المطلوبة. ملاحظة: بإمكانك الاطلاع على الشيفرة المصدرية الكاملة لهذا المثال على جيت-هب، وتستطيع تجربته مباشرة كذلك. تخطي أحد التكرارات باستخدام continue تعمل التعليمة continue بشكل مشابه للتعليمة break لكنها تتخطى التكرار الحالي إلى التكرار التالي بدلًا من الخروج من الحلقة نهائيًا. لنلق نظرة على مثال آخر يأخذ عدة مدخلات ويعيد فقط اﻷعداد التي تمثل مرّبعات ﻷعداد صحيحة. إليك أولًا شيفرة HTML: <label for="number">Enter number: </label> <input id="number" type="number" /> <button>Generate integer squares</button> <p>Output:</p> وهذه شيفرة جافا سكريبت اللازمة: const para = document.querySelector("p"); const input = document.querySelector("input"); const btn = document.querySelector("button"); btn.addEventListener("click", () => { para.textContent = "Output: "; const num = input.value; input.value = ""; input.focus(); for (let i = 1; i <= num; i++) { let sqRoot = Math.sqrt(i); if (Math.floor(sqRoot) !== sqRoot) { continue; } para.textContent += `${i} `; } }); وهذا هو خرج البرنامج: See the Pen Looping Code4 by Hsoub Academy (@HsoubAcademy) on CodePen. يجب أن يكون الدخل في هذه الحالة عددًا (المتغير num)، وتبدأ الحلقة من 1 (لأن 0 لا يهمنا في هذا التطبيق). هنالك أيضًا شرط للخروج من الحلقة وهو أن يكون العداد أكبر من العدد المدخل num، ويزداد العداد بمقدار 1 في كل تكرار. نحسب الجذر التربيعي لكل عدد داخل الحلقة ياستخدام التابع (i)Math.sqrtومن ثم نتحقق أنه عدد صحيح بمقارنته مع نفسه عند تقريبه إلى أصغر عدد صحيح باستخدام التابع ()Math.floor. إن لم يتساوى الجذر التربيعي والقيمة المقرّبة له (==!) فهذا يعني أن الجذر التربيعي ليس عددًا صحيحًا ولن يكون مهمًا بالنسبة لنا لهذا نستخدم continue في هذه الحالة للانتقال إلى العدد التالي دون أن نسجّل هذا العدد. إن كان الجذر التربيعي صحيحًا نتخطى الكتلة if كليًا ولن تُنفَّذ التعليمة continue بل نضيف قيمة i وبعده مسافة فارغة إلى محتوى الفقرة النصية. ملاحظة: بإمكانك الاطلاع على الشيفرة المصدرية الكاملة لهذا المثال على جيت-هب، وتستطيع تجربته مباشرة كذلك. الحلقتين while و do...while إن الحلقة for ليست الوحيدة في جافا سكريبت، وعلى الرغم أنك لن تحتاج إلى معرفة كل أنواع الحلقات حاليًا، من الجيد الاطلاع على بعضها كي تميّز وجه الشبه والاختلاف بينها. لنلق نظرة بداية على الحلقة while التي لها الصياغة التالية: initializer while (condition) { // code to run final-expression } تعمل الحلقة بشكل مشابه للحلقة for، إلا أن المهيّء يُضبط قبل الحلقة وعبارة الإنهاء موجودة ضمن الحلقة بعد الشيفرة التي تُنفَّذ عند تحقق الشرط بدلًا من وجودهما بين القوسين التاليين للتعليمة while إلى جانب عبارة الشرط. وكما نرى، فإن العناصر الثلاث السابقة الموجودة في حلقة for موجودة أيضًا في الحلقة while وبنفس الترتيب لأنك تحتاج إلى المهيئ قبل أن تختبر صحة الشرط ثم تُنفَّذ عبارة اﻹنهاء عند انتهاء تنفيذ شيفرة الحلقة التي تستمر طالما أن الشرط محقق. نحاول تاليًا إعادة كتابة شيفرة مثالنا السابق عن القطط لكن باستخدام الحلقة while: const cats = ["Pete", "Biggles", "Jasmine"]; let myFavoriteCats = "My cats are called "; let i = 0; while (i < cats.length) { if (i === cats.length - 1) { myFavoriteCats += `and ${cats[i]}.`; } else { myFavoriteCats += `${cats[i]}, `; } i++; } console.log(myFavoriteCats); // "My cats are called Pete, Biggles, and Jasmine." وبالنسبة للحلقة do...while، فهي مشابهة جدًا للحلقة while مع بعض التغييرات في الصياغة: initializer do { // code to run final-expression } while (condition) نلاحظ في هذه الحالة أن المهيئ يأتي أولًا قبل بداية الحلقة ثم الكلمة المحجوزة do يتبعها قوسين معقوصين يضمان الشيفرة التي يجب تنفيذها ثم عبارة اﻹنهاء ثم يأتي بعد القوسين المعقوصين مباشرة التعليمة while يليها الشرط. إن الفرق الرئيسي بين الحلقتين السابقتين هو أن الشيفرة ضمن الحلقة do...while ستُنفَّذ على اﻷقل مرة واحدة قبل اختبار صحة الشرط. إي تُنفَّذ الشيفرة وبعدها يُختبر الشرط لتحديد الحاجة إلى تنفيذ الشيفرة مجددًا. أما في whileو forيُختبر الشرط أولًا. لنحاول تاليًا إعادة كتابة شيفرة مثالنا السابق عن القطط باستخدام الحلقة do...while: const cats = ["Pete", "Biggles", "Jasmine"]; let myFavoriteCats = "My cats are called "; let i = 0; do { if (i === cats.length - 1) { myFavoriteCats += `and ${cats[i]}.`; } else { myFavoriteCats += `${cats[i]}, `; } i++; } while (i < cats.length); console.log(myFavoriteCats); // "My cats are called Pete, Biggles, and Jasmine." تحذير: يجب التأكد في حلقات while و do...while وجميع الحلقات اﻷخرى من زيادة أو إنقاص المهيئ تبعًا للحالة حتى يصبح الشرط في مرحلة ما غير محقق وتنتهي الحلقة وإلا ستستمر الحلقة إلى ما لا نهاية أو سيوقفها المتصفح أو ستنهار الشيفرة. تُدعى هذه الحالة -حلقة لانهائية infinite loop-. تطبيق عملي: إبدأ العد التنازلي! نطلب إليك في هذا التمرين طباعة عد تنازلي من 10 إلى 0، ونريد تحديدًا: حلقة من 10 إلى 0، وقد زوّدناك بالمهيئ ;let i = 10. إنشاء فقرة نصية جديدة في كل تكرار للحلقة وإلحاقها بعنصر الخرج <div> الذي نختاره باستخدام اﻷمر : const output = document.querySelector('.output'); وقد زوّدناك في التعليقات بثلاث أسطر من الشيفرة يجب استخدامها ضمن الحلقة: السطر;const para = document.createElement('p') الذي يُنشىء فقرة نصية جديدة. السطر ;output.appendChild(para) الذي يُلحق الفقرة النصية بالعنصر <div>. السطر =para.textContentالذي يجعل محتوى الفقرة ما تضعه من الناحية اليمنى للمساواة. يتطلب كل تكرار نصًا مختلفًا يوضع ضمن الفقرة النصية وفقًا للعدد الذي وصلنا إليه، لذا تحتاج عبارة شرطية وعدة عبارات =para.textContent: إن كان العدد هو 10 يجب طباعة "Countdown 10". إن كان العدد هو 0 يجب طباعة "!Blast off". يُطبع أي عدد آخر كما هو. تذكّر أن تضيف عدادًا وانتبه إلى أننا نعد تنازليًا في تمريننا وليس تصاعديًا (لا تستخدم ++i!). ملاحظة: إن بدأت كتابة الحلقة على الشكل فقد يتوقف المتصفح عن التنفيذ إن لم تضع الشرط النهائي، انتبه إلى ذلك. يمكنك لتفادي اﻷمر كتابة الشيفرة ضمن تعليقات ومن ثم تزيل إشارات التعليق عندما تنتهي من كتابة الشيفرة كاملةً. إن ارتكبت خطأً تستطيع أن تنقر على الزر "Reset" لتعود إلى الوضع الأساسي، كما يمكنك النقر على الزر "Show solution" لترى الحل. See the Pen Looping Code5 by Hsoub Academy (@HsoubAcademy) on CodePen. تطبيق عملي: ملء قائمة المدعوين نطلب منك في هذا التمرين أخذ قائمة أسماء من مصفوفة ووضعها ضمن قائمة المدعوين، لكن لا نريد دعوة "Phil" و "Lola" لأسباب خاصة.لهذا نحتاج إلى قائمة للمدعوين وأخرى لغير المدعوين. لهذا نطلب منك تحديدًا: كتابة حلقة تتنقل بين عناصر المصفوفة people. تتحقق في كل مرور من أن اسم الشخص ليس "Phil"أو "Lola" مستخدمًا عبارة شرطية: إن كان كذلك، ألحق الاسم بنهاية المحتوى النصي textContent للفقرة النصية refused يليه فاصلة وفراغ. إن لم يكن كذلك، ألحق الاسم بنهاية المحتوى النصي textContent للفقرة النصية admitted يليه فاصلة وفراغ. زوّدناك مسبقًا بأسطر الشيرة التالية: السطر =+refused.textContent الذي يمثل بداية أمر ضم شيء ما إلى نهاية refused.textContent. السطر =+admitted.textContent الذي يمثل بداية أمر ضم شيء ما إلى نهاية admitted.textContent. وإليك بطلب آخر يمنحك نقاطًا إضافية: بعد إكمالك المهمتين السابقتين بنجاح سنتركك مع قائمتين من الأسماء تفصل بينها فواصل لكنها غير مرتبة. هل تستطيع اقتطاع الفواصل ووضع نقطة آخر القائمة؟ عد إلى مقال توابع جافا سكريبت للتعامل مع النصوص إن احتجت أي مساعدة. إن ارتكبت خطأً تستطيع أن تنقر على الزر "Reset" لتعود إلى الوضع الأساسي، كما يمكنك النقر على الزر "Show solution" لترى الحل. See the Pen Looping Code6 by Hsoub Academy (@HsoubAcademy) on CodePen. ما هو نوع الحلقة التي علي استخدامها؟ إن كنت ستتنقل ضمن عناصر مصفوفة أو أي كائن يدعم ذلك ولن تحتاج إلى دليل العنصر (موقعه) في كل مرة، استخدم for...of فهي أسهل قراءة وأقل توليدًا للأخطاء. واستخدم for و while و do...while لبقية الحالات ، فجميعها تخدمك لحل نفس المسائل. أما أيها ستختار فهذا أمر شخصي وذلك وفقًا لسهولة تطبيقها أو وضوحها بالنسبة إليك. لكننا ننصحك باستخدام for كبداية لأن ترتيب وموقع عناصرها أسهل تذكرًا (جميعها ضمن القوسين التاليين للكلمة المحجوزة for) فلن تنسى أيًا منها. لنلق نظرة عليها جميعًا: الحلقة for...of: for (const item of array) { // code to run } الحلقةfor: for (initializer; condition; final-expression) { // code to run } الحلقة while: initializer while (condition) { // code to run final-expression } الحلقة do...while: initializer do { // code to run final-expression } while (condition) ملاحظة: هناك حلقات أخرى لها ميزات مختلفة وفوائد في حالات محددة وخاصة لكننا لن نمر عليها في هذا المقال. خلاصة قدمنا لك في هذا المقال المفاهيم الأساسية والخيارات المختلفة عند تكرار تنفيذ شيفرة جافا سكريبت عبر الحلقات. وعرضنا أهمية هذا النوع من البنى البرمجية في التعامل مع الحالات التي تتكرر فيها الشيفرة نفسها وأوضحنا طريقة عملها من خلال أمثلة عدة. إن استعصى عليك فهم أية نقطة، عد إليها مجددًا أو اطرح سؤالك عبر نقاش الصفحة. ترجمة -وبتصرف- لمقال Looping code اقرأ أيضًا المقال السابق: العبارات الشرطية واتخاذ القرار في جافا سكريبت تعلم جافا سكريبت من الصفر حتى الاحتراف هيكل البرنامج في جافاسكريبت ترميز النصوص والتعامل مع كائنات الملفات في جافاسكربت
-
الصندوق المرن Flexbox هو أسلوب تخطيط وحيد الاتجاه لترتيب العناصر في صفحة الويب سواء على سطر واحد أو ضمن عمود واحد. إذ تتمتد العناصر لتشغل مساحة أوسع، أو تتقلص لتتلائم مع مساحة أقل. سنتطرق في هذا المقال إلى جميع الأساسيات التي تغطي هذا المفهوم. عليك قبل البدء في قراءة هذا المقال أن: تطلع على أساسيات HTML كما شرحناها في سلسلة مقالات مدخل إلى HTML. تفهم أساسيات عمل CSS. لماذا نستخدم الصندوق المرن؟ بقيت التخطيطات المعتمدة على خاصيات CSS المتعلقة بتعويم العناصر floats وبتوضعها positioning ولفترة طويلة أكثر التخطيطات موثوقية عبر مختلف المتصفحات، وقد نجح هذا الأمر لكن مع وجود شيء من المحدودية في التنفيذ وشيء من الإحباط أحيانًا. فتخطيطات بسيطة كالتي سنذكرها ستكون إما صعبة التنفيذ أو مستحيلة باستخدام تلك الأدوات وبأسلوب مرن وملائم: توسيط كتلة من المحتوى عموديًا ضمن العنصر الأب. التوزيع المتساوي لاتساع العنصر الأب بين مختلف العناصر الأبناء ضمن حاوية بغض النظر عن مقدار المساحة المتوفرة. ضبط جميع الأعمدة في التخطيط متعدد الأعمدة على نفس الارتفاع إن ضمت كميات مختلفة من المحتوى. وكما سنرى، يساعدنا تخطيط الصندوق المرن في تنفيذ الكثير من المهام بسهولة أكبر، وإليك التفاصيل. مثال تمهيدي بسيط سنمر في هذا المقال بسلسلة من التمارين كي نفهم آلية عمل الصندوق المرن. عليك بداية أن تنسخ ملف التمرين من جيت-هاب إلى جهازك، ثم حمّله ضمن متصفح حديث (كروم أو فايرفوكس) والق نظرة على الشيفرة باستخدام محرر الشيفرة الذي تستخدمه. بإمكان أيضًا الاطلاع على طريقة عمله مباشرة على جيت-هاب. ستلاحظ وجود ترويسة <header> تضم عنوانًا من المستوى الأول، وكذلك العنصر <section> الذي يضم ثلاث عناصر <article>. نستخدم هذه الشيفرة في بناء تخطيط معياري مكون من ثلاث أعمدة. تحديد العناصر التي ينبغي ترتيبها كصناديق مرنة علينا كبداية اختيار العناصر التي سترتب كصناديق مرنة، ولفعل ذلك ضبطنا الخاصية display للعنصر الأب للعناصر التي تريدها أن تتحول لصناديق مرنة (وهي في حالتنا العناصر <article>) على القيمة flex. وبالطبع فإن العنصر الأب هو <section>. section { display: flex; } هكذا يصبح العنصر حاوية مرنة وتصبح العناصر الأبناء عناصر مرنة. وستكون النتيجة شيئًا من هذا القبيل: إذًا، منحنا هذا التصريح بمفرده ما نريد، وهذا أمر رائع! إذ عُرض المحتوى ضمن ثلاث أعمدة لها الاتساع ذاته والارتفاع ذاته. والسبب في ذلك أن أن القيمة الافتراضية للعناصر المرنة (العناصر الأبناء ضمن الحاوية المرنة) تحل هذا النوع من المسائل مباشرة. دعونا نعيد ما حدث حتى يكون كل شيء واضحًا. يعمل العنصر الذي ضبطنا قيمة الخاصية display له على flex كعنصر كتلي من ناحية الطريقة التي يتفاعل فيها مع بقية أجزاء الصفحة، لكن أبناءه قد رُتِّبت كعناصر مرنة. وسنشرح في القسم التالي بتفاصيل أكبر ما يعنيه هذا الكلام. لا حظ أيضًا إمكانية استخدام القاعدة ;display: inline-flex إن أردت توضيع أبناء عنصر معين كعناصر مرنة لكن سيسلك هذا العنصر سلوك العناصر السطرية. نموذج الصندوق المرن عندما تتوضع العناصر بصفتها عناصر مرنة فإنها تتوضع وفق محورين: المحور الأساسي main axis: وله الاتجاه الذي تتوضع وفقه العناصر المرنة (أي على شكل سطر عبر الصفحة أو عمود يتجه لأسفل الصفحة) وتُدعى بداية هذا المحور البداية الأساسية main start وتُدعى نهايته بالنهاية الأساسية main end. المحور القاطع cross axis: هو المحور العمودي على اتجاه توضع العناصر المرنة وله بداية القاطع cross start ونهاية القاطع cross end. يُدعى العنصر الذي تُطبق عليه القاعدة display: flex (العنصر <section> في مثالنا) بالحاوية المرنة flex container. تُدعى العناصر الموجودة داخل الحاوية المرنة بالعناصر المرنة (العناصر <article> في مثالنا). تذكر هذه المصطلحات دائمًا، وعد إليها إن اختلط عليك الأمر في الأقسام القادمة. أعمدة وأسطر يقدم مخطط الصندوق المرن خاصية تُدعى flex-direction تحدد اتجاه المحور الأساسي (الاتجاه التي تُترتب وفقه عناصر الصندوق المرن). تأخذ هذه الخاصية القيمة row افتراضيًا والتي ترتب العناصر في صف باتجاه انسياب اللغة الافتراضية للمتصفح (من اليمين إلى اليسار في العربية و العكس في الإنجليزية). حاول أن تضيف التصريح التالي لقاعدة التنسيق الخاصة بالعنصر <section>: flex-direction: column; سترى أن هذه القاعة ستعيد العناصر إلى تخطيط العمود الواحد كما كان تقريبًا قبل إضافة أية تنسيقات. احذف هذا التصريح من شيفرة مثالنا قبل المتابعة. ملاحظة: بإمكانك إيضًا ترتيب العناصر المرنة عكسيًا باستخدام القيمتين row-reverse و column-reverse. التفاف العناصر من أحد المشاكل التي تظهر عند استخدام تخطيط ثابت الاتساع أو الإرتفاع هو طفحان العناصر في مرحلة ما خارج الحاوية مما يدمر التخطيط. لنلق نظرة على المثال wrap0.html الموجود على جيت-هاب (انسخ هذا المثال على جهازك إن كنت ترغب في تطبيق الأكواد الواردة في هذا المقال) لاحظ هنا كيف خرجت الأعمدة الأبناء خارج الحاوية، ولحل هذه المشكلة يمكن أن نستخدم التصريح التالي ضمن قاعدة تنسيق الكتلة الحاوية <section>: flex-wrap: wrap; أضف أيضًا التصريح التالي إلى قاعدة تنسيق العناصر <article>: flex: 200px; حاول استعراض الشيفرة الآن في المتصفح وسترى أن التخطيط قد تحسّن كثيرًا: لدينا الآن عدة أسطر يضم كلًا منها عدة عناصر أبناء مرنة تشغل تمامًا المساحة المتاحة للسطر، وعند حدوث طفحان يُنقل العنصر إلى السطر التالي. واستخدم التصريح flex: 200px في تنسيق العناصر <article> للدلالة إلى أن اتساع كل عنصر يجب أن يكون على الأقل 200 بكسل، وسنناقش هذه الخاصية بالتفصيل لاحقًا. يمكن أن نلاحظ أيضًا أن آخر العناصر الأبناء في السطر الأخير أكثر اتساعًا كي تشغل مساحة السطر بأكمله. هناك الكثير لنفعله أيضًا! حاول أن تغيّر قيمة الخاصية flex-direction إلى row-reverse وسترى أن تخطيط الأسطر المتعددة بقي كما هو لكن العناصر قد رتّبت بشكل معكوس ابتداءًا من الزاوية الأخرى للمتصفح. الخاصية المختصرة flex-flow تجدر الملاحظة هنا أن الخاصيات المختصرة موجودة للخاصيات flex-direction و flex-wrap و flex-flow. إذ يمكنك مثلًا استبدال التصريح: flex-direction: row; flex-wrap: wrap; بالتصريح التالي: flex-flow: row wrap; التحديد المرن لأبعاد العناصر المرنة لنعد الآن إلى مثالنا الأول لنرى كيف يمكننا التحكم بحجم المساحة الفارغة التي يمكن أن يشغلها العنصر المرن بالنسبة إلى بقية العناصر المرنة. شغّل الملف flexbox0.html أو انسخ الملف flexbox1.html إلى جهازك ليكون نقطة الانطلاق إذا قررت تطبيق ما سنفعله. أضف بداية القاعدة التالية أسفل تصريحات CSS: article { flex: 1; } إن القيمة في التصريح السابق هي قيمة نسبية لا وحدة لها تشير إلى حجم الفراغ المتاح لكل عنصر مرن على امتداد المحور الأساسي موازنة مع غيره من العناصر المرنة. في حالتنا منحنا كل عنصر <article> نفس القيمة وهي 1 كي تأخذ جميع العناصر نفس الكمية من الفراغ الإضافي الذي قد يبقى (الذي لا يتسع لعنصر بأكمله) بعد أن تُحتسب قيم بعض الخاصيات مثل الهوامش والحشوات. تشترك جميع العناصر المرنة بهذه النسبة وحتى لو وضعنا القيمة 40000 بدل القيمة 1 فسيكون لها الأثر ذاته. أضف الآن القاعدة التالية تحت القاعدة السابقة: article:nth-of-type(3) { flex: 2; } عند النقر على زر التحديث في المتصفح، سترى أن العنصر <article> الثالث يشغل ضعفي مساحة العنصرين الباقيين. في هذه الحالة سيكون هناك أربع وحدات تناسبية إجمالًا موزعة على الشكل (4=1+1+2). سيشغل أو عنصرين مرنين ما مقداره وحدة من أصل أربعة أي 1/4 من المساحة المتاحة، وسيشغل العنصر الثالث الربعين الآخرين. بإمكانك أيضًا أن تخصص حدًا أدنى من الحجم لكل عنصر مرن. لترى ذلك، حاول تعديل قواعد تنسيق العنصر <article> كما يلي: article { flex: 1 200px; } article:nth-of-type(3) { flex: 2 200px; } تنص هذه القواعد على أن كل عنصر مرن سيعطى اتساعًا قدره 200 بكسل بداية من المساحة المتوفرة، وبعدها تتقاسم العناصر المساحة الباقية بنفس النسبة. جرّب أن تنقر على زر التحديث في المتصفح وسترى اختلافًا في طريقة تقاسم المساحة الفارغة. تتجلى القيمة الحقيقية لتخطيط الصندوق المرن من خلال مرونته وتجاوبه، فإن غيرّت أبعاد المتصفح أو أضفت عنصر <article> جديد سيبقى التخطيط جميلًا. الخاصية flex المختصرة موازنة بالخاصية المطوّلة يمكن استخدام الخاصية المختصرة flex لتحديد ثلاث قيم على الأكثر: القيمة النسبية التي ليس لها واحدة وقد ناقشناها في الأعلى. بالإمكان ضبط هذه القيمة بمفردها أيضًا من خلال الخاصية المطولة flex-grow. قيمة نسبية أخرى ليس لها واحدة تعبر عنها الخاصية المطوّلة flex-shrink وتظهر أهميتها عندما يحدث الطفحان في الحاوية المرنة. إذ تحدد هذه القيمة مقدار ما يتقلصه العنصر ليمنع الطفحان. لا تُعد هذه الميزة للصندوق المرن متقدمة ولن نغطيها لاحقًا. القيمة التي تحدد الحد الأدنى لاتساع العنصر المرن والتي ناقشناها سابقًا. بالإمكان أن نضبط هذه القيمة أيضًا باستخدام الخاصية المطوّلة flex-basis. لا ننصحك باستخدام الصيغ المطوّلة لخاصيات الصندوق المرن إلا في الحالة التي لا مفر من استخدامها (كأن تلغي قاعدة ما حُدِّدت قيمتها سابقًا). إذ سيزيد استخدام هذه الخاصيات من حجم الشيفرة المكتوبة، وقد تغدو مربكة أحيانًا. المحاذاة الأفقية والعمودية نستطيع أيضًا استخدام خاصيات الصندوق المرن لمحاذاة العناصر المرنة عبر المحور الأساسي أو القاطع. ولتوضيح ذلك، سنلقي نظرة على المثال flex-align0.html الموجود على جت-هاب (بإمكانك متابعته مباشرة) أيضًا)، وسنحاول تحويل محتواه إلى قائمة من الأزرار المرنة الأنيقة. لاحظ بداية وجود شريط قائمة أفقي يضم عدة أزرار محشورة بجوار بعضها ابتداءً من الزاوية اليمينية العليا. جهز نسخة عن هذا الملف على جهازك، ثم أضف الشيفرة التالية إلى أسفل شيفرة CSS: div { display: flex; align-items: center; justify-content: space-around; } انقر على زر "تحديث" في المتصفح وسترى أن الأزرار قد ضبطت لتبدو متمركزة في منتصف القائمة عموديًا وأفقيًا، وقد أنجزنا ذلك باستخدام الخاصيتين التاليتين: الخاصية align-items: وتتحكم بموضع العناصر المرنة على امتداد المحور القاطع: تأخذ هذه الخاصية القيمة stretch افتراضيًا وتوسّع العناصر المرنة لتشغل المساحة المتاحة للعنصر الأب وفق اتجاه المحور القاطع. فإن لم يكن للعنصر الأب ارتفاع محدد وفق اتجاه المحور القاطع، ستأخذ جميع العناصر الأبناء ارتفاع أطول العناصر المرنة. لهذا كان للأعمدة في مثالنا الأول نفس الارتفاع تلقائيًا. أما القيمة التي استخدمناها في الشيفرة السابقة، ستُبقي الأبعاد الأصلية للعناصر كما هي لكن ستجعلها تتمركز وفق اتجاه المحور القاطع. لهذا تجد الأزرار متمركزة عموديًا في مثالنا الحالي. يمكن للخاصية أن تأخذ قيمًا مثل flex-start و flex-end والتي تحاذي العناصر المرنة إلى بداية ونهاية المحور القاطع. بإمكانك إلغاء سلوك الخاصية align-items لعنصر مرن محدد بتطبيق الخاصية align-self عليه. إليك كيف يكون الأمر: button:first-child { align-self: flex-end; } جرب تلك القيم لضبط محاذاة العناصر لترى تأثيرها ثم احذف ما فعلته عندما تنتهي. الخاصية justify-content: وتتحكم بموضع العناصر المرنة على امتداد المحور الأساسي: تأخذ الخاصية القيمة flex-start افتراضيًا مما يجعل جميع العناصر مرتبة ابتداءً من بداية المحور الأساسي. يمكن أن تأخذ الخاصية القيمة flex-end فترتب حينها العناصر من ناحية النهاية. وترتب العناصر حول مركز الحاوية المرنة إن كانت قيمة هذه الخاصية center. استخدمنا في الشيفرة السابقة القيمة space-around والتي توزّع جميع العناصر المرنة بالتساوي على طول المحور الأساسي مع وجود بعض المساحة الفارغة في كلا الطرفين. هنالك قيمة أخرى للخاصية وهيspace-between وهي مشابهة جدًا للقيمة space-around إلا أنها لا تترك أية مسافات فارغة إضافية في البداية والنهاية. أهملت الخاصية justify-items في تخطيط الصندوق المرن ولن نشير إليها، لكننا ننصحك بتجريب القيم السابقة للخاصيتين المتعلقتين بمحاذاة العناصر المرنة قبل متابعة القراءة. ترتيب العناصر المرنة يمتلك تخطيط الصندوق المرن ميزة تغيير ترتيب العناصر المرنة ضمن التخطيط دون التأثير على الترتيب المصدري. وهذا أمر يستحيل إنجازه باستخدام أساليب التخطيط التقليدي. إن الشيفرة الموافقة لهذه الميزة بسيطة، حاول أن تضيف الشيفرة التالية إلى شيفرة مثال شريط الأزرار: button:first-child { order: 1; } انقر على زر التحديث في المتصفح وسترى أن الزر "Smile" قد انتقل إلى نهاية المحور الأساسي، وسنشرح قليلًا كيف حدث هذا: تأخذ الخاصية order القيمة 0 لجميع العناصر المرنة افتراضيًا. ستظهر جميع العناصر ذات القيمة الأعلى لهذه الخاصية في النهاية موازنة بالعناصر التي تحمل قيمة أقل. تحافظ العناصر التي تحمل نفسة القيمة للخاصية order على ترتيب الشيفرة المصدرية. فإن كان لدينا مثلًا أربعة عناصر تحمل القيم 0،1،1،2 على الترتيب سيكون ترتيب عرضها على الشاشة الرابع ثم الثاني والثالث ثم الأول. سيظهر العنصر الثالث بعد الثاني على الرغم من امتلاكهما نفس قيمة الخاصية order إلا أن الثاني جاء قبل الثالث في ترتيب الشيفرة المصدرية. يمكن للخاصية أن تأخذ قيمًا سالبة كي تعرض العناصر التي تحمل هذه القيم قبل العنصر الذي يحمل القيمة 0. يمكننا مثلًا عرض الزر "Blush" في مثالنا في بداية المحور الأساسي باستخدام قاعدة التنسيق التالية: button:last-child { order: -1; } صناديق مرنة متداخلة بإمكانك إنشاء بعض التخطيطات الجميلة والمعقدة باستخدام الصناديق المرنة. فمن الممكن جدًا أن أجعل عنصرًا مرنًا حاويةً مرنةً أيضًا وأن يكون بعض أبناءه أيضًا حاويات مرنة وهكذا. ألق نظرة الآن على المثال complex-flexbox.html على جت-هاب (بإمكانك متابعة التنفيذ المباشر له أيضًا). إن شيفرة HTML لهذا المثال بسيطة، إذ تحتوي الشيفرة على العنصر <section> وضمنه ثلاثة عناصر <article> يحوي آخرها ثلاث عناصر <div> ويضم أول العناصر <div> خمسة أزرار. section - article article article - div - button div button div button button button لنلق نظرة على الشيفرة المستخدمة لتخطيط الصفحة، وسنرى أن أول ما فعلناه هو ضبط العناصر الأبناء للعنصر <section> لتكون صناديق مرنة. section { display: flex; } ثم ضبطنا بعض القيم المرنة للعناصر. وانتبه جيدًا إلى القاعدة الثانية في شيفرة التنسيق والتي جعلت العناصر الأبناء للعنصر <article> الثالث عناصر مرنة أيضًا لكننا رتبناها على هيئة عمود. article { flex: 1 200px; } article:nth-of-type(3) { flex: 3 200px; display: flex; flex-flow: column; } نختار تاليًا أول العناصر <div> ونطبق عليه التنسيق ;flex: 1 100px كي نعطيه ارتفاعًا مقداره 100 بكسل كحد أدنى، ثم نرتب العناصر الأبناء له على شكل عناصر مرنة. سنختار أن تتوضع الأزرار ضمن سطر وتكون قابلة للالتفاف والانتقال إلى سطر آخر في حال الطفحان كما اخترنا أن نعطيها محاذاة إلى مركز العنصر الأب كما فعلنا مع مثال قائمة الأزرار سابقًا: article:nth-of-type(3) div:first-child { flex: 1 100px; display: flex; flex-flow: row wrap; align-items: center; justify-content: space-around; } حاولنا في الخطوة الأخيرة تحديد حجم الأزرار باستخدام القاعدة ;flex: 1 auto. ولهذه القاعدة تأثير مهم، فإن حاولت أن تغير حجم نافذة المتصفح، ستشغل الأزرار أوسع مساحة ممكنة من الحيز المتاح وتبقى في السطر ذاته ما لم يحدث طفحان لتنتقل عندها إلى سطر آخر. button { flex: 1 auto; margin: 5px; font-size: 18px; line-height: 1.5; } توافقية المتصفحات مع تخطيط الصندوق المرن تدعم معظم المتصفحات الحديثة تخطيط الصندوق المرن مثل فايرفوكس وكروم وأوبرا ومايكروسوفت إيدج وإكسبلورر 11 والنسخ الجديدة من نظامي التشغيل أندرويد وأي أو إس. لكن عليك أن تدرك أن البعض يستخدم متصفحات قديمة لا تدعم هذا التخطيط (أو تفعل لكن بأسلوب قديم حقًا). وطالما أنك لا تزال في مرحلة التعلم والتجريب، لا تكترث للأمر كثيرًا. فإن قررت أن تستخدم تخطيط الصندوق المرن في تصميم موقعك، لا بأس إذًا من اختبار شيفرتك وتجريب بعض الحلول حتى تبقى تجربة مستخدمي موقعك مقبولة بأكبر عدد ممكن من المتصفحات. ويبقى تخطيط الصندوق المرن مربكًا موازنة من بعض ميزات CSS. فإن لم يدعم المتصفح مثلًا الظل حول العنصر فلن تتأثر تجربة المستخدمين كثيرًا لكن إن لم يدعم تخطيط الصنوق المرن فسيكسر المتصفح التخطيط بأكمله ولن يستطيع الزائر استخدام الصفحة أصلًا. الخلاصة حاولنا في هذا المقال الإشارة إلى أساسيات الصندوق المرن، ونتمنى أن تكون المعلومات التي وصلتك مفيدة وتساعدك في خطواتك التالية في رحلة التعلم. ترجمة -وبتصرف- للمقال: Flexbox اقرأ أيضًا المقال السابق: الانسياب الاعتيادي Normal Flow لعناصر صفحة الويب مدخل إلى تخطيط صفحات الويب باستخدام CSS التحكم في تخطيط الصفحة وضبط محاذاة العناصر في CSS تحجيم الأشياء في CSS تعرف على أساسيات لغة CSS
-
بعد أن اطلعت في مقالات سابقة على مفاهيم محرك الألعاب جودو، حان وقت الحديث عن التصميم الذي يميزه، فكل محرك ألعاب مختلف عن اﻵخر ويلبي حاجات مختلفة. إذ يقدم كل محرك ألعاب مجالًا من الميزات المختلفة لكن تصميم كل محرك ألعاب متفرد عن اﻵخر. ويقود هذا اﻷمر إلى سياقات عمل مختلفة وطرق مختلفة في هيكلة اﻷلعاب. ويعود السبب في ذلك إلى الفلسفة المختلفة في التصميم لكل محرّك. نساعدك في هذا المقال على فهم طريقة عمل جودو انطلاقًا من بعض الدعائم البنيوية للمحرك، فما نعرضه ليس قائمةً بالميزات ولا موازنة بين المحركات. ولكي تعرف ما يناسبك من محركات، لابد من اختبارها بنفسك وفهم تصميمها ومحدوديتها. التصميم والتأليف كائني التوجه يتبنى محرك الألعاب جودو مفهوم التصميم كائني التوجه كأساس له من خلال نظام المشاهد المرن والتسلسل الهرمي للعقد. كما يحاول أن يبتعد عن اﻷنماط البرمجية البحتة كي يقدم طريقة واضحة ومباشرة لهيكلة ألعابك. فمن ناحية أولى، يسمح لك جودو في تأليف أو تجميع المشاهد. إذ تستطيع إنشاء مشهد لأضواء وامضة BlinkingLight وآخر لمصابيح مكسورة BrokenLantern تستخدم اﻷضواء الوامضة والتعامل معها ككائنات جاهزة. وستتمكن باستخدامها من بناء مدينة مملوءة بالمصابيح المكسورة ومن ثم تغيّر لون الضوء الوامض وتخزّين التغيرات لتتغير عندها حالة المصابيح المكسورة في المدينة دفعة واحدة. وفوق كل ذلك يدعم جودو الوراثة وهو أحد المبادئ الأساسية في البرمجة كائنية التوجه oop، فقد يكون مشهد جودو سلاحًا أو شخصية أو غرضًا ما أو بابًا أو مستوى أو جزء من مستوى وبالمختصر أي شيء تريده. ويعمل المشهد عمل الصنف class في الشيفرة الصرفة، ما عدا أنك حر في تصميمه من خلال المحرر مستخدمًا الشيفرة فقط أو مزيجًا من الاثنين. تختلف المشاهد في جودو عن الكائنات الجاهزة prefab في محركات اﻷلعاب ثلاثية اﻷبعاد اﻷخرى في إمكانية الوراثة عنها أو توسيعها. فقد تُنشئ ساحرًا بتوسعة مشهد الشخصية مثلًا. وعندما تُحدّث الشخصية في المحرر ستتحدث معها شخصية الساحر أيضًا. سيمكنك ذلك من بناء مشروعك كي يتطابق التصميم مع هيكلية الشيفرة. وتجدر الملاحظة أن جودو يقدّم أنواعًا مختلفة من الكائنات التي تُدعى عقدًا Nodes، ولكل منها غاية محددة. والعقد أجزاء من شجرة وترث دائمًا من العقدة اﻷب وصولًا إلى الصنف الرئيسي Node. وعلى الرغم من وجود عقد في جودو مثل اﻷشكال المتصادمة التي تستخدمها فيزيائية العقدة اﻷب، تُعد معظم العقد مستقلة عن بعضها. وبعبارة أخرى، لا تعمل العقد كما تعمل المكوّنات components في محركات ألعاب أخرى. فالعقد Sprite2D و Node2D و CanvasItem و Node، تمتلك جميع الخاصيات والميزات للأصناف اﻵباء كالتحويلات أو القدرة على رسم أشكال محددة وتصييرها باستخدام معالج لوني shader محدد. حزم حصرية بالكامل يحاول محرك الألعاب جودو تزويد المستخدمين بأدوات خاصة تلبي معظم احتياجاتهم. إذ يمتلك المحرّك فضاء عمل مخصص لكتابة السكربتات، ومحرر رسوميات، ومحرر خرائط، ومحرر معالج لوني، ومنقح، ومحلل أداء، باﻹضافة إلى قدرته على عرض التغييرات مباشرة على جهازك المحلي أو جهاز متصل عبر الشبكة. إن الغاية من ذلك كله تقديم حزمة متكاملة من اﻷدوات ﻹنشاء اﻷلعاب تحسين تجربة المستخدمين. بإمكانك بالطبع العمل مع برامج خارجية نظرًا لدعمه للإضافات plug-in، والتي تستطيع أن تنشئها بنفسك أيضًا! ويعود وجود لغة GDScript الخاصة بجودو إلى هذا السبب جزئيًا إضافة إلى دعمه لغة #C. فلغة GDScript مصممة لتلائم حاجات المطورين والمصممين ومتكاملة تمامًا مع المحرّك والمحرر. وتتيح لك GDScript كتابة شيفرة اعتمادًا على صياغة اﻹزاحة أو المسافات البادئة indentation، وتتعرف على اﻷنواع، وتزودك بميزة اﻹكمال التلقائي. وتُعد هذه اللغة من لغات برمجة الألعاب وتوفر شيفرة سهلة ومفهومة وتضم أنواعًا مخصصة مثل Vectors و Colors. ومع GDExtension يمكنك كتابة شيفرة عالية اﻷداء باستخدام لغات مصرّفة مثل C و ++C و Rust وبايثون (باستخدام Cython) دون إعادة تصريف المحرّك. ملاحظة: لا يقدم فضاء العمل ثلاثي الأبعاد نفس العدد من الميزات التي يقدمها ثنائي اﻷبعاد، ويحتاج إلى برامج خارجية أو إضافات لإنشاء التضاريس، وتحريك شخصيات الألعاب المعقدة وهكذا. مع ذلك، يزودك جودو بواجهة برمجية متكاملة لتوسعة وظائف المحرر باستخدام شيفرة اللعبة، سنرى ذلك لاحقًا في هذا المقال. (محرر وهو إضافة على جودو 2 ويساعد على إدارة الحالة والانتقالات بصريًا) محرّك مفتوح المصدر يقدّم جودو قاعدة برمجية مفتوحة المصدر بالكامل وفق ترخيص MIT. ويعني ذلك أن كل التقنيات التي تأتي معه هي حرة أيضًا. وقد طوّرت معظم أجزاء المحرّك من الصفر من قبل مساهمين. ويمكن لأي شخص استخدام اﻹضافات المناسبة لمشروعه ولا يعني ذلك أن تُشحن هذه اﻹضافات مع المحرك. من اﻷمثلة عليها نجد Google AdMob أو FMOD. وأيًا منها يمكن أن يأتي على شكل إضافة مصدرها طرف آخر. وتعني القاعدة البرمجية المفتوحة من ناحية أخرى أنه بإمكانك التعلم من محرّك الالعاب وتوسيعه كما تشاء، وتستطيع تنقيح اﻷلعاب بسهولة، إذ يطبع جودو رسائل الخطأ من خلال متتبع المكدّس stack tracer حتى لو اتت اﻷخطاء من المحرّك نفسه. ملاحظة: لا يؤثر ذلك على العمل الذي تنفذّه على جودو إطلاقًا. فلن ترتبط أي قيمة نصيّة بالمحرّك أو أي شيء تفعله عليه. محرّك يطوّره مجتمعه يُطوّر محرك الألعاب جودو من قبل مجتمعه الخاص، فهو منهم ولهم ولجميع مطوري اﻷلعاب، وحاجات ونقاشات المستخدمين هو اﻷساس الذي يقود تطوير نواته. وتركّز الميزات الجديدة التي يقدمها مطوّرو النواة على ما يقدم الفائدة لمعظم مستخدميه، والقلة فقط من مطوري النواة يعملون رسميًا بدوام كامل. لكن المشروع يضم أكثر من 600 مساهم حتى لحظة كتابة هذه المقالة. فالمبرمجون المساهمون يعملون على تطوير الميزات التي يحتاجونها لأنفسهم لهذا سترى تحسينات من جميع النواحي تظهر معًا عند كل إصدار رئيسي للمحرك. محرر جودو هو في الواقع لعبة جودو يعمل محرر جودو على محرّك اللعبة ويستخدم واجهة المحرّك الرسومية ويمكنه إعادة تحميل الشيفرة والمشاهد مباشرة عند اختبار مشروعك أو تشغيل شيفرة اللعبة ضمن المحرر. أي بإمكانك استخدام نفس الشيفرة في والمشاهد في ألعابك أو بناء إضافات وتوسيع المحرر. ويعطي ذلك مصداقية ومرونة لواجهة المستخدم فهي تدعم المحرر ذاته. فعندما تستخدم التعليمة tool@ ستتمكن من تشغيل شيفرة اللعبة ضمن المحرر. (محرر RPG in a Box هو محرر صمم باستخدام جودو 2 ويستخدم واجهة جودو الرسومية لمنظومته البرمجية المبنية على أساس العقد) ضع التعليمة tool@ في بداية ملف GDScript وستعمل الشيفرة ضمن المحرر. يساعدك ذلك على إدراج وتصدير الإضافات وإنشاء إضافة مخصصة مثل محررات مخصصة للمراحل أو إنشاء سكربتات لها نفس العقد وترتبط بنفس الواجهة البرمجية التي تستخدمها في مشروعك. ملاحظة: كُتب محرر جودو بالكامل باستخدام لغة ++C وصُرّف إلى الصيغة الثنائية. ويعني ذلك أنه من غير الممكن إدراجه كمشروع نمطي على شكل ملف project.godot. محركان منفصلان ثنائي البعد وثلاثي البعد من جودو. يوفّر جودو محركين مخصصين ثنائي وثلاثي البعد. وكنتيجة لذلك ستكون وحدة القياس اﻷساسية للمشهد ثنائي البعد هي البكسل. وعلى الرغم من أن المحركين منفصلين، يمكنك تصيير المشاهد ثنائي البعد في المحرّك ثلاثي البعد والعكس صحيح، كما يمكنك كذلك تضمين الشخصيات والواجهات ثنائية البعد في العالم ثلاثي اﻷبعاد. الخلاصة بهذا تكون قد وصلت لنهاية مقال اليوم الذي ألقينا فيه نظرة عامة عن ميزات محرك الألعاب مفتوح المصدر جودو Godot، ووضحنا أسلوب التصميم المتبع في هذا المحرك وكيفية استخدامه في تطوير الألعاب، وأهم الميزات التي يدعمها مثل التصميم كائني التوجه والوراثة، بالإضافة إلى الأدوات والميزات الحصرية المتوفرة في المحرك، ودعمه لتصميم الألعاب ثنائية وثلاثية الأبعاد مع إمكانية تفاعلهما معًا في تطوير الألعاب، وسلطنا الضوء على الجهود المبذولة من قبل مجتمع مطوري جودو في تطوير المحرك وتحسينه باستمرار لضمان أفضل تجربة استخدام. ترجمة -وبتصرف- لمقال: Godot's design philosophy اقرأ أيضًا تعلم الميزات الجديدة في محرك الألعاب جودو وطرح الأسئلة حوله إعداد محرك الألعاب جودو Godot للعمل مع قاعدة البيانات SQLite تعرف على أهمية صناعة الألعاب الإلكترونية أشهر أنواع الألعاب الإلكترونية
-
بعد أن اطلعنا في مقال سابق على مفهوم جافا سكريبت وما يمكنها فعله، سنتابع في مقالنا هذا فكرة إنشاء تطبيق بسيط باستخدام جافا سكربت من خلال جولة إرشادية عملية نبني من خلالها تطبيق "خمّن الرقم Guess the number" خطوةً بخطوة. عليك قبل البدء بقراءة هذا المقال أن تٌلّم بأساسيات العمل على الحاسوب وأساسيات HTML و CSS وكذلك فهم طبيعة جافا سكريبت. ولا بد أيضًا من اﻹشارة إلى ما يجب أن تتوقعه بعد قراءة هذا المقال، فلا تتوقع أن تتعلم جافا سكريبت أو حتى أن تفهم كل الشيفرة التي نطلب منك كتابتها، بل كل ما نريده هو إعطاؤك فكرة عن طريقة عمل ميزات جافا سكريبت مع بعضها، وكيف تبدو كتابة شيفرة جافا سكريبت. سنعود في مقالات تالية إلى كل الميزات التي نستعرضها في هذا المقال بتفاصيل أكثر. لذا لا تقلق إن لم تفهم كل شيء مباشرةً. ملاحظة: إن الكثير من ميزات شيفرة جافا سكريبت هي نفسها تقريبًا في لغات البرمجة اﻷخرى مثل الدوال functions والحلقات loops وغيرها. فقد تبدو صياغة الشيفرة مختلفة لكن المفهوم يبقى ذاته. فكّر مثل مبرمج من أصعب اﻷمور التي تصادفك عند تعلم البرمجة هو طريقة تطبيق قواعد اللغة لحل مشاكل فعلية وليس تعلّم هذه القواعد. فعليك إذًا أن تتعلم كيف تفكّر مثل المبرمجين، ويتضمن ذلك الاطلاع على وصف ما يتطلّبه برنامجك ثم تقدير ميزات الشيفرة التي تستخدمها ﻹنجاز المطلوب وكيف ستربط بين هذه الميزات. يتطلب اﻷمر مزيجًا من العمل الجاد والخبرة بصياغة اللغة والتدريب، إضافة إلى لمسة من الإبداع. وكلًما كتبت شيفرة أكثر ستتمكن منها أكثر. لن نعدك بالطبع أن تكوّن دماغ مبرمج في خمس دقائق، لكن سنتيح لك فرص عديدة كي تمارس تفكير المبرمج لنلق نظرة إذًا على المثال الذي نبنيه في مقالنا ونراجع الغاية العامة من تقسيمه إلى مهام. تطبيق -خمّن الرقم Guess the number- نعرض في هذا التطبيق كيفية بناء لعبة بسيطة كما في المثال الحي التالي: حاول أن تجرّب اللعبة وتعتاد عليها قبل أن نتابع، ثم لنتخيل أن مديرك قد أعطاك الوصف الموجز التالي للعبة كي تبنيها: بعد الاطلاع على الموجز السابق، لا بد أولًا من تقسيم العملية إلى مهام أصغر قابلة للتنفيذ إستنادًا إلى عقلية المبرمج قدر اﻹمكان: توليد رقم عشوائي بين 1 و 100. تسجيل رقم المحاولة التي وصلها اللاعب ابتداءً من 1. إعطاء اللاعب طريقة ليسجًل العدد الذي يخمّنه. تسجيل العدد الذي خمّنه اللاعب وعرضه في مكان ما كي يرى محاولاته السابقة. التحقق من صحة التخمين. إن كان التخمين صحيحًا: عرض رسالة تهنئة. منع اللاعب من إدخال أية تخمينات أخرى (لأن ذلك يسيء إلى اللعبة). عرض آلية تتيح للاعب اللعب مجددًا. إن كان التخمين خاطئًا: إخبار اللاعب أن تخمينه خاطئ وإن كان التخمين قريبًا جدًا أو بعيدًا جدًا. السماح للاعب بإدخال تخمين جديد. زيادة عدّاد المحاولات بمقدار 1. إن كان التخمين خاطئًا وتجاوز اللاعب عدد المحاولات المتاحة: إبلاغ اللاعب بانتهاء اللعبة. منع اللاعب من إدخال أية تخمينات أخرى (لأن ذلك يسيء إلى اللعبة). عرض آلية تتيح للاعب اللعب مجددًا. التأكد عند إعادة اللعبة أن منطق اللعبة وواجهة المستخدم قد عادا إلى الوضع اﻷصلي، ثم العودة إلى الخطوة 1. سنحاول اﻵن التفكير بآلية لتحويل هذه الخطوات إلى شيفرة صحيحة ثم بناء اللعبة واكتشاف ميزات جافا سكريبت أثناء تقدم العمل. اﻹعدادات اﻷساسية لنبدأ العمل على كتابة الشيفرة البرمجية للعبة تخمين الأرقام والتي ستعمل كما موضح في الصورة التالية. تحتاج في البداية لتحضير نسخة محلية خاصة بك من الملف number-guessing-game-start.html كما تستيطع أن تختبره مباشرة ضمن المستودع المخصص له على جيت-هب. افتح الملف ضمن المتصفح وضمن المحرر النصي. سترى للوهلة الأولى ترويسة بسيطة وفقرة نصية توضح التعليمات ونموذج لإدخال التخمين، لكن التطبيق لن يعمل حاليًا. سنضع كل شيفرة اللعبة ضمن العنصر <script> في نهاية شيفرة HTML: <script> // Your JavaScript goes here </script> إضافة متغيرات لتخزين البيانات أضف بداية الأسطر التالية ضمن العنصر <script>: let randomNumber = Math.floor(Math.random() * 100) + 1; const guesses = document.querySelector(".guesses"); const lastResult = document.querySelector(".lastResult"); const lowOrHi = document.querySelector(".lowOrHi"); const guessSubmit = document.querySelector(".guessSubmit"); const guessField = document.querySelector(".guessField"); let guessCount = 1; let resetButton; يهيئ هذا القسم من الشيفرة المتغيرات والثوابت التي نحتاجها لتخزين البيانات اللازمة لبرنامجنا. تُعرّف المتغيرات variables مبدئيًا بأنها أسماء لقيم مثل اﻷعداد أو النصوص. ويمكنك إنشاء متغير باستخدام التعليمة let يليها اسم المتغير. وتُعرّف الثوابت constant بأنها أسماء لقيم أيضًا لكن لا يمكن تغيير هذه القيم بمجرد أن نهيئها. نستخدم في حالتنا الثوابت لتخزين مراجع إلى عناصر من واجهة المستخدم. وقد يتغير النص ضمن تلك العناصر لكن الثوابت تشير دائمًا إلى العنصر نفسه الذي ضُبطت عليه في البداية. يمكن إنشاء الثابت باستخدام التعليمة const يليها اسم الثابت. يمكن إسناد قيمة إلى الثابت أو المتغير باستخدام الإشارة (=) تليها القيمة التي تريد. في مثالنا: يُسند إلى المتغير اﻷول randomNumber عدد عشوائي بين 1 و 100 يُحسب من خلال خوارزمية رياضية. أنشئت الثوابت الثلاث اﻷولى لتخزين مراجع إلى المقاطع النصية الخاصة بالنتائج في شيفرة HTML وتستخدم ﻹدراج نصوص ضمن هذه الفقرات برمجيًا، ولاحظ أنها ضمن عنصر <div>والذي يُستخدم لاختيار المقاطع الثلاث لتصفيرها (إعادتها إلى وضعها اﻷصلي) لاحقًا عن إعادة اللعبة إلى وضعها اﻷصلي. <div class="resultParas"> <p class="guesses"></p> <p class="lastResult"></p> <p class="lowOrHi"></p> </div> يُخّزن الثابتان التاليان مرجع إلى عنصر اﻹدخال النصي في النموذج وإلى زر اﻹرسال وتُستخدمان للتحكم بإرسال العدد الذي يُخمنه اللاعب. <label for="guessField">Enter a guess: </label> <input type="number" id="guessField" class="guessField" /> <input type="submit" value="Submit guess" class="guessSubmit" /> يٌخزّن المتغيران اﻷخيران القيمة 1 لعدد محاولات اللعب (ويُستخدم لتتبع عدد المحاولات التي قام بها اللاعب) ومرجعًا إلى زر إعادة الضبط الذي لا يظهر حاليًا (لكنه سيظهر لاحقًا). ملاحظة: سنتعلم أكثر عن المتغيرات والثوابت لاحقًا في سلسلة المقالات هذه عن جافا سكريبت. الدوال أضف تاليًا الشيفرة التالية تحت الشيفرة السابقة: function checkGuess() { alert("I am a placeholder"); } الدوال هي كتل من الشيفرة يمكن استخدامها مجددًا، إذ يمكن كتابتها مرة واحدة وتنفيذها مرارًا وتكرارًا موفرين عناء كتابة الشيفرة من جديد، وهذا أمر غاية في اﻷهمية.وهنالك عدد من الطرق في تعريف الدوال لكننا سنركز الآن على نوع بسيط. نستخدم في مثالنا الكلمة المفتاحية function متبوعةً باسم الدالة وبعدها قوسين معقوصين{}. نضع الشيفرة التي تنفذها الدالة عند استدعائها داخل القوسين المعقوصين. وعندما نريد تنفيذ الشيفرة، تكتب اسم الدالة يليها قوسين () لنجرب ذلك اﻵن، احفظ التغييرات التي أجريتها على الملف وحدّث الصفحة ضمن المتصفح. انتقل بعد ذلك إلى (طرفية جافا سكريبت ضمن أدوات مطوري ويب ثم أدخل السطر التالي: checkGuess(); من المفترض أن ترى بعد الضغط على المفتاح Enter/Return رسالة تنبيه نصها I am a placeholder، ﻷننا عرفنا دالة في شيفرتنا تعيد هذه الرسالة كلما استدعيناها. ملاحظة: سنتعلم أكثر عن الدوال لاحقًا في سلسلة المقالات هذه. العوامل تسمح العوامل Operators في لغة جافا سكريبت بإجراء اختبارات وتنفيذ العمليات الرياضية وضم السلاسل النصية إلى بعضها وغيرها من اﻷشياء. فإن لم تكن قد فعلت ذلك مسبقًا، احفظ التغييرات على ملفك ثم أعد تحميل الصفحة في المتصفح وانتقل إلى طرفية جافا سكريبت في أدوات مطوري ويب. حاول أن تنفذ اﻷمثلة المذكورة في الجدول التالي، منتبهًا إلى نقل المثال حرفيًا كما هو ثم انقر Enter بعد كل عملية لترى نتيجتها: العامل اسمه مثال + الجمع 6 + 9 - الطرح 20 - 15 * الضرب 3 * 7 / القسمة 10 / 5 هنالك أيضًا بعض العوامل المختصرة تُدعى عوامل اﻹسناد المركّبة compound assignment operators، فإن أردت مثلًا إضافة عدد جديد إلى عدد موجود وإعادة النتيجة، إليك الطريقة: let number1 = 1; number1 += 2; وهذا مشابه لعمل الشيفرة: let number2 = 1; number2 = number2 + 2; وعند تنفيذ اختبارات نتيجتها (صحيح أو خاطئ) وذلك ضمن العبارات الشرطية (سنراها لاحقًا) نستخدم عوامل الموازنة comparison operators مثل: العامل الاسم مثال === مساواة تامة (هل يساويه تمامًا؟) 5===2+4 // خاطئ، chris===pop// خاطئ، 5===2+3 // صحيح، 2==='2' // خاطئ موازنة رقم بنص ==! لا مساواة تامة (هل هما غير متساويين؟) 5==!2+4 // صحيح، chris!==pop// صحيح، 5==! 2+3 // خاطئ، 2==!'2' // صحيح موازنة رقم بنص < أقل من 10>6 // صحيح، 10>20 //خاطئ > أكبر من 10<6 // خاطئ، 10<20 //صحيح السلاسل النصية تستخدم السلاسل النصية لتمثيل النصوص، وقد رأينا سابقًا متغير نصي في الشيفرة "I am a placeholder": function checkGuess() { alert("I am a placeholder"); } يمكن التصريح عن سلسلة نصية باستخدام إشارتي التنصيص المزدوجتين (" ") أو المفردتين (' ')، لكن ينبغي عليك استخدام أحد اﻷسلوبين من بداية السلسلة إلى نهايتها فلا يمكن أن تكتب مثلًا" "I am a placeholder'. باﻹمكان أيضًا تعريف السلسلة النصية بين علامتي اقتباس مائلتين (``) وتُدعى السلاسل المعرّفة بهذا الشكل بالقوالب المفسّرة template literals التي تحمل بعض الخاصيات المميزة وتحديدًا إمكانية وضع متغيرات أخرى أو تعابير برمجية ضمن السلسلة النصية لتتحول قيمها إلى جزء من النص: const name = "Mahalia"; const greeting = `Hello ${name}`; يمنحك هذا اﻷمر أسلوبًا لضم السلاسل النصية معًا. العبارات الشرطية بالعودة إلى الدالة ()ckeckGuess نجد أنه من المنطقي ألا تكون وظيفتها طباعة رسالة فقط، بل من المفترض أن تتحقق من صحة العدد الذي يخمّنه اللاعب وتعيد الجواب الملائم. لنستبدل اﻵن بالشيفرة التالية ما كان موجودًا ضمن الدالة ()ckeckGuess: function checkGuess() { const userGuess = Number(guessField.value); if (guessCount === 1) { guesses.textContent = "Previous guesses:"; } guesses.textContent = `${guesses.textContent} ${userGuess}`; if (userGuess === randomNumber) { lastResult.textContent = "Congratulations! You got it right!"; lastResult.style.backgroundColor = "green"; lowOrHi.textContent = ""; setGameOver(); } else if (guessCount === 10) { lastResult.textContent = "!!!GAME OVER!!!"; lowOrHi.textContent = ""; setGameOver(); } else { lastResult.textContent = "Wrong!"; lastResult.style.backgroundColor = "red"; if (userGuess < randomNumber) { lowOrHi.textContent = "Last guess was too low!"; } else if (userGuess > randomNumber) { lowOrHi.textContent = "Last guess was too high!"; } } guessCount++; guessField.value = ""; guessField.focus(); } ما وضعناه كمية كبيرة من الشيفرة بالفعل، لكن سنمر على كل قسم منها ونشرحه: يعرّف السطر اﻷول متغيرًا يُدعى userGuess وتُسند إليه القيمة التي أُدخلت ضمن المربع النصي في النموذج. نمرر بعد ذلك هذه القيمة إلى الدالة ()Number المدمجة في جافا سكريبت وذلك للتأكد أن هذه القيمة عدد بالفعل. وطالما أننا لن نغيّر هذا العدد فقد قررنا أن يكون ثابتًا const. نواجه بعد ذلك أول كتلة شرطية، وتُستخدم الكتل الشرطية لتنفيذ الشيفرة انتقائيًا إن تحقق شرط معين أو لم يتحقق. قد تبدو العبارة الشرطية شبيهة بالدالة لكنه ليست كذلك. تبدأ أبسط أشكال الكتل الشرطية بالكلمة المحجوزة if يليها بعض اﻷقواس ثم بعض اﻷقواس المعقوصة. يوضع الشرط أو الاختبار بين اﻷقواس العادية، فإن أعاد تنفيذ الشرط النتيجة true تُنفَّذ الشيفرة الموجودة ضمن اﻷقواس المعقوصة، وإن لم يكن اﻷمر كذلك، فلن تنفَّذ تلك الشيفرة وينتقل التنفيذ إلى القسم اﻵخر من الشيفرة. يختبر الشرط فيما لو كانت قيمة المتغيّر guessCount مساويًا للقيمة 1 (أي إذا كانت هذه المحاولة اﻷولى للاعب أم لا): guessCount === 1; إن تحقق ذلك، نجعل النص المحتوى ضمن الفقرة النصية guesses مساويًا Previous guesses، وإن لم يتحقق لا نفعل ذلك. نستخدم بعد ذلك قالبًا مفسّرًا لضم قيمة المتغّير userGuess إلى نهاية النص في الفقرة النصية guesses مع وجود مسافة فارغة بينهما. تُنفِّذ الشيفرة التالية عدة اختبارات: تتحق العبارة الشرطية اﻷولى {}()if إن كان التخمين الذي أدخله اللاعب مساويًا قيمة المتغيّر randomNumber الذي ضُبطت قيمته في بداية الشيفرة. فإن كان تخمين اللاعب صحيحًا، ربح اللعبة، لهذا نعرض رسالة تهنئة بلون أخضر ونمسح محتويات صندوق المعلومات الذي يشير إلى بعد أوقرب التخمين الخاطئ عن القيمة الصحيحة، ثم تُنفَّذ الدالة ()setGameOver التي سنناقشها لاحقًا. نربط بعد ذلك اختبارًا آخر في نهاية اﻷول من خلال البنية {}()else if والتي تتحقق إن كان الدور الذي لعبه اللاعب هو آخر دور له. فإن كان كذلك، يكرر ما فعلناه في الكتلة السابقة لكن مع رسالة تشير إلى نهاية اللعبة بدلًا من رسالة التهنئة. أما الكتلة اﻷخيرة التي نربطها بسابقتها فهي الكتلة {}else التي تضم شيفرة تُنفَّذ فقط إن فشل الاختباران في الكتلتين السابقتين (أي اللاعب لم يخمن العدد، ,ولم ينهي جميع محاولاته). نخبره في هذه الحالة أن تخمينه خاطئ ثم ننفذ اختبارًا آخر يتحقق إن كان تخمينه أكبر بكثير أو أقل بكثير من العدد الصحيح ثم نعرض رسالة أخرى بطريقة مناسبة ﻹخبار اللاعب بذلك. تحضر اﻷسطر الثلاث اﻷخيرة من الدالة (من 26 إلى 28) إرسال لتخمين التالي. إذ تضيف 1 إلى المتغير guessCount كي يستنفذ اللاعب دورًا جديدًا (العامل ++ هو عامل يزيد قيمة المتغير بمقدار 1). وتفرّغ قيمة المربع النصي في النموذج وتعطيه تركيز الدخل مجددًا، وبهذا يصبح اللاعب جاهزًا ﻹدخال التخمين التالي. اﻷحداث ما فعلنا حتى اﻵن هو إنجاز الدالة لكنها لن تفعل شيئًا لأننا لم نستدعها بعد. ومن المنطقي أن نستدعي هذه الدالة عند النقر على زر "Submit guess"، ولإنجاز ذلك، نحتاج إلى ما يُدعى حدثًا event. فاﻷحداث هي أشياء تحدث في المتصفح مثل نقر زر أو تحميل صفحة أو تشغيل فيديو وغيرها. ولكي ينفّذ المتصفح كتلة من الشيفرة كاستجابة لحدث ما نحتاج إلى مترصّد أحداث event listener يراقب أحداث معينة ويستدعي معالج أحداث event handler مناسب، والمعالج هو كتل من الشيفرة تعمل استجابةً لوقوع حدث في المتصفح. أضف السطر التالي تحت الدالة ()checkGuess: guessSubmit.addEventListener("click", checkGuess); أضفنا هنا مترصّد أحداث إلى الزر، وهو تابع يأخذ قيمتين (تُدعيان وسيطين arguments)، اﻷول هو نوع الحدث الذي نترصده فهو حدث (النقر click) وله قيمة نصية، والثاني هو الشيفرة التي تُنفَّذ عند وقوع الحدث وهي في حالتنا الدالة ()checkGuess. ولاحظ أنه لا حاجة لوضع القوسين عند كتابة الدالة كوسيط لمترصد الحدث ()addEventListener جرّب حفظ التغييرات على الملف ثم إعادة تحميل الصفحة في متصفحك وسيعمل التطبيق إلى حد معين. وما سيحدث أنه لو خمّنت العدد الصحيح أو أنهيت كل محاولاتك ستنهار اللعبة لأننا لم نعرّف بعد الدالة ()setGameOver التي من المفترض أن تُستدعى عند نهاية اللعبة. لهذا سنضيف الشيفرة الناقصة ﻹكمال اللعبة إنهاء جميع وظائف اللعبة لنضف اﻵن الدالة ()setGameOver إلى أسفل الشيفرة التي كتبتها حتى اﻵن ثم نشرح ما تفعله: function setGameOver() { guessField.disabled = true; guessSubmit.disabled = true; resetButton = document.createElement("button"); resetButton.textContent = "Start new game"; document.body.append(resetButton); resetButton.addEventListener("click", resetGame); } يعطّل أول سطرين عمل عنصر اﻹدخال النصي في النموذج وعمل الزر بضبط الخاصية disabled على القيمة trueلكل منهما. وهذا اﻷمر ضروري، لأن اللاعب سيتمكن من إرسال قيم تخمين أخرى رغم إنتهاء اللعبة، وسيجعل ذلك اللعبة فوضوية. توّلد اﻷسطر الثلاث التالية زرًا جديدًا <button>بعنوان "Start new game" ومن ثم تضيفه إلى شيفرة HTML الموجودة. يضبط السطر اﻷخير مترصّد أحداث للزر الجديد كي يترصد حدث النقر عليه ويستدعي الدالة ()resetGame. علينا اﻵن كتابة الشيفرة الخاصة بالدالة اﻷخيرة، لهذا ضع الشيفرة التالية في آخر ما كتبته: function resetGame() { guessCount = 1; const resetParas = document.querySelectorAll(".resultParas p"); for (const resetPara of resetParas) { resetPara.textContent = ""; } resetButton.parentNode.removeChild(resetButton); guessField.disabled = false; guessSubmit.disabled = false; guessField.value = ""; guessField.focus(); lastResult.style.backgroundColor = "white"; randomNumber = Math.floor(Math.random() * 100) + 1; } هذه الكتلة من الشيفرة طويلة نوعًا ما وتعيد كل شيء إلى وضعه اﻷصلي عند بداية اللعبة، وبالتالي يصبح اللاعب قادرًا على المتابعة من جديد، فهي: تعيد قيمة المتغير guessCount إلى 1. تفرّغ كل ما هو موجود في الفقرات النصية. فقد اخترنا جميع الفقرات النصية ضمن العنصر <div class="resultParas"></div> ثم تفقدنا من خلال حلقة كل فقرة وضبطنا الخاصية textContent لكل منها على' ' (سلسلة فارغة). تزيل زر إعادة الضبط من الشيفرة. تفعّل عناصر النموذج وتفرّغ الفقرات النصية وتعطي تركيز الدخل إلى مربع اﻹدخال النصي في النموذج كي يصبح جاهزًا لتلقي تخمين جديد من قبل اللاعب. تزيل لون خلفية الفقرة النصية lastResult. توّلد رقم عشوائي جديد كي لا تخمّن نفس الرقم العشوائي السابق. لقد اكتملت اللعبة اﻵن، تهانينا! وكل ما بقي لنا في هذا المقال هو الحديث عن بعض الميزات الهامة التي رأيناها في شيفرة اللعبة والتي ربما لم تدركها. الحلقات من أهم أجزاء الشيفرة التي كتبناها، والتي لا بد من الحديث عنها هي حلقة (for…of). فالحلقات هي مفاهيم برمجية غاية في اﻷهمية تتيح لك تنفيذ جزء من الشيفرة مرارًا وتكرارًا حتى يتحقق شرط معين. وكي نبدأ تتبع عمل الحلقات انتقل إلى طرفية جافا سكريبت في أدوات مطوري ويب، ثم أدخل الشيفرة التالية: const fruits = ["apples", "bananas", "cherries"]; for (const fruit of fruits) { console.log(fruit); } ما الذي حدث؟ لقد طبعت الكلمات 'apples', 'bananas', 'cherries' على الشاشة كنتيجة لتنفيذ الحلقة. لاحظ السطر التالي: ;['const fruits = ['apples', 'bananas', 'cherries يُنشئ هذا السطر مصفوفة (سنتعرف عليها لاحقًا في مقال لاحق) وهي مبدئيًا مجموعة من العناصر (سلاسل نصية في حالتنا). وتزوّدك الحلقة for...of بطريقة للحصول على كل عنصر من المصفوفة وتطبيق شيفرة جافا سكريبت عليه، فما يفعله السطر (for (const fruit of fruits هو التالي: الحصول على أول عناصر المصفوفة fruits. ضبط قيمة المتغيّر fruit لتكون قيمة العنصر اﻷول، ثم تنفيذ شيفرة جافا سكريبت الموجودة داخل القوسين {}. الحصول على العنصر التالي من المصفوفة fruits. في هذه الحالة، تطبع الشيفرة قيمة المتغيّر fruit على الشاشة. لنلق نظرة اﻵن على الحلقة الموجودة في لعبة "خمّن الرقم" وتحديدًا ضمن الدالة ()resetGame: const resetParas = document.querySelectorAll(".resultParas p"); for (const resetPara of resetParas) { resetPara.textContent = ""; } توّلد هذه الشيفرة متغيّرًا يضم قائمة بجميع الفقرات النصية الموجودة ضمن العنصر <div class="resultParas"> باستخدام التابع ()querySelectorAll ومن ثم يتفقد كل فقرة باستخدام حلقة ويزيل المحتوى النصي لها. وتجدر الملاحظة أن ثوابت مثل resetParas يمكن إزالة المحتوى النصي لها. مناقشة سريع لمفهوم الكائن Object في جافا سكريبت سنضيف تحسينًا أخيرًا إلى اللعبة قبل الدخول في هذا النقاش وهو سطر يجب وضعه تحت السطر ;let resetButton بالقرب من بداية شيفرة جافا سكريبت ثم نحفظ التغيرات: guessField.focus(); يستخدم هذا السطر التابع ()focus الذي يجعل مؤشر الدخل ضمن عنصر اﻹدخال النصي <input> حالما تُحمّّل الصفحة، وهكذا يصبح اللاعب جاهزًا لكتابة تخمينه مباشرة دون الحاجة إلى النقر داخل مربع اﻹدخال النصي. صحيح أنها إضافة صغيرة لكنها تحسن استخدام اللعبة وتمنح اللاعب فكرة عما سيفعله تاليًا حتى يبدأ اللعب. لنحلل ما يجري هنا بمزيد من التفصيل. إذ نتعامل غالبًا في جافا سكريبت مع الكائنات objects، والكائن هو مجموعة مترابطة من الوظائف المخزّنة في شيء واحد. بإمكانك بالطبع إنشاء كائنات خاصة بك، لكن اﻷمر متقدم قليلًا ولن نغطيه في مقالنا. أما ما نناقشه اﻵن باختصار فهي الكائنات المدمجة مع اللغة والتي يتضمنها متصفحك وتسمح لك بتنفيذ العديد من الأمور المفيدة. في هذه الحالة الخاصة، أنشأنا ثابتًا يخزّن مرجعًا إلى عنصر اﻹدخال النصي الموجود في نموذج HTML، وستجد السطر التالي ضمن التصريحات في بداية الشيفرة: const guessField = document.querySelector(".guessField"); وللحصول على هذا المرجع، استخدمنا التابع ()querySelector العائد إلى الكائن document. ويأخذ هذا التابع معلومة واحدة وهي محدد CSS الذي يُستخدم ﻹنتقاء العنصر الذي تريد إنشاء مرجع إليه. ولأن الثابت guessField يحتوي مرجعًا إلى العنصر <input>، فلديه القدرة اﻵن على الوصول إلى خصائصه (تُخزَّن المتغيرات أساسًا ضمن كائنات، ولا يمكن تغيير قيم بعضها) وتوابعه (وهي أساسًا دوال مخزّنة ضمنه). ومن أحد توابع عنصر اﻹدخال النصي نجد ()focus لهذا يمكننا استخدام السطر التالي لنقل تركيز الدخل إلى هذا العنصر: guessField.focus(); لا يمكن استخدام التابع ()focus على المتغيرات التي لا تضم مراجعًا إلى عناصر، فالثابت guesses مثلًا يضم مرجعًا إلى العنصر <p> والمتغير guessCount يضم عددًا. التعامل مع كائنات المتصفح لنعمل قليلًا مع بعض كائنات المتصفح: افتح ملف اللعبة أولًا ضمن المتصفح. افتح أدوات مطوري ويب في متصفحك (من زر القائمة>أدوات إضافية>أدوات مطوري ويب "في متصفح فايرفكس"). وتأكد من وصولك إلى طرفية جافا سكريبت. اكتب في الطرفية guessField وستعرض لك حينها أن هذا المتغيّر يضم العنصر <input>، وستلاحظ أيضًا أن الطرفية تعرض لك تلقائيًا أسماء جميع الكائنات الموجودة ضمن بيئة التنفيذ بما في ذلك متغيّراتك التي صرحت عنها. اكتب اﻵن مايلي: guessField.value = 2; تمثل الخاصية value القيمة الحالية المُدخلة إلى مربع النص، وسترى أن تنفيذ اﻷمر السابق يغيّر النص الموجود في مربع النص. جرّب أن تكتب guesses في الطرفية ثم اضغط المفتاح "Enter" وسترى أن هذا المتغير يتضمن العنصر <p>. جرّب كتابة اﻷمر التالي: guesses.value وسيعيد المتصفح القيمة undefined لأن الفقرة النصية لا تمتلك الخاصية value لتغيير النص داخل الفقرة النصية جرّب الأمر التالي: "?guesses.textContent="Where is my paragraph ولكي تجرب أشياء أخرى، اكتب اﻷسطر التالية: guesses.style.backgroundColor = "yellow"; guesses.style.fontSize = "200%"; guesses.style.padding = "10px"; guesses.style.boxShadow = "3px 3px 6px black"; لكل عنصر في الصفحة خاصية تُدعى style تضم بحد ذاتها كائنًا له خاصيات تتضمن جميع تنسيقات CSS السطرية المطبقة على العنصر. ويسمح لنا ذلك ضبط تنسيقات جديدة للعنصر ديناميكيًا من خلال جافا سكريبت. الخلاصة هكذا نكون قد بنينا تطبيقًا بسيط باستخدام جافا سكريبت، استمتع بتجريب ما فعلته أو جرّب النسخة الجاهزة من اللعبة على جيت-هاب كما يمكنك تحميل شيفرتها المصدرية أيضًا. ترجمة -وبتصرف- للمقال Afirst splash into JavaScript اقرأ أيضًا المقال السابق: تعرّف على لغة جافا سكريبت من منظور عام تعلم لغة جافا سكريبت من الصفر حتى الاحتراف أساسيات لغة جافاسكربت العبارات الشرطية واتخاذ القرار في جافا سكريبت مدخل إلى جافاسكريبت كائنية التوجه (Object-Oriented JavaScript)
-
نتابع في سلسلة المقالات هذه تغطية أساسيات جافاسكريبت وميزاتها، وتحديدًا أنواع البنى البرمجية التي نصادفها بكثرة في الشيفرة مثل العبارات الشرطية والحلقات والدوال والأحداث. وقد اطلعنا على هذه النقاط بشكل مبسط في سلسلة المقالات السابقة لكننا سنناقشها هنا بشيء من التفصيل. ننصحك قبل أن تبدأ العمل معنا في هذه السلسلة أن تطلع على: أساسيات علوم الحاسب. أساسيات HTML. أساسيات عمل CSS أساسيات جافاسكربت كما شرحناها في سلسلة المقالات السابقة. ملاحظة: إن كنت تستخدم حاسوب أو جهاز لوحي أو غيره من الأجهزة التي لا تمكّنك من إنشاء ملفاتك الخاصة، جرِّب الشيفرة التي ستجدها في الأمثلة من خلال برامج كتابة شيفرة على الإنترنت مثل Glitch أو JSBin. نتحدث في هذا المقال عن مفهوم العبارات الشرطية في جافاسكريبت إذ لا بد من اتخاذ قرارات عند كتابة الشيفرة في أية لغة برمجة ومن ثم تنفيذ بعض اﻹجراءات وفقًا لمعطيات الدخل المختلفة. فلو أخذنا لعبة ما كمثال، سنجد أن اللعبة تنتهي عندما يصبح عدد المحاولات المسموحة 0، ونأخذ كمثال آخر تطبيقًا للطقس يعرض صورة شروق الشمس في الصباح ويعرض صورة قمر ونجوم في ساعات الليل. هذا اﻷفعال تقرر من خلال استخدام البنى الشرطية وستكون موضوع هذا المقال. لماذا نستخدم عبارات شرطية في جافا سكريبت؟ يتخذ البشر (وكائنات حية أخرى) باستمرار قرارات تؤثر على حياتهم منها البسيطة (كان تقرر تناول قطعة أو قطعتي بسكويت) أو قرارات مصيرية (هل سأبقى في بلدي والعمل في مزرعة أبي أو أن أسافر لأكمل دراستي). وهذا ما تمثله العبارات الشرطية في جافا سكريبت تمامًا، سواء لاتخاذ قرار (أكل قطعة بسكويت واحدة مثلًا) أو معالجة نتائج هذه القرارات (إن اكل قطعة واحد فقد لا أشعر بالشبع، وإن أكلت أكثر فقد أشعر بالتخمة). العبارة الشرطية if...else نلقي نظرة في هذا القسم على العبارة الشرطية if...else، وهي أكثر العبارات الشرطية شيوعًا في جافا سكريبت. الصياغة القواعدية اﻷساسية للعبارة if...else تبدو هذه العبارة بالشكل التالي: if (condition) { /* code to run if condition is true */ } else { /* run some other code instead */ } لدينا هنا: الكلمة المحجوزة if ويليها قوسان عاديان(). شرط يجري اختباره يوضع ضمن القوسين ( مثل هل قيمة ما أكبر من قيمة أخرى أو هل لهذه القيمة وجود؟). وتستخدم الشرط في صياغته عادة عوامل مقارنة تعيد إحدى النتيجتين صحيح true أو خاطئ false. مجموعة من اﻷقواس المعقوصة {} وضمنها بعض الشيفرة، وقد تكون هذه الشيفرة أي شيء نريده وتُنفَّذ إن تحقق الشرط. الكلمة المحجوزة else. مجموعة أخرى من اﻷقواس المعقوصة، وضمنها بعض الشيفرة التي تُنفَّذ فقط إن لم يتحقق الشرط (كانت نتيجته false). يمكن بسهولة قراءة الشيفرة السابقة التي تقول "إذا أعاد الشرط النتيجة true نفّذ الشيفرة A وإلا نفّذ الشيفرة B. وتجدر الملاحظة أنه ليس من الضروري تضمين القسم else، فالشيفرة التالية صحيحة تمامًا: if (condition) { /* code to run if condition is true */ } /* run some other code */ لكن لا بد من الانتباه في هذه الحالة إلى أن العبارة الشرطية في هذه الحالة لن تتحكم بكتلة الشيفرة التي تليها والتي ستُنفَّذ أيًا كانت نتيجة الشرط. ولا نعني بذلك أن هذا أمر سيء لكن قد لا يتحقق ما تريده بالطريقة المتوقعة، لأنك غالبًا ما تريد تنفيذ أحد كتلتين من الشيفرة في الحالات الشرطية وليس كلاهما. وأخيرًا قد تجد العبارة الشرطية دون أقواس معقوصة مع أنه أمر غير مرغوب إطلاقًا: if (condition) /* code to run if condition is true */ else /* run some other code instead */ لا يوجد بالطبع خطأ في الصياغة السابقة لكن وجود اﻷقواس يجعل الشيفرة أكثر وضوحًا، فهي توضح حدود الشيفرة التي تتحكم بها العبارة الشرطية، كما أن استخدام أسطر متعددة مع إزاحة مناسبة يسهّل قراءة الشيفرة أكثر. مثال واقعي لفهم صياغة العبارة الشرطية بصورة أفضل، لنتخيل في هذا المثال الواقعي طفلًا يطلب مساعدة أمه أو أبيه في مهمة ما. فقد يقول الوالد أو الوالدة "إن ساعدتني في التسوق سأعطيك مصروفًا إضافيًا كي تشتري اللعبة التي تريد". يمكن أن نمثل اﻷمر في جافا سكريبت كالتالي: let shoppingDone = false; let childsAllowance; if (shoppingDone === true) { childsAllowance = 10; } else { childsAllowance = 5; } تجعل الشيفرة السابقة قيمة المتغير shoppingDone دائمًا false وهذا يعني أن ظن الطفل سيخيب. لكن بالطبع يمكننا اقتراح آلية على الوالدين لتصبح قيمة المتغير shoppingDone هي true إن ساعد الطفل الوالد في التسوق. التعليمة else if يزودنا المثال السابق بخيارين أو نتيجتين، لكن ماذا لو أردنا خيارات أكثر؟ توجد طريقة لربط عدة خيارات أو نتائج إلى العبارة if...else باستخدام التعليمة else if التي تضم كتلة إضافية من الشيفرة بين الكتلتين {}()if و {} else. لنلق نظرة على المثال التالي الذي يصلح أن يكون جزءًا من تطبيق للأحوال الجوية: <label for="weather">Select the weather type today: </label> <select id="weather"> <option value="">--Make a choice--</option> <option value="sunny">Sunny</option> <option value="rainy">Rainy</option> <option value="snowing">Snowing</option> <option value="overcast">Overcast</option> </select> <p></p> const select = document.querySelector("select"); const para = document.querySelector("p"); select.addEventListener("change", setWeather); function setWeather() { const choice = select.value; if (choice === "sunny") { para.textContent = "It is nice and sunny outside today. Wear shorts! Go to the beach, or the park, and get an ice cream."; } else if (choice === "rainy") { para.textContent = "Rain is falling outside; take a rain coat and an umbrella, and don't stay out for too long."; } else if (choice === "snowing") { para.textContent = "The snow is coming down — it is freezing! Best to stay in with a cup of hot chocolate, or go build a snowman."; } else if (choice === "overcast") { para.textContent = "It isn't raining, but the sky is grey and gloomy; it could turn any minute, so take a rain coat just in case."; } else { para.textContent = ""; } } See the Pen js-making decisions1 by Hsoub Academy (@HsoubAcademy) on CodePen. لدينا في الشيفرة السابقة العنصر <select> الذي يسمح لنا باختيار أحد التوقعات الجوية إضافة إلى فقرة نصية بسيطة. نخزّن في شيفرة جافا سكريبت مرجعًا إلى العنصر <select> وآخر للفقرة النصية <p> ونضيف مترصد أحداث إلى <select> كي يستدعي الدالة ()setWeather عندما تتغير قيمته (يتغير الخيار). عندما تُنفَّذ هذه الدالة، تُسند إلى المتغير choice قيمة العنصر <select> ثم نستخدم عبارة شرطية لعرض نصوص مختلفة ضمن الفقرة النصية وفقًا لقيمة المتغير choice. لاحظ كيف اختُبرت كل الشروط باستخدام الكتلة {}()else if ما عدا الشرط اﻷول الذي استخدمنا في اختباره {}()if. أما آخر الخيارات فقد وضع ضمن الكتلة {} else ليكون الملاذ اﻷخير الذي تُنفّذ الشيفرة التي يضمها إن فشلت جميع الاختبارات السابقة. إذ تفرغ شيفرة هذه الكتلة النص الموجود في الفقرة النصية إن لم يختار المستخدم أيًا من خيارات العنصر <select> أي أعاد استخدام الخيار "--Make a choice--" الذي يُعرض افتراضيًا. ملاحظة حول عوامل المقارنة تُستخدم عوامل المقارنة للتحقق من صحة شرط العبارة الشرطية. ولو عدنا إلى المقال الذي يشرح [العوامل الرياضية]() سنجد الخيارات التالية: === و !== لاختبار تطابق قيمة أو عدم تطابقها مع قيمة أخرى. < و > لاختبار ما إذا كانت قيمة ما أكبر تمامًا أو أصغر تمامًا من أخرى. <= و >= لاختبار ما إذا كانت قيمة ما أكبر أو تساوي أو أصغر أو تساوي قيمة أخرى. نريد هنا اﻹشارة بشكل خاص إلى اختبار القيم المنطقية (true و false) ونموذج شائع الاستخدام في ذلك. تُعيد أية قيمة ليست إحدى القيم التالية: false أو undefined أو null أو 0 أو NaN أو ('') القيمة true عندما تستخدم كشرط في عبارة شرطية، وبالتالي بإمكانك استخدام اسم لمتغير لاختبار وجوده أو إن كانت قيمته true. إليك مثالًا: let cheese = "Cheddar"; if (cheese) { console.log("Yay! Cheese available for making cheese on toast."); } else { console.log("No cheese on toast for you today."); } وبالعودة إلى مثالنا السابق حول الطفل الذي يؤدي عملًا لوالديه، يمكن إعادة كتابة المثال كالتالي: let shoppingDone = false; let childsAllowance; //'shoppingDone === true' لا نريد أن نقول صراحة أن if (shoppingDone) { childsAllowance = 10; } else { childsAllowance = 5; } كتل if..else متداخلة باﻹمكان وضع الكتلة if...else ضمن كتلة if...else أخرى. إذ يمكننا مثلًا كتابة تمرين التوقعات الجوية ليعرض مجموعة أخرى من الخيارات وفقًا لدرجة الحرارة أيضًا: if (choice === "sunny") { if (temperature < 86) { para.textContent = `It is ${temperature} degrees outside — nice and sunny. Let's go out to the beach, or the park, and get an ice cream.`; } else if (temperature >= 86) { para.textContent = `It is ${temperature} degrees outside — REALLY HOT! If you want to go outside, make sure to put some sunscreen on.`; } } ستعمل الشيفرة ككل بتناسق مع بعضها، لكن كل عبارة if...else تعمل بشكل منفصل تمامًا عن اﻷخرى. العوامل المنطقية: AND و OR و NOT إن أردت اختبار عدة شروط دون أن تكتب عبارات متداخلة، يمكنك استخدام العوامل المنطقية وهي: العامل AND &&: يسمح لك بربط عبارتين أو أكثر وينبغي أن تكون نتيجة جميع هذه العبارات true حتى تعيد العبارة الشرطية النتيجة true. العامل OR ||: يسمح لك بربط عبارتين أو أكثر وينبغي أن تكون نتيجة إحدى هذه العبارات true حتى تعيد العبارة الشرطية النتيجة true. وللتعرف على عمل AND، سنعيد كتابة المثال السابق باستخدامها: if (choice === "sunny" && temperature < 86) { para.textContent = `It is ${temperature} degrees outside — nice and sunny. Let's go out to the beach, or the park, and get an ice cream.`; } else if (choice === "sunny" && temperature >= 86) { para.textContent = `It is ${temperature} degrees outside — REALLY HOT! If you want to go outside, make sure to put some sunscreen on.`; } تُنفَّذ الكتلة البرمجية اﻷولى إن أعاد الاختبار (choice === 'sunny' and temperature < 86) النتيجة true. لنلق نظرة اﻵن على مثال سريع عن استخدام OR: if (iceCreamVanOutside || houseStatus === "on fire") { console.log("You should leave the house quickly."); } else { console.log("Probably should just stay in then."); } أما النوع اﻷخير من العوامل المنطقة NOT والذي يعبّر عنه بالمحرف !، فيُستخدم لنفي عبارة، إليك المثال السابق بوجود عامل النفي: if (!(iceCreamVanOutside || houseStatus === "on fire")) { console.log("Probably should just stay in then."); } else { console.log("You should leave the house quickly."); } فإن أعادت العبارة OR القيمة true سينفيها العامل NOT وستعيد العبارة الكاملة القيمة false. بإمكانك دمج العدد الذي تساء من العوامل المنطقية مع بعضها إن أردت ووفقًا للبنية البرمجية التي تريدها. إليك مثالًا تُنفَّذ فيه شيفرة الكتلة إذا أعادت كلتا عبارتي OR القيمة true أي عندما تعيد العبارة الكلية AND القيمة true. if ((x === 5 || y > 3 || z <= 10) && (loggedIn || userName === "Steve")) { // run the code } من اﻷخطاء الشائعة المرتكبة عند استخدام العامل OR في العبارات الشرطية هو كتابة المتغير الذي تريد التحقق من حالته، ثم كتابة قائمة من القيم التي تريد موازنتها بالمتغير يفصل بينها العامل OR (||) كما في المثال التالي: // OR استخدام خاطئ للعامل if (x === 5 || 7 || 10 || 20) { // run my code } في هذه الحالة ستكون نتيجة الشرط داخل ()if صحيحة دائمًا لأن الرقم 7 أو أي عدد غير معدوم سيعطي النتيجة true ويُفهم اﻷمر على النحو " إذا كان x مساويًا 5 أو 7 ستكون النتيجة true" وهذا أمر محقق دائمًا (كون x متغير غير محدد القيمة). لكنك لا تريد هذه النتيجة منطقيًا، وعليك تصحيح الكود السابق على النحو التالي: if (x === 5 || x === 7 || x === 10 || x === 20) { // run my code } عبارة الاختيار المتعدد switch تؤدي عبارة if واجبها تمامًا في اختبار الشروط، لكن لا يخلو اﻷمر من بعض السلبيات. فهي أسلوب جيد عندما تكون أمام خيارات محدودة يتضمن كل منها كمية معقولة من الشيفرة، أو عندما يكون الشرط مركبًا ويضم عدة عوامل موازنة منطقية. لكن في الحالات التي تريد فيا إسناد قيمة إلى متغير أو طباعة عبارة محددة عند تحقق شرط معين، ستكون الشيفرة طويلة وخاصة إن كان أمامك عدد كبير من الخيارات. تظهر في حالات كهذه فائدة البنية switch التي تأخذ عبارة أو قيمة واحدة ثم تبحث عن هذه القيمة أو الشرط بين الخيارات المتعددة المتاحة حتى تجد المطلوب وتنفذ عندها الشيفرة التي يضمها الخيار المطابق. إليك قالب استخدام switch: switch (expression) { case choice1: // run this code break; case choice2: // run this code instead break; // include as many cases as you like default: // actually, just run this code break; } لدينا في الكود السابق: الكلمة المحجوزة switch يليها قوسان. عبارة أو قيمة ضمن القوسين. الكلمة المحجوزة case يتبعها خيار يمثل ما يمكن أن تكونه العبارة أو القيمة التي تشير إليها. شيفرة تُنفَّذ إن طابق الخيار العبارة أو القيمة. الكلمة المحجوزة break تليها فاصلة منقوطة كي يتوقف المتصفح عن التحقق من بقية الخيارات عندما يجد الخيار المطابق ويتابع تنفيذ الشيفرة ما بعد الكتلة switch. تكرار للبنية case وفقًا لعدد الخيارات الموجودة. الكلمة المحجوزة default يليها نمط الشيفرة الموجود في أي حالة case أخرى ما عدا أن default لا يتبعها خيار محدد ولا تحتاج إلى التعليمة break لأنها آخر ما يُنفّذ في عبارة switch ويمثّل الخيار الافتراضي إن أخفق التطابق مع جميع الخيارات المتاحة. ملاحظة: لا حاجة لوجود الجزء default وبإمكانك حذفه إن كنت متأكدًا تمامًا من وجود حالة تطابق. لكن إن أمكن وجود حالة عدم تطابق لا بد من تضمينها لمعالجة الحالات المجهولة. مثال عن كتلة switch لنلق نظرة اﻵن على مثال واقعي، يُظهر استخدام الكتلة switch في تطبيق الأحوال الجوية: <label for="weather">Select the weather type today: </label> <select id="weather"> <option value="">--Make a choice--</option> <option value="sunny">Sunny</option> <option value="rainy">Rainy</option> <option value="snowing">Snowing</option> <option value="overcast">Overcast</option> </select> <p></p> const select = document.querySelector("select"); const para = document.querySelector("p"); select.addEventListener("change", setWeather); function setWeather() { const choice = select.value; switch (choice) { case "sunny": para.textContent = "It is nice and sunny outside today. Wear shorts! Go to the beach, or the park, and get an ice cream."; break; case "rainy": para.textContent = "Rain is falling outside; take a rain coat and an umbrella, and don't stay out for too long."; break; case "snowing": para.textContent = "The snow is coming down — it is freezing! Best to stay in with a cup of hot chocolate, or go build a snowman."; break; case "overcast": para.textContent = "It isn't raining, but the sky is grey and gloomy; it could turn any minute, so take a rain coat just in case."; break; default: para.textContent = ""; } } العامل الثلاثي Ternary operator نريد أخيرًا المرور على صيغة أخيرة للعبارات الشرطية قبل الانتقال إلى التمارين وهي العامل الثلاثي الذي يختبر شرطًا واحدًا ويعيد قيمة أو عبارة إن كان الشرط محققًا وقيمة أو عبارة أخرى إن لم يكن محققًا. لهذا العامل فائدته في بعض الحالات ويحتاج إلى شيفرة أقل من كتلة if...else في حال كان لديك خيارين اثنين لانتقاء أحدهما وفقًا لنتيجة من الشكل true/false. condition ? run this code : run this code instead إليك مثالًا بسيطًا: const greeting = isBirthday ? "Happy birthday Mrs. Smith — we hope you have a great day!" : "Good morning Mrs. Smith."; تعرض الشيفرة متغيرًا يُدعى isBirthday فإن كانت قيمته true يُحييا الضيف برسالة معايدة بعيد ميلاده، أما إن كانت قيمته false فيُحييا بالتحية الاعتيادية. مثال عن العامل الثلاثي لا تُستخدم المتغيرات فقط مع العامل الثلاثي، بل يستطيع أن ينفذ دوال أو أسطر متعددة من الشيفرة. ولتوضيح الأمر، يعرض المثال التالي برنامجًا لاختيار سمة لتنسيق موقع ويب باستخدام العامل الثلاثي: <label for="theme">Select theme: </label> <select id="theme"> <option value="white">White</option> <option value="black">Black</option> </select> <h1>This is my website</h1> const select = document.querySelector("select"); const html = document.querySelector("html"); document.body.style.padding = "10px"; function update(bgColor, textColor) { html.style.backgroundColor = bgColor; html.style.color = textColor; } select.addEventListener("change", () => select.value === "black" ? update("black", "white") : update("white", "black"), ); See the Pen js-making-decisions2 by Hsoub Academy (@HsoubAcademy) on CodePen. لدينا في الشيفرة السابقة العنصر <select> الذي يُستخدم في اختيار سمة (أحد اللونين اﻷبيض أو اﻷسود)، إضافة إلى عنصر عنوان من المستوى اﻷول <h1> لعرض عنوان صفحة الويب. كما لدينا الدالة ()update التي تأخذ لونين كمعاملين لها. تضبط الدالة لون خلفية الصفحة لتصبح اللون الأول ولون النص هو اللون الثاني. نلاحظ أخيرًا استخدام مترصّد اﻷحداث onChange الذي ينُفِّذ الدالة مع معاملين هما اللونين اﻷسود واﻷبيض. يبدأ البرنامج بالعبارة الشرطية select.value === 'black' وبعدها تُنفّذ الدالة ()update ومعاملاها اللونان اﻷسود واﻷبيض، إذا كانت النتيجة true وبالتالي تصبح الخلفية سوداء والنص أبيض. أما إن أعاد الشرط السابق القيمة false، تُنفَّذ الدالة ()update ومعاملاها اللونان الأبيض واﻷسود فتكون الخلفية بيضاء والنص أسود اللون. مثال عل إنشاء تقويم زمني بسيط سننشئ تطبيق تطبيق تقويم زمني بسيط يتيح للمستخدم الاختيار بين اﻷشهر المختلفة المستخدم ليعرض تقويم مناسب للشهر. إذا كان لدينا تصميم الصفحة التالية: <h2>Live output</h2> <iframe id="output" width="100%" height="600px"></iframe> <h2>Editable code</h2> <p class="a11y-label"> Press Esc to move focus away from the code area (Tab inserts a tab character). </p> <textarea id="code" class="playable-code" style="height: 400px;width: 95%"> const select = document.querySelector('select'); const list = document.querySelector('ul'); const h1 = document.querySelector('h1'); select.addEventListener('change', () => { const choice = select.value; // ADD CONDITIONAL HERE createCalendar(days, choice); }); function createCalendar(days, choice) { list.innerHTML = ''; h1.textContent = choice; for (let i = 1; i <= days; i++) { const listItem = document.createElement('li'); listItem.textContent = i; list.appendChild(listItem); } } createCalendar(31, 'January'); </textarea> <div class="playable-buttons"> <input id="reset" type="button" value="Reset" /> <input id="solution" type="button" value="Show solution" /> </div> لنضف بعض التنسيقات كما يلي: html { font-family: sans-serif; } h2 { font-size: 16px; } .a11y-label { margin: 0; text-align: right; font-size: 0.7rem; width: 98%; } body { margin: 10px; background: #f5f9fa; } هذا كود جافا سكريبت المطلوب لعمل التقويم الزمني بالشكل المناسب: const select = document.querySelector("select"); const list = document.querySelector("ul"); const h1 = document.querySelector("h1"); select.addEventListener("change", () => { const choice = select.value; let days = 31; if (choice === "February") { days = 28; } else if ( choice === "April" || choice === "June" || choice === "September" || choice === "November" ) { days = 30; } createCalendar(days, choice); }); function createCalendar(days, choice) { list.innerHTML = ""; h1.textContent = choice; for (let i = 1; i <= days; i++) { const listItem = document.createElement("li"); listItem.textContent = i; list.appendChild(listItem); } } createCalendar(31, "January"); كما تلاحظ في الكود أعلاه فقد استخدمنا معالج أحداث onchange يرصد تغيّر قيمة العنصر الذي يختاره المستخدم من العنصر <select>، وكتبنا عبارة شرطية ضمن دالة معالج الأحداث onchange مهمتها ما يلي: النظر إلى الشهر المختار المُخزَّن في المتغير choice وهو قيمة العنصر <select>. ضبط المتغيّر days كي يكون مساويًا لعدد أيام الشهر المختار. وهذه النتيجة التي ستحصل عليها: تطبيق عملي: خيارات لونية أكثر ستحوّل في هذا التمرين العامل الثلاثي الذي تعرفنا عليه سابقًا إلى عبارة switch كي نتمكن من تطبيق خيارات أكثر على موقع الويب البسيط الذي نعمل عليه. لاحظ أن العنصر <select> لا يضم خيارين فقط بل خمسة، لهذا لا بد من استخدام switch هذه المرة تحت التعليق ADD SWITCH STATEMENT//: يجب أن تقبل العبارة المتغير choice ليكون عبارة الدخل. يجب أن تُوازن قيمة المتغير choice في كل حالة مع أحد الخيارات <option> وهي white أو black أو purple أو yellow أو psychedelic. يجب تنفيذ الدالة ()update في كل حالة وذلك بتمرير قيمتين لونيتين لها تمثّل اﻷولى لون الخلفية والثانية لون النص، وتذكر أن هذه القيم نصيّة ولا بد من وضعها ضمن إشارتي تنصيص. إذا ارتكبت خطأ، يمكنك دائمًا إعادة تعيين المثال باستخدام زر "Reset" الموجود في نهاية الإطار التالي، وإذا واجهتك مشكلة في حل السؤال بنفسك، فاضغط على زر "Show solution" لرؤية الحل الصحيح. See the Pen js-making-decisions4 by Hsoub Academy (@HsoubAcademy) on CodePen. الخلاصة قدمنا في هذا المقال كل ما تحتاج معرفته حاليًا عن البنى الشرطية في جافا سكريبت، راجع المقال مرارًا لترسّخ المعلومات التي حصلت عليها في ذهنك أو بإمكانك طرح اﻷسئلة التي تشاء في نقاش صفحة المقال ضمن أكاديمية حسوب. ترجمة -وبتصرف- لمقال: Making decisions in your code-conditionals اقرأ أيضًا المقال السابق: المصفوفات في جافا سكريبت تعرّف على لغة جافا سكريبت من منظور عام تعلم لغة جافا سكريبت من الصفر حتى الاحتراف المعاملات المنطقية في جافاسكربت الجمل الشرطية if/else في جافاسكريبت
-
من أكثر الأخطاء كارثيةً على الإنتاجية هو عدم التطابق بين أهداف فريق العمل وتوقعات المديرين. وستجد دائمًا وجهين عندما يتعلق الأمر بتأسيس قناة فعالة للتواصل في مقر العمل: الأول هو رأي المدير أو توجيهاته إلى الموظفين، والآخر هو آراء الموظفين وردودهم الموجهة إلى المدير. إنّ هكذا قناة ثنائية الاتجاه قادرة على خلق العديد من الفرص التي تزيد من إنتاجية الفريق وتعزز اندماج الموظفين في العمل. لقد جمعنا في هذا المقال 12 مثالًا عن الطريقة التي يخاطب فيها الموظف مديريه حول أمر ما والتي قد تستفيد منها لاحقًا في تحديد وحل أي طارئ. ما الأهمية الحيوية للإفادات الموجهة إلى المديرين لا أحد يريد أن يسمع ردود أفعال سلبية مهما تكون بناءةً ودقيقة. لقد بينت الدراسات أن 37% من الموظفين لا يعدون أنفسهم مقربين من مديريهم، وأنّ 64% منهم يرغبون بردود فعل أفضل، لهذا لا بدّ من إيصال آراء الموظفين وردود أفعالهم إلى مديريهم لتعزيز نمو الشركة. سيشعر الموظف بقيمته إن كان لرأيه أهمية، وهذا سيقود بدوره إلى حافز أعلى للعمل والمشاركة، وبالتالي إلى إنتاجية أفضل، فقد لا يعلم الكثير من المديرين بوجود مشاكل، وقد لا يعرفون بها حتى تنفجر أمامهم إن أهملوا الاطلاع على آراء الموظفين ومتطلباتهم أو تجاهلوها. قد تؤثر ديناميكية السلطة بين الموظفين والمديرين على إيصال الآراء إلى المدير وجعلها أمرًا محفوفًا بالمخاطر، فقد يساهم المدير الأقل استعدادًا للاستماع مباشرةً في تلويث بيئة العمل مما يؤثر سلبًا على كل من فيها. تعطي القدرة على تبادل الآراء فهمًا أفضل للمشاكل المحتملة ولأداء الفريق بالكامل. عليك أن تبني إذًا ثقافة تبادل الآراء في شركتك كي يشعر كل شخص بالراحة عندما يحاول أن يقدم رأيًا. 12 مثال عن الردود البناءة للمديرين من السهل أن تقول ما تريد قوله لمديرك أمام المرآة، لكنه أمر غير مريح عندما ينتقل الأمر إلى أرض الواقع، ولهذا نظمنا فيما يأتي قائمةً بأمثلة عن التظلمات والطلبات والآراء وكيفية إيصالها إلى المدير. 1. الحاجة إلى التوجيه يلقي بعض المديرين العمل بأكمله على الفريق دون أن يتدخلوا بالتفاصيل وهذا أمر جيد بالنسبة للموظفين الطموحين، لكنه قد يسبب نقصًا في التوجيه وشعورًا بعدم الثقة. أحد الأمثلة لمقاربة هذه الحالة: "أعرف أنك كثير المشاغل، فهل يمكننا بطريقة ما وضع جدول كي تتأكد دوريًا أنّ عملي على المسار الصحيح في هذا المشروع؟ وبمجرد أن أكوِّن فكرةً أفضل عما تتوقعه من نتائج وعن مسار الإنجاز المبدئي للمشروع بأكلمه، أعتقد أني سأكون قادرًا على المتابعة بنفسي." يظهر هذا النوع من الطلبات استعدادك للعمل الاستباقي والمستقل لكن مع حاجة لبعض التواصل في بعض النقاط. 2. مدير شديد التدقيق يشعر الموظف على نقيض الحالة السابقة بأن المدير لا يثق بمهاراته أو خبراته أو أحكامه. وقد تشعر بعض الفرق بأن عليهم ترك العمل للمدير بأكمله طالما أنه سيتدخل في كل صغيرة وكبيرة أيًا ما فعلوا. وكنتيجة لذلك، ستنخفض الروح المعنوية للفريق ويقل اندماجهم في العمل. أحد الأمثلة لمقاربة هذه الحالة: "أقدر كثيرًا ملاحظاتك وإرشاداتك، لكن أعتقد أني سأتعلم وأنجز أفضل إن استطعت تصوّر الهدف وكيفية بلوغه بنفسي. وعلى الرغم من استمتاعي بمشاعر إنجاز المهام الموكلة إلي، فإني أقضي وقتًا أكبر في كتابة التقارير بدلًا من التركيز على مشروعي. هل بإمكاننا الانتقال إلى أسلوب التقرير الأسبوعي أو الشهري؟" 3. الحاجة إلى توضيح الوجهة قد يفترض المدير أحيانًا أنّ فريقه يفهم تمامًا ما يتوقعه من مشروع معين، فلا يزوّدهم بكل التفاصيل المطلوبة. يمكن لضبابية الوجهة أن تؤثر على أداء بعض أعضاء الفريق وتبطئ تنفيذ المشروع، فقد يرتكب الفريق أخطاءً من الممكن تفاديها لو أنّ الأهداف أكثر وضوحًا. أحد الأمثلة لمقاربة هذه الحالة: "أود أن أشكرك على الثقة التي منحتها للفريق كي يفكّر بحرية في طريقة إنجاز هذا المشروع وأهدافه، لكني لاحظت أثناء العمل عدم وضوح الأولوية في ترتيب الأهداف، فهل يمكنك تقديم بعض الإيضاحات عن الأولوية التي تريدها لأهداف المشروع رجاءً؟". 4. لا تحصل على التقدير الذي تستحقه من المدير لا شيء أسوأ من بذل قصارى جهدك في المشروع ثم لا تلقى أي تقدير لما فعلته من مديرك أو زملائك. عندما يشعر الموظف بقلة التقدير سيقلل اندماجه في المشروع إلى الحد الأدنى الذي يتفادى معه الطرد، في حين يشعر في المقابل من يتلقى مكافآت على ما يبذله من جهد بالاندفاع الشديد نحو العمل. فإن كنت تشعر بأنك لا تلقى التقدير الذي تستحقه من مديرك ولا ردود فعل إيجابية، فحاول أن تستفسر عن الأمر بالشكل التالي: "لقد قدمت الكثير من الإضاءات الرائعة على أداء فريقنا في المشروع السابق، لكنني لاحظت أنك أخرجت عددًا من أعضاء الفريق في مرحلة مبكرة. هل بإمكاننا أن نبدأ الاجتماع بإظهار التقدير للفريق بأكمله قبل الانتقال إلى النقاط التي ينبغي تحسينها؟ فقد يعزز ذلك الروح المعنوية ويزيد اهتمام وحماسة الجميع للمشروع القادم". 5. الشعور بالإرهاق لهذا الشعور أسباب كثيرة وأهمها هو كثافة العمل الملقى على عاتق موظف واحد. على المدير أن يتابع حجم العمل المخصص لكل عضو من أعضاء الفريق أسبوعيًا، لكن مشاغلهم قد تبعدهم عن هذا الموضوع. من المهم ألا تخجل من الإشارة إلى كم العمل الذي تنجزه إن كان مرهقًا. إحدى الطرق للإشارة إلى رغبتك في متنفس أوسع: "بعد تقييمي لمهامي الحالية خلال هذا الشهر، رأيت أني أقضي 25 ساعة أسبوعيًا على المشروع "أ" بالإضافة إلى 10 ساعات إضافية. ستؤثر أية مهام إضافية على عملي الحالي ولن أتمكن من تقديم الأداء المطلوب فيها. هل يمكنني مقابلتك لمناقشة كم العمل الذي يمكنني إنجازه كي أقدم الأداء الأمثل؟". 6. الموازنة بين العمل ومشاغل الحياة يعتقد بعض المديرين أن أولويات موظفيهم هي نفسها أولوياتهم، وهذا ما يقود إلى الصراع بين العمل ومشاغل الحياة. قد يكون إبداء رأيك في الأمر بنّاءً كالتالي: "أحترم اندفاعك لإنجاز العمل وأقدر طريقتك في تنفيذ الأمر، لكنها بدأت بالتأثير سلبًا على حياتي الشخصية. هل يمكننا ترتيب لقاء كي نناقش به طريقة أكثر فعالية في التواصل "وضمن أوقات الدوام" المحددة؟". 7. حل المشاكل داخل الفريق ستظهر الخلافات في مرحلة ما داخل الفريق. وعلى الرغم من أنّ مهارات القائد في حل المشاكل وقوة شخصيته كافيان لحل هذه النزاعات، لكن لا بد من معرفته بها أولًا: ابدأ الحديث كالتالي: "ظهرت بعض الخلافات مؤخرًا بين أعضاء الفريق وقد تؤثر على أدائنا وعملنا الجماعي. ما رأيك في ترتيب آلية تراها مناسبة وبإشرافك لتدارك الأمر، كي نستفيد من إمكانياتك في إرشادنا إلى أفضل طريقة للتصرف؟". 8. تفضيل موظف على آخر قد يؤثر ذلك على الروح المعنوية ويقلل من رغبة الموظفين في المساهمة في العمل، كما قد ينمي بيئة عمل عدائية. يمكن أن تنقل هذه المشكلة إلى الموارد البشرية في شركتك، لكن إن كنت واثقًا من قوة علاقتك بمديرك، فيمكنك أن تبدأ كالتالي: "أتطلع باستمرار إلى تحسين مهاراتي في العمل مع عميل جديد، فهل يمكنك أن توضح لي سبب عدم اختياري؟". 9. كلمات التشجيع حال المديرين كالموظفين، فهم يرغبون بالتشجيع والتقدير ويحبون تلقي الردود الإيجابية على كافة مستوياتهم. وعندما يطوّر المديرون العلاقة مع موظفيهم سيزيد احتمال تلقيهم ردودًا إيجابية. بإمكانك توجيه الشكر إلى مديرك سرًا أو علنًا بأسلوب مشابه للتالي: "شكرًا جزيلًا على التقدير الذي أبديته لفريقنا عند إنجازه المشروع الأخير. لم نكن لننجز ما أنجزناه دون إرشادك وتوجيهك. ونتطلع قدمًا للعمل على تحدٍ جديد ورفع أدائنا إلى مستوىً أعلى". 10. إبداء آراء بناءة في الوقت المناسب لا يشعر موظف من بين أربعة تقريبًا بأنه يتلقى آراءً كافية على عمله تساعده في التقدم. وقد تكثر مشاغل المديرين فيغفلون هذه الناحية. إن لم تستطع لقاء مديرك سوى أثناء جلسات تقييم الأداء، فيمكنك أن تطرح الموضوع على الشكل التالي: "أعلم أن مشاغلك كثيرة، لكن إن استطعنا عقد جلسات أكثر لتبادل الآراء، فسيعزز ذلك الروح المعنوية للفريق. وبهذا الشكل نضبط أداءنا بدقة أكبر ونزيد من إنتاجيتنا الحالية". 11. الحصول على أفكار للنمو والتطور إن شعرت بأنك مقيّد في مهمة ما لا تجد فيها أبدًا فرصة للتطور، فقد حان الوقت لترتيب لقاء مع مديرك. إن كنت تبذل جهدك في عمل ما ولا ترى النتائج المتوقعة، ففكر في مخاطبة المدير كالتالي: "أحب عملي الحالي كثيرًا، لكني أرغب في الحصول على فرص أخرى لتنمو قدراتي وتتطور. هل يمكنني مقابلتك لنناقش إمكاناتي وأسمع رأيك عما يمكنني فعله لأترقى أكثر في الشركة؟". 12. مخاوف متعلقة بإدارة الوقت سيؤثر حجم المهام التي تُلقى على عاتقك سواءً كثيرة جدًا أو قليلةً جدًا على جودة عملك، وسيؤثر ذلك بدوره على الروح المعنوية لديك. ابدأ الحديث كالتالي: " بدأت أعاني مؤخرًا في عملي لأنني غير قادر على تخصيص الوقت الكافي لكل مهمة كما يجب. هل يمكنني لقاؤك لتحديد أولوية المهام الموكلة إلي وتوزيع المهام بالتساوي على كامل أعضاء الفريق؟". أفضل الممارسات في إبداء الآراء للمديرين لإبداء الآراء في غير مكانها انعكاسات سلبية، إذ لا بد من تحضير النقاط التي ستطرحها في محادثتك لأن أهميتها توازي أهمية رد المدير على موظفيه. إليك بعض الطرق التي تضمن إيصال رأيك إلى المدير بفعالية والحصول على النتائج الإيجابية المتوقعة: اسأل عن رغبة المدير في الاطلاع على رأيك يؤمن العديد من المدراء بفكرة الإدارة وحيدة الاتجاه من الأعلى إلى الأدنى، ولا يرغبون بالاستماع إلى الآراء أو تبادلها، خاصةً فيما يتعلق بقرارات الإدارة أو أساليبها. في هذه الحالة توجه إلى قسم الموارد البشرية كي لا تؤثر على تطور مهنتك ونمو قدراتك. قد تكون المسألة برمتها مسألة وقت فقط نظرًا لانشغال المديرين، لذا حاول في هذه الحالة اختيار الوقت المناسب لطرح الموضوع. اسألهم قبل البدء إن كان الوقت مناسبًا لطرح موضوعك، أو إن كان بالإمكان تحديد وقت لاحق للحديث. ابدأ بالوقائع ينبغي أن تكون طروحات الموظفين حياديةً وتركّز على أمثلة محددة. لن ترغب في الوصول إلى موضوع حساس أو قاسي وأنت تحاول أن تقدم رأيًا صريحًا، لأن هذا يمنع مديرك من تقبل ما تقول؛ لذا حاول أن توضح دائمًا أنّ ما تقوله مبني على تجاربك الشخصية وأنك لست مطلعًا على صورة الموقف بأكملها. جد الهدف المشترك تأكد من أنك في صف مديرك وركز على الأشياء المهمة بالنسبة إليه. استفد من حوارك معه في تحديد أهداف واستراتيجيات مشتركة وعزز الحوار بالتركيز على الاهتمامات المشتركة. اجعل ردود الفعل الإيجابية إطارًا لتعزيز الروح المعنوية تتلخص أفضل طريقة في تقديم نقد بناء ضمن حديثك بالخطوات التالية: ابدأ بمديح السلوك الإيجابي. ادخل بعد ذلك في صلب الموضوع. أنهِ بعبارة إيجابية أيضًا. تقبل الرفض قد يعارض مديرك ما تقوله، خاصةً إن قدمت رأيًا سلبيًا. لهذا تعلم أن تتراجع بهدوء وأن تتفهم أنك لا ترى الموضوع من نفس المنظور الذي يراه مديرك. اعرض رأيك على شكل أسئلة ستُظهر بهذه الطريقة تفهمك لمشاعر المدير بوضع نفسك مكانه، وستجبره صياغة طلبك على شكل أسئلة أن يرد بأجوبة قد تنطوي على الحلول المطلوبة. هل ينبغي التوجه إلى المدير بآراء صريحة حتى لو كانت سلبية؟ في عالم مثالي، قد يكون الجواب نعم. لتبادل الآراء بين الطرفين منافع كثيرة وأهمية كبرى لكلا الطرفين على حد سواء، مع ذلك لن يقدّر بعض المديرين الآراء غير المرغوب بها أو التي تنطوي على نقد سلبي لأدائهم. في هذه الحالة اتبع حدسك دائمًا، واسأل مديرك إن كان يرغب في سماع ما ستقوله أو أنّ الوقت مناسب لمثل هذا الحديث، خاصةً إن كان أساس حديثك إظهار السلبيات في الإدارة. ترجمة -وبتصرف- للمقال 12 Manager feedback examples for employees لصاحبته Rachel Steben. اقرأ أيضًا فوائد استطلاع آراء الموظفين أهمية تبادل الآراء مع الموظفين ما يتوقعه الموظفون من المديرين وفقا لآراء الموظفين الدليل الشامل للمديرين حول كيفية إعطاء الملاحظات للموظفين هل يشعر الموظفون بالراحة عند إعطاء آرائهم؟
-
يشرح هذا المقال الانسياب الاعتيادي للعناصر أو الطريقة التي تُرتّب فيها تلك العناصر ضمن صفحة الويب أي المواضع التي ستأخذها بشكل تلقائي إن لم يتغير تخطيطها. عليك قبل البدء في قراءة هذا المقال أن تكون على اطلاع بالمفاهيم التالية: أساسيات HTML. أساسيات عمل CSS. تُرتب العناصر في صفحة الويب كما شرحنا في المقال السابق مدخل إلى تخطيط صفحات ويب ضمن تخطيط تلقائي يُعرف بالانسياب الاعتيادي Normal Flow إن لم تُطبق أي تنسيقات CSS لتغيير هذا السلوك. وكما رأينا، يمكننا تغيير سلوك العناصر بتغيير موقعها في الانسياب الاعتيادي أو إزالتها من هذا الانسياب كليًا. وبالطبع فإن البدء بإنشاء مستند HTML متماسك ومهيكل بطريقة متينة وله انسياب اعتيادي واضح هي الطريقة الأنسب لتبدأ العمل على صفحة ويب. ستضمن بهذه الطريقة أن الصفحة مقروءة وواضحة بالنسبة لأي متصفح أو جهاز محدود الإمكانيات مثل قارئات الشاشة التي تقرأ للزائر محتويات الصفحة. إضافة إلى ذلك، فإن العمل مع تصميم الانسياب الاعتيادي الموجّة لإنشاء مستندات سهلة القراءة أفضل بكثير من العمل ضده عندما تبدأ بتعديل التخطيط بالشكل الذي تريده. لهذا السبب سنستعرض سريعًا في مقال اليوم بعض الأشياء التي مرّت معنا سابقًا والمتعلقة بالانسياب الاعتيادي قبل أن نتعمق في تفاصيل التخطيطات الأخرى. كيف تُرتب العناصر افتراضيًا في صفحة ويب؟ تبدأ عملية ترتيب العناصر التي تضيفها لصفحة الويب بشكل صناديق، وهذا ما يُدعى نموذج الصندوق Box model وهو نموذج يستخدم لتحديد وتصميم ترتيب وتخطيط العناصر في صفحة الويب. ويتكون النموذج من مربع يحيط بكل عنصر في الصفحة بحيث نحدد لكل مربع أربعة مكونات أساسية هي: الهامش Margin، والحاشية Padding، والإطار Border، والمحتوى Content كما تلاحظ في الصورة التوضيحية التالية: ولفهم المزيد عن ترتيب عناصر صفحة الويب عليك أن تفهم بشكل جيد ما هي المساحة التي سيشغلها كل عنصر تضيفه على الصفحة، فهناك نوعان مختلفان من عناصر HTML هما العناصر السطرية Inline-level والعناصر الكتلية Block-level، ويشغل كل نوع مساحة محددة من صفحة الويب، ويكمن الفرق الأساسي بين العناصر الكتلية والعناصر السطرية بأن العناصر الكتلية تعرض على أسطر منفصلة وتأخذ مساحة كاملة أفقيًا ورأسيًا، بينما تعرض العناصر السطرية على نفس السطر مع العناصر الأخرى وتأخذ مساحة تبعًا لحجم محتواها. حيث يملأ محتوى العناصر الكتلية block-level element افتراضيًا الفراغ الموافق لكامل السطر في العنصر الأب، وتنمو أو تتوسع هذه العناصر على طول البعد الرأسي أو ارتفاع الكتلة لاستيعاب محتواها، أما أبعاد العناصر السطرية inline-level elements فتتطابق مع أبعاد المحتوى. بالإمكان ضبط ارتفاع height أو اتساع width العناصر التي تأخذ فيها الخاصية display القيمة inline افتراضيًا مثل <img>، لكن تبقى قيمة display كما هي. وإن أردت التحكم بقيم الخاصية display للعناصر السطرية بهذه الطريقة، يمكن أن تستخدم CSS لجعلها تسلك سلوك العناصر الكتلية (مثل ;display: block أو ;display: inline-block التي تمزج ميزات كلا النوعين). ويفسر ذلك كيفية هيكلة العناصر كلًا على حدى، لكن ماذا عن هيكلتها عندما تكون معًا؟ هنا يظهر الانسياب الاعتيادي الذي أشرنا إليه سابقًا الذي يوضّع العناصر ضمن نافذة العرض الخاصة بالمتصفح. إذ ترتب العناصر الكتلية باتجاه الكتلة الذي يرتبط بدوره بنمط الكتابة (عادة أفقي horizontal-tb). يظهر كل عنصر في سطر مستقل تحت العنصر الذي يسبقه وتفصل بينهما الهوامش المخصصة لكل منها. ففي اللغة العربية أو الإنكليزية (أو أية لغة نمط الكتابة فيها أفقي)، سترتب العناصر الكتلية عموديًا. من ناحية أخرى، تسلك العناصر السطرية inline elements سلوكًا مختلفًا، فلا تظهر في أسطر مستقلة، بل تشغل السطر نفسه مع أي محتوى نصي مجاور أو ضمن عنصر حاوٍ طالما أن هناك فراغًا كافيًا ضمن المساحة الذي يشغلها العنصر الكتلي الأب. وإن لم يكن هناك فراغًا كافيًا فستنتقل العناصر التي تطفح عن السطر إلى سطر جديد. إن كان لأي عنصرين متجاورين عموديًا قيمة معينة للهامش وتلامس هامشيهما، سيبقى الهامش الأكبر فاصلًا بينهما ويختفي الهامش الأصغر، وهذا ما يُعرف بانكماش الهوامش margin collapsing، ويحدث فقط في الاتجاه العمودي. لنلق نظرة على المثال البسيط التالي الذي يشرح كل ما ذكرناه: <h1>Basic document flow</h1> <p> I am a basic block level element. My adjacent block level elements sit on new lines below me. </p> <p> By default we span 100% of the width of our parent element, and we are as tall as our child content. Our total width and height is our content + padding + border width/height. </p> <p> We are separated by our margins. Because of margin collapsing, we are separated by the width of one of our margins, not both. </p> <p> Inline elements <span>like this one</span> and <span>this one</span> sit on the same line along with adjacent text nodes, if there is space on the same line. Overflowing inline elements will <span>wrap onto a new line if possible (like this one containing text)</span>, or just go on to a new line if not, much like this image will do: <img src="long.jpg" alt="snippet of cloth" /> </p> body { width: 500px; margin: 0 auto; } p { background: rgba(255, 84, 104, 0.3); border: 2px solid rgb(255, 84, 104); padding: 10px; margin: 10px; } span { background: white; border: 1px solid black; } يوضح الكود السابق الطريقة الأساسية التي تتدفق بها العناصر والمحتوى في صفحة HTML تتضمن عنصر عنوان رئيسي h1، وأربع فقرات نصية p . لاحظ تأثير تطبيق نموذج الصندوق لتوضع هذه العناصر والقيم التي منحناها لتعديل الهامش والحدود والحشو للفقرات الأربعة p باستخدام تنسيقات CSS، سيكون الخرج على النحو التالي: See the Pen normal-flow-css by Hsoub Academy (@HsoubAcademy) on CodePen. الخلاصة تعلمنا في هذا المقال أساسيات الانسياب الاعتيادي أو الطبيعي Normal Flow للعناصر على صفحة الويب، وهو تخطيط CSS الافتراضي لترتيب العناصر. وبفهم السلوك الافتراضي للعناصر الكتلية والسطرية والهوامش، سيكون من السهل تعديلها في المستقبل، وسنعرفك في مقالات لاحقة على طرق وتقنيات أخرى لتنظيم العناصر على صفحات الويب مثل طريقة العناصر العائمة Floats والتخطيط الشبكي Grids والصندوق المرن Flexbox التي توفر لك أساليب مختلفة تساعدك على تحقيق تخطيطات منوعة لعناصر صفحات الويب واختيار ما يناسب احتياجات تصميم واجهات موقعك من بينها. ترجمة -وبتصرف- للمقال: Normal flow اقرأ أيضًا المقال السابق: مدخل إلى تخطيط صفحات الويب باستخدام CSS التحكم في تخطيط الصفحة وضبط محاذاة العناصر في CSS بعض العناصر والمفاهيم المهمة في لغة HTML عرض محتوى صفحات الويب بتجاوب على الأجهزة المتعددة تعرف على أساسيات لغة CSS
-
يُعد جودو محرّك ألعاب غني بالميزات، وهنالك الكثير لتتعلمه. لهذا سنشرح في هذا المقال كيفية استخدام دليل العمل على اﻹنترنت، والمراجع إلى الشيفرة والانضمام إلى مجتمع جودو لتعلم ميزات وتقنيات جديدة. الاستفادة القصوى من هذا المقال ما سنوضحه في هذا المقال هو طريقة التعامل دليل الاستخدام "user manual" الخاص بمحرك ألعاب جودو والذي يوثّق جميع مفاهيم محرّك الألعاب وميزاته المتاحة. فعندما تتعلم موضوعًا جديدًا، يمكنك البدء في تصفح القسم المخصص لهذا الموضوع في دليل المستخدم هذا. إذ تتيح لك القائمة اليمينية استكشاف مواضيع عامة عن محرك الألعاب، بينما يساعدك شريط البحث على إيجاد صفحات محددة. فإن وجدت صفحة خاصة بالموضوع ستكون مرتبطة غالبًا بصفحات أخرى متعلقة به يترافق هذا الدليل مع مراجع للأصناف "class reference" تشرح لك كل صنف أو دالة أو خاصية متاحة في جودو. فبينما يتحدث الدليل عن الميزات العامة والمفاهيم وكيفية استخدام المحرر، يتحدث مرجع اﻷصناف عن طريقة استخدام الواجهة البرمجية المستخدمة في كتابة سكربتات جودو Godot. وبإمكانك الوصول إلى معلومات هذا المرجع عبر اﻹنترنت أو محليًا. فكي تتمكن من تصفحها محليًا عبر محرر جودو كل ما عليك هو بالانتقال إلى "مساعدة Help" > "البحث في المساعدة Search Help" أو بالضغط على المفتاح F1: أما إن أردت البحث عن طريق اﻹنترنت انتقل إلى قسم مرجع اﻷصناف، وستدلك صفحة المرجع على ما يلي: أين هو موقع الصنف في التسلسل الهرمي، إذ يمكنك النقر على الروابط العليا للانتقال إلى الأصناف اﻵباء والاطلاع على الخاصيات والتوابع الموروثة. ملخص عن وظيفة الصنف وتوضيح حالات استخدامه. شرح خاصيات الصنف وتوابعه و إشاراته و معداته وثوابته. الربط مع صفحات الدليل التي تشرح الصنف أكثر. ملاحظة: إن كان دليل المستخدم أو مرجع الأصناف مفقودًا أو لم يحو على معلومات كافية، يمكنك فتح طلب من مستودع توثيق جودو على جيت هب للإبلاغ عن اﻷمر. بإمكانك النقر مع الضغط على المفتاح Ctrl على أية رابط نصي يمثل اسم صنف أو خاصية أو تابع أو إشارة أو ثابت للانتقال إليه. تعلم التفكير مثل المبرمجين إن موضوع تعلم أساسيات البرمجة وطريقة تفكير مطوري اﻷلعاب خارج سياق توثيق جودو لكنه أمر أساسي لك إن اردت أن تصبح مطور ألعاب . لذا إن كنت جديدًا في عالم البرمجة وكنت مهتمًا بتخصص برمجة وصناعة الألعاب الإلكترونية فننصحك بأن تُلمَّ قبل ذلك بأياسيات البرمجة قبل البدء، ويمكنك الاعتماد على أحد المصدرين التاليين: أكاديمية حسوب التي تقدم لك كمًا كبيرًا من المقالات المتخصصة بتعلم مختلف لغات البرمجة إضافة إلى دورات تدريبية عملية تبدأ بك من الصفر وحتى الاحتراف صممها وقدمها مبرمجون محترفون على دراية كاملة بمتطلباتك وتطلعاتك. موسوعة حسوب التي تعد أكبر مرجع باللغة العربية لتوثيق أشهر لغات البرمجة ومن بينها Python و JavaScript والعديد من اللغات والتقنيات الأخرى. التعلّم من خلال مجتمع جودو يتميز محرّك جودو بمجتمع يتطور ويزداد تعداده باستمرار. فإن واجهتك مشكلة ما وأردت المساعدة في فهم طريقة عمل شيء ما، بإمكانك سؤال مستخدمين آخرين ينتمون إلى أي من مجتمعات جودو النشطة. وأفضل مكان لطرح اﻷسئلة وإيجاد حلول لاسئلة طرحت مسبقًا هو الموقع الرسمي للأسئلة واﻷجوبة ask.godotengine . إذ تظهر الإجابات في نتائج محرّك البحث وتُخزّن ليستفيد اﻵخرون من النقاشات التي تدور في المنصة. وبمجرد أن تطرح سؤالًا، بإمكانك أن تستخدم رابطه لمشاركته في منصات أخرى. لكن قبل أن تطرح سؤالًا، تأكد من وجود أجوبة مسبقة عنه في هذه المنصة أو ابحث عنها باستخدام محرّك البحث الذي تفضلّه. الطريقة الصحيحة لطرح الأسئلة حول محرك جودو إن طرحك للسؤال بشكل جيد وتقديم تفاصيل دقيقة يساعد اﻵخرين في اﻹجابة عن سؤالك بسرعة. وننصحك عند طرح السؤال أن يتضمن المعلومات التالية: وصف الغاية من السؤال: يجب أن تشرح ما الذي تحاول فعله من وجهة نظر تصميمية. فإن لم تتمكن من تصور طريقة لتطبيق الحل، قد تكون هناك حلول أخرى أبسط لتحقيق نفس الغاية. مشاركة رسالة الخطأ كما هي تمامًا: إن كان هناك خطأ. انسخ رسالة الخطأ بدقة من المنقح بالنقر على أيقونة "انسخ الخطأ Copy Error". فمعرفة رسالة الخطأ تساعد أعضاء المجتمع في تحديد السبب الذي أدى لوقوع المشكلة. مشاركة عينة من الشيفرة: إن كان الخطأ في الشيفرة. فلن يتمكن اﻵخرون من مساعدتك في حل المشكلة دون رؤية الشيفرة. لذا شارك الشيفرة على شكل نص مباشرة بنسخ ولصق جزء من الشيفرة في رسالتك أو استخدام مواقع مثل Pastebin لمشاركة الملفات الطويلة. مشاركة لقطة شاشة: لحاوية المشهد في المحرر إضافة إلى شيفرتك. فالشيفرة التي تكتبها تؤثر على عقد عقد المشاهد. إذًا عليك التفكير بالمشاهد كجزء من شيفرتك المصدرية. كذلك لا تستخدم هاتف محمول لالتقاط الصور، فالدقة المنخفضة والانعكاسات قد تُصعّب فهم الصورة. لهذا استخدم الأداة التي يوفّرها نظام التشغيل لديك لالتقاط صورة للشاشة (مثل الزر Print Screen). كما يمكنك استخدام ShareX في ويندوز مثلًا أو FlameShot في لينكس. مشاركة فيديو للعبتك وهي تعمل: أمر مفيد جدّا. يمكنك استخدام برامج مثل OBS Studio أو Screen to GIF لالتقاط فيديو لسطح مكتبك، ثم استخدام خدمات مثل streamable أو مزوّد سحابي لرفع ومشاركة الفيديو. اﻹشارة إلى نسخة جودو التي تستخدمها: وخاصة إن لم تكن النسخة مستقرة stable version. لأن الجواب قد يختلف نتيجة لتغير الميزات والواجهة باستمرار. باتباعك اﻹرشادات السابقة ستزيد فرص حصولك على الجواب الدقيق الذي تبحث عنه، وسيوفر وقتك ووقت اﻷشخاص الذين يساعدونك في حل مشكلتك. مصادر تعليمية حول محرك الألعاب جودو إن كنت تبحث عن دورات حول إنشاء نوع لعبة إلكترونية مثل خطوات إنشاء لعبة تقمص الأدوار "Role-Playing Games" أو غيرها من أنواع الألعاب الإلكترونية، ننصحك بالاطلاع على قسم المصادر والدورات التعليمية Tutorials and resources الذي يعرض محتوى متخصصًا يقدمه مجتمع جودو. وإذا كنت مهتمًا بمعرفة المزيد من التفاصيل عن محرك ألعاب جودو Godot أو لغة برمجة الألعاب GDScript وطريقة تطوير الألعاب ثنائية الأبعاد وثنائية الأبعاد باللغة العربية، فننصحك بالاطلاع على سلسلة المقالات المنشورة في أكاديمية حسوب تحت وسم godot، كما يمكنك الاطلاع على العديد من المقالات المفيدة في قسم مقالات صناعة الألعاب الذي ينشر بصورة دورية العديد من المقالات التي تفيدك كمطور ألعاب. ويمكنك كذلك طرح أي سؤال أو مشكلة تعترضك خلال برمجة لعبتك الخاصة في قسم الأسئلة والأجوبة في الأكاديمية أو في مجتمع حسوب IO. الخلاصة تعرفنا في مقال اليوم على طريقة الاستفادة من الميزات الجديدة التي يوفرها لك محرك الألعاب جودو Godot وأهمية التفكير البرمجي لك كمطور ألعاب وكيفية طرح سؤال حول أي مشكلة تقنية تصادفك خلال صناعة لعبتك بطريقة صحيحة وحلها بسرعة وكفاءة، وأخيرًا ختمنا المقال بمصادر تعليمية مفيدة حول Godot وتطوير الألعاب. ترجمة -وبتصرف- لمقال: Learning new features اقرأ أيضًا دليلك الشامل إلى بناء كاميرا خاصة بشاشات اللمس في محرّك اﻷلعاب جودو تعرف على محرر محرك اﻷلعاب جودو Godot إعداد محرك الألعاب جودو Godot للعمل مع قاعدة البيانات SQLite تشغيل محرك الألعاب جودو على بعض أنواع العتاد غير المدعوم تعرف على أشهر لغات برمجة الألعاب
-
سنلقي نظرة في هذا المقال على جافا سكريبت JavaScript من منظور عام ونجيب على أسئلة مثل "ما هي جافا سكريبت؟" و "ماذا تفعل هذه اللغة؟" لنتأكد أنك تملك الفهم الجيد لهذه اللغة بالعموم قبل الغوص في التفاصيل الأكثر تعقيدًا. لن تحتاج أية معرفة مسبقة بلغة جافا سكربت لتتابع معنا، لكن عليك قبل البدء بقراءة هذا المقال أن تمتلك بعض المعرفة بالأمور التالية: معرفة ببعض أساسيات HTML و CSS، لهذا ننصحك بالاطلاع على بعض المقالات السابقة مثل: أساسيات HTML. أساسيات عمل CSS. عالم الويب ومعاييره. ملاحظة: إن كنت تعمل على حاسوب أو جهاز لوحي أو أجهزة أخرى لا تسمح لك بإنشاء ملفات خاصة بك، يمكن تجريب معظم الأمثلة والشيفرات ضمن محرر برمجي عبر الإنترنت مثل JSBin أو Glitch. ما هي لغة جافا سكريبت لغة جافا سكريبت هي لغة برمجة تسمح لك بتنفيذ ميزات عديدة في صفحات الويب. ترى ذلك في الصفحات التي لا تكتفي بعرض معلومات ثابتة أو تلك التي تحدّث محتواها تلقائيًا مع الوقت أو تعرض خرائط تفاعلية أو رسوم ثنائية وثلاثية البعد. تُعد جافا سكريبت الطبقة الثالثة من طبقات الكعكة التي تمثّل تقنيات الويب المعيارية أما الطبقتان الباقيتين فهما HTML و CSS، وقد غطينا المفاهيم المتعلقة بهما في مقالات سابقة من سلسلة تعلم تطوير الويب. HTML: لغة توصيف تُستخدم لهيكلة صفحات الويب وإعطاء معنى لمحتواها. فهي تعرّف مثلًا المقاطع النصية والعناوين وجداول البيانات والصور والفيديو. CSS: هي لغة تنسيق تُطبق قواعد تنسيق محددة على محتوى HTML مثل ضبط ألوان الخلفية وخطوط الكتابة وترتيب المحتوى ضمن عدة أعمدة جافا سكريبت: لغة برمجة تمكّنك من إنشاء محتوى يُحدّّث ديناميكيًا على صفحات الويب، والتحكم بالوسائط المتعددة وتحريك الصور وكل ما يجعل صفحة الويب تفاعلية (ليس كل شيء تمامًا، لكنها ستذهلك بما يمكن أن تفعله شيفرة مكوّنة من أسطر قليلة) تُبنى هذه الطبقات فوق بعضها بأناقة، ولنأخذ عنوان نصي بسيط كمثال. سنوصّف ذلك من خلال لغة HTML التي تعطيه الشكل وتصف الغاية منه: <p>Player 1: Chris</p> يمكننا بعد ذلك إضافة بعض تنسيقات CSS لكي نحسّن المظهر: p { font-family: "helvetica neue", helvetica, sans-serif; letter-spacing: 1px; text-transform: uppercase; text-align: center; border: 2px solid rgb(0 0 200 / 0.6); background: rgb(0 0 200 / 0.6); color: rgb(255 255 255 / 1); box-shadow: 1px 1px 2px rgb(0 0 200 / 0.4); border-radius: 10px; padding: 3px 10px; display: inline-block; cursor: pointer; } وأخيرًا يمكننا إعطاء ديناميكية للصفحة بإضافة شيفرة جافا سكريبت بسيطة على النحو التالي: const para = document.querySelector("p"); para.addEventListener("click", updateName); function updateName() { const name = prompt("Enter a new name"); para.textContent = `Player 1: ${name}`; } See the Pen javascript1 by Hsoub Academy (@HsoubAcademy) on CodePen. جرّب أن تنقر على العنوان النصي بنسخته الأخيرة وراقب ما سيحدث. ما تفعله جافا سكريبت حقيقة أكثر من ذلك بكثير، سنستعرض ذلك بتفاصيل أوفى في الفقرات التالية. ما الذي تستطيعه جافا سكريبت حقيقةً؟ تتكون لغة جافا سكريبت التي تعمل من ناحية العميل من بعض الميزات البرمجية البنيوية التي تسمح لك بتنفيذ الكثير من الأشياء مثل: تخزين قيم مهمة ضمن المتغيرات. فما فعلناه في المثال السابق مثلًا أننا طلبنا إدخال اسم جديد ثم خزّنا القيمة المُدخلة في متغيّر سميناه name. العمل على جزء من نص وهو ما يعرف برمجيًا بالسلسلة النصية String فإذ أخذنا في المثال السابق السلسلة النصية " :Player1" وأضفنا إليها قيمة المتغيّر name يمكننا إنشاء عنوان نصي كامل مثل "Player 1: Chris". تنفيذ شيفرة برمجية استجابةً لحدث معين يقع في صفحة الويب، فقد استخدمنا في المثال السابق الحدث click لالتقاط عملية النقر على العنوان النصي ومن ثم نفذنا الشيفرة التي تُحدّث العنوان بعد إضافة الاسم عليه. وهناك الكثير من الأشياء التي تقوم بها لغة جافا سكريبت أيضًا! ولعل أكثر الأمور إثارة، هي القدرة الوظيفية الكبيرة لهذه اللغة من طرف العميل والتي تُعرف بواجهة التطبيق البرمجية Application programming Interface واختصارًا API والتي تعطي قوة كبيرة في استخدام الشيفرة. فواجهات API هي مجموعة معدة سلفًا من الشيفرة التي تسمح للمطور بتنفيذ برامج من الصعب أو المستحيل إنجازها. وما تقدمه هذه الواجهات للشيفرة يشابه ما يمنحه الأثاث والتجهيزات المنزلية للمنزل. فمن السهل أن تأخذ قطع خشبية مقصوصة وجاهزة ثم تجمّعها بالبراغي لتحصل على رف كتب مثلًا بدلًا من أن تصمم كل شيء بنفسك، إذ عليك حينها أن تجد نوع الخشب المناسب ثم تقطع الأخشاب وفق القياس الصحيح والشكل المطلوب وأن تجد البراغي المناسبة ومن ثم تجميعها لتحصل على رف الكتب المطلوب. وتصنّف واجهات API عمومًا ضمن فئتين أساسيتين: واجهات API الخاصة بالمتصفح واجهات API يقدمها طرف آخر Third party API وسنشرح تاليًا آلية عمل كل منهما بمزيد من التفصيل. واجهات API الخاصة بالمتصفح وهي برمجيات مدمجة في بنية المتصفح، قادرة على التعامل مع البيانات في البيئة الحاسوبية أو أن تقوم ببعض العمليات المعقدة المفيدة مثل: واجهة DOM (أو DOM API) وتسمح بالتعامل مع ملفات HTML و CSS كإنشاء وحذف وتغيير شيفرات HTML ديناميكيًا وتطبيق تنسيقات جديدة، فكل مرة ترى فيها نافذة منبثقة تُعرض على المتصفح أو ظهور محتوى جديد في الصفحة هي من فعل واجهة DOM. واجهة الموقع الجغرافي (Geolocation API) تستخلص برمجيات الواجهة معلومات جغرافية عن موقعك، لهذا يستطيع تطبيق خرائط جوجل العثور على موقعك وإظهاره على الخريطة. واجهتا canvas و webGL: اللتان تساعدانك على إنشاء رسومات متحركة ثنائية وثلاثية الأبعاد، وتستطيع تنفيذ أشياء رائعة باستخدامهما. واجهات الفيديو والصوتيات مثل HTMLMediaElement و WebRTC: التي تسمح لك بتنفيذ أشياء مميزة مع الوسائط المتعددة مثل تشغيل مقاطع الصوت والفيديو في صفحة الويب مباشرة أو التقاط بث كاميرا الويب وعرضها على حاسوب آخر. ملاحظة: يُفضّل عند تجريب استخدام الواجهات البرمجية استخدام متصفحات حديثة مثل فايرفوكس أو كروم أو إيدج أو أوبيرا. كما عليك الأخذ بعين الاعتبار فكرة اختبار الشيفرة على اكثر من متصفح وخاصة عند الوصول إلى مرحلة تسليم شيفرة الإنتاج بعد إنهاء الشيفرة التجريبية. واجهات API يقدمها طرف ثالث لا تُبنى هذه الواجهات ضمن المتصفح افتراضيًا، وعادة ما تحصل على شيفرتها من الويب. ومن الأمثلة عليها واجهة تويتر Twitter API: وتسمح لك بعرض آخر تغريداتك ضمن موقع الويب الخاص بك مثلًا وغيرها من الأشياء. واجهة خرائط جوجل وواجهة OpenStreetMap API وتسمحان لك بإدراج خرائط ضمن موقعك الإلكتروني وغيرها من الوظائف. ملاحظة: إن الواجهات البرمجية موضوع متقدم، ولن نغطي أيًا منها في حديثنا عن جافا سكريبت، لكن هناك الكثير أيضًا لتتعلمه حول لغة جافا سكريبت، فلا تأخذك الحماسة المفرطة. فلن تكون قادرًا على بناء فيسبوك أو خرائط جوجل أو إنستغرام بعد دراستك جافا سكريبت مدة 24 ساعة! فهنالك الكثير من الأساسيات التي عليك معرفتها أولًا، وهذا ما ستوفره لك سلسلة المقالات التالية حول جافا سكريبت. ما الذي تفعله جافا سكريبت في صفحة الويب سنلقي هنا نظرة على بعض الشيفرة، ونستعرض ما يحدث حقيقةً عندما تنفّذ شيفرة جافا سكريبت في صفحتك. لنستذكر سريعًا ما يحدث عند تحميل صفحة ويب في متصفح . عندما تحمّل صفحة ويب في متصفحك فإنك تنفّذ شيفرتك (HTML و CSS وجافا سكريبت) ضمن بيئة تنفيذية (نافذة المتصفح)، وهذا أمر مماثل لمعمل يستقبل موادًا أولية (الشيفرة) ويخرج منتجًا (صفحة الويب). إن أكثر الاستخدامات شيوعًا لجافا سكريبت هو تعديل شيفرة HTML و CSS ديناميكيًا لتغيير الواجهة من خلال الواجهة البرمجية DOM API. ولاحظ أن شيفرة صفحات الويب تُحمّل عمومًا وتنفذ وفق ترتيب ظهورها في الصفحة. وقد تحدث أخطاء إن حُمّلت شيفرة جافا سكريبت ونُفِّذت قبل شيفرة HTML و CSS التي ستُعدّل. سنتعلم لاحقًا في مقالنا كيف نلتف على الموضوع. أمان المتصفح لكل نافذة من نوافذ المتصفح جيب خاص لتنفيذ الشيفرة ويُعرف هذا الجيب ببيئة التنفيذ. ويعني ذلك عمومًا أن الشيفرة في كل نافذة تعمل بشكل منفصل تمامًا عن شيفرة النوافذ الأخرى ولا يمكن أن تؤثر الشيفرة في النافذة مباشرة على الشيفرة التي تجري في النافذة الأخرى أو على موقع ويب آخر. يُعد هذا الأمر مقياسًا جيدًا للأمان، فلو لم يكن الأمر كذلك سيكتب القراصنة شيفرات لسرقة معلومات من مواقع أخرى وغيرها من الأفعال الضارة. ملاحظة: هنالك طريقة لإرسال الشيفرة والبيانات من مختلف المواقع أو النوافذ بطريقة آمنة لكنها تقنيات متقدمة لن نغطيها في هذا المقال لكن يمكنك الاطلاع على مقال التخاطب بين نوافذ المتصفح عبر جافا سكريبت في أكاديمية حسوب. ترتيب تنفيذ شيفرة جافا سكريبت عندما يصادف المتصفح كتلة من شيفرات جافا سكريبت، سينفّذها عادة بالترتيب من الأعلى إلى الأسفل. ويعني هذا ضرورة الانتباه إلى الترتيب الذي تضع وفقه الأشياء. لنعد على سبيل المثال إلى شيفرة جافا سكريبت في المثال الأول: const para = document.querySelector("p"); para.addEventListener("click", updateName); function updateName() { const name = prompt("Enter a new name"); para.textContent = `Player 1: ${name}`; } نختار في السطر الأول مقطعًا نصيًا ثم نضيف مترصّد للحدث (مستمع للحدث) في السطر الثالث كي تُنفّذ شيفرة الكتلة البرمجية ()updteName (الأسطر من 5-8). تسأل الكتلة البرمجية ()updateName (يُدعى هذا النوع القابل للاستخدام المتكرر من الكتل البرمجية دوال functions) أن يُدخل المستخدم اسمًا جديدًا ومن ثم تضع الاسم ضمن الفقرة النصية وتحدّث ما يُعرض على الشاشة. فإن بدلت ترتيب أول سطرين، لن تعمل الشيفرة، بل ستحصل على خطأ تعرضه طرفية المطوّر في المتصفح وهي typeError: para is undefined. ويعني هذا أن الكائن para غير موجود بعد، ولا يمكن إضافة مترصّد أحداث إليه. ملاحظة: هذا الخطأ شائع الحدوث، لهذا انتبه إلى وجود الكائن في الشيفرة قبل أن تحاول العمل معه. الشيفرة المفسّرة والمصرّفة ربما سمعت بالمصطلحين "مفسّر interpreted" و "مصرّف compiled" في سياق تعلمك للبرمجة. ففي اللغات المفسّرة تنفّذ الشيفرة من الأعلى إلى الأسفل وتُعاد نتيجة تنفيذ الشيفرة مباشرة، ولا حاجة لنقل الشيفرة إلى شكل آخر قبل أن يُنفّذها المتصفح. إذ يستقبل الشيفرة بشكلها النصي المفهوم من قبل المبرمج ثم يعالجها مباشرة. بينما تحوّل الشيفرة في اللغات المصرّفة إلى شكل آخر قبل أن يُنفّذها الحاسوب. إذ تحوّل مثلًا شيفرة لغتي C أو ++C إلى لغة الآلة التي ينفذها الحاسوب بعد ذلك. وينفّذ البرنامج انطلاقًا من صيغته الثنائية التي تنتج عن تصريف شيفرته المصدرية. جافا سكريبت هي لغة برمجة مفسّرة خفيفة الحجم، يتلقى المتصفح شيفرتها بشكلها النصي وينفّذه. وإذا أردنا الحديث تقنيًا يمكننا القول أن مفسرّات جافا سكريبت الحديثة تستخدم تقنية تّدعى التصريف عند التنفيذ Just-in-time compiling لتحسين الأداء. إذ تُصرّف شيفرة جافا سكريبت المصدرية إلى شكل ثنائي أسرع في نفس الوقت الذي تُنفّذ فيه الشيفرة وهذا ما يجعل التنفيذ أسرع ما يمكن. مع ذلك لا تزال جافا سكريبت في عداد اللغات المفسّرة لأن التصريف يجري أثناء التنفيذ بدلًا من تصريف الشيفرة مسبقًا. ولكلا نوعي اللغات ميزاته، لكن لن نناقش هذا الموضوع الآن. شيفرة طرف العميل موازنة مع شيفرة طرف الخادم لربما سمعت أيضًا بمصطلحي "طرف الخادم server-side" و "طرف العميل client-side" وخاصة في سياق تعلم تطوير الويب. تُنفّذ شيفرة طرف العميل على حاسوب العميل أو المستخدم، فما يحدث عند استعراض صفحة الويب هو أن المتصفح سينزّل شيفرة طرف العميل ثم ينفذها ويعرضها. وما سنتحدث عنه في سلسلة مقالات جافا سكريبت هو استخدام جافا سكريبت من طرف العميل فقط. تُنفّذ شيفرة طرف الخادم بالمقابل على الخوادم ثم تُنزّل نتيجة التنفيذ وتُعرض في المتصفح. ومن لغات ويب التي تعمل من طرف الخادم نذكر PHP و Python و Ruby و ASP.NET وكذلك لغة جافا سكريبت. إذ يمكن استخدام جافا سكريبت كلغة برمجة من طرف الخادم في بيئة Node.js الشهيرة. الشيفرة الديناميكية موازنة مع الشيفرة الساكنة تُستخدم كلمة ديناميكي لوصف شيفرة جافا سكريبت من طرف العميل ولغات طرف الخادم، وتشير إلى إمكانية تحديث صفحة الويب أو التطبيق ليعرض أشياء مختلفة في ظروف مختلفة، وتوليد محتوى جديد حسب الحاجة. إذ توُلّد لغات طرف الخادم محتوى جديدًا على الخادم عن طريق سحب البيانات من قواعد البيانات مثلًا، بينما تولّد شيفرة جافا سكريبت المحتوى ديناميكيًا ضمن متصفّح العميل، كأن تنشأ جداول HTML وتملأها بالبيانات التي تطلبها من الخادم ومن ثم تعرض هذه الجداول ضمن صفحة الويب التي يراها المستخدم. قد يكون هناك اختلاف بسيط بين سياقي العمل لكنهما متعلقان ببعضهما، وكلا النهجين (طرف العميل وطرف الخادم) يعملان معًا عادة. عندما لا تُحدَّث صفحة الويب ديناميكيًا بمحتوى جديد ندعوها ساكنة static، فهي تعرض نفس المحتوى دائمًا. كيف تضيف شيفرة جافا سكريبت إلى صفحتك؟ تُضاف شيفرة جافا سكريبت إلى صفحة HTML بنفس الأسلوب الذي تُضاف به شيفرة CSS. إذ تستخدم CSS العنصر <link> لتطبيق ورقة تنسيق خارجية و العنصر <style> لتطبيق ورقة تنسيق داخلية على شيفرة HTML، بينما لا تحتاج جافا سكريبت سوى العنصر <script>. لنلق نظرة على عمله. شيفرة جافا سكريبت داخلية قبل كل شيء، أنشئ نسخة من الملف apply-javascript.html على جهازك وخزّنها في مكان مناسب. افتح الملف في متصفحك وضمن المحرر النصي في نفس الوقت. سترى أن شيفرة HTML قد أنشأت صفحة ويب بسيطة تضم زرًا يمكن النقر عليه. اضف الشيفرة التالية ضمن الترويسة في المحرر النصي وقبل وسم النهاية <head/>: <script> // JavaScript goes here </script> سنضيف الآن بعض شيفرة جافا سكريبت ضمن الوسم <script> وتحت عبارة "JavaScript goes here//" لنجعل الصفحة أكثر حيوية: document.addEventListener("DOMContentLoaded", () => { function createParagraph() { const para = document.createElement("p"); para.textContent = "You clicked the button!"; document.body.appendChild(para); } const buttons = document.querySelectorAll("button"); for (const button of buttons) { button.addEventListener("click", createParagraph); } }); احفظ الملف وحدّث المتصفّح، وستلاحظ ظهور فقرة نصية تحت الزر عند النقر عليه. ملاحظة: إن رأيت أن المثال لا يعمل كما هو مطلوب، راجع الخطوات السابقة بتأنٍ وتحقق أنك فعلت كل شيء بالشكل الصحيح. هل خزنت الملف بلاحقة html.؟ هل وضعت الوسم <script> قبل وسم النهاية <head/>؟ هل أدخلت شيفرة جافا سكريبت كما هي تمامًا؟ وانتبه إلى أن جافا سكريبت حساسة لحالة الأحرف وعليك إضافة الشيفرات كما هي تمامًا وإلا لن تعمل. شيفرة جافا سكريبت خارجية تعمل الشيفرة السابقة جيدًا، لكن ماذا لو أردت أن تضع تلك الشيفرة في ملف خارجي منفصل؟ أنشئ ملفًا جديدًا في نفس المكان الذي خزّنت فيه ملف HTML ثم سمّه script.js. تأكد أن لاحقة الملف هي js. لأنها الطريقة التي يُعرف بها ملف جافا سكريبت. استبدل الوسم <script> بالوسم التالي: <script src="script.js" defer></script> ضع الشيفرة التالية في ملف script.js: function createParagraph() { const para = document.createElement("p"); para.textContent = "You clicked the button!"; document.body.appendChild(para); } const buttons = document.querySelectorAll("button"); for (const button of buttons) { button.addEventListener("click", createParagraph); } احفظ الملف وحدّث المتصفّح وسترى الشيء ذاته، لكن شيفرة جافا سكريبت في هذه الحالة موجودة في ملف خارجي. وهذا عمومًا أمر جيد من ناحية تنظيم الشيفرة وجعلها قابلة للاستخدام ضمن جميع صفحات الموقع، إضافة إلى أن ملفات HTML أسهل قراءة في هذه الحالة دون وجود قطع من الشيفرة مزروعةً ضمنها. معالجات جافا سكريبت السطرية قد تصادف في بعض الحالات شيفرة جافا سكريبت ضمن شيفرة HTML، وسيبدو الأمر مشابهًا لما يلي: function createParagraph() { const para = document.createElement("p"); para.textContent = "You clicked the button!"; document.body.appendChild(para); } <button onclick="createParagraph()">Click me!</button> جرب هذه النسخة في المحرر التفاعلي: See the Pen javascript2 by Hsoub Academy (@HsoubAcademy) on CodePen. لهذه النسخة العمل ذاته كما في النسختين السابقتين، إلا أن العنصر <button> يضم معالجًا سطريًا هو onclick يفعّل عمل الدالة عند النقر على الزر. ننصحك بأن لا تفعل ذلك، فمن السيء أن تلوّث شيفرة HTML يشيفرة جافا سكريبت، إضافة إلى أنها طريقة غير فعّالة أن تضيف السمة "()onclick="createParagraph في كل زر تريده أن ينفّذ الوظيفة ذاتها. استخدام الدالة addEvenetListerer بدلًا من وضع شيفرة جافا سكريبت ضمن وسوم HTML، من الأفضل استخدام بناء جافا سكريبت صرف. إذ تسمح الدالة ()querySelectorAll بانتقاء كل أزرار الصفحة، ومن ثم إسناد معالج أحداث لكل زر من خلال الدالة ()addEventListener. إليك الشيفرة اللازمة: const buttons = document.querySelectorAll("button"); for (const button of buttons) { button.addEventListener("click", createParagraph); } قد تبدو الشيفرة أطول بقليل موازنة باستخدام السمة onclick، لكنها ستعمل مع جميع أزرار الصفحة مهما كان عددها أو مهما أضفت أو أزلت أزرارًا، دون الحاجة إلى تغيير شيفرة جافا سكريبت. ملاحظة: جرّب أن تعدّل في شيفرة الملف apply-javascript.html بإضافة بضعة أزرار إضافية، سترى عندها وبعد تحديث الصفحة أن النقر على أي زر منها سينشئ فقرة نصية. استراتيجيات تحميل السكربتات في صفحات الويب تصادفنا بعض المشاكل تتعلق بتوقيت تحميل السكربتات في صفحة الويب. فلا شيء بالبساطة التي يبدو عليها. ومن المشاكل الشائعة هي تحميل شيفرة HTML كاملة وبالترتيب الذي تظهر عليه ضمن الملف. فإن استخدمت جافا سكريبت للتعامل مع عناصر الصفحة (أو شجرة DOM بدقة)، لن تعمل الشيفرة إن حُّملت وفُسِّرت قبل أن يُحمّل عنصر HTML الذي تستهدفه. وما حدث في الأمثلة الماضية سواء باستخدام جافا سكربت داخليًا أو خارجيًا هو تحميل الشيفرة وتنفيذها ضمن ترويسة الملف وقبل تفسير أي عنصر من عناصر جسم الملف. قد ينتج عن هذا الأمر خطأ لهذا استخدمنا أسلوبًا للالتفاف حول المشكلة. لاحظ الأسلوب المستخدم في مثال الشيفرة الداخلية: document.addEventListener("DOMContentLoaded", () => { // … }); إن مترّصد الأحداث والذي يصغي إلى الحدث DOMContentLoaded في المتصفح، سينتظر اكتمال تحميل وتفسير جسم ملف HTML، ولن تعمل شيفرة جافا سكريبت ضمن هذه الكتلة قبل ذلك. وبهذه الطريقة نتفادى الخطأ. وفي مثال الشيفرة الخارجية، نستخدم ميزة أكثر حداثة لجافا سكريبت لحل المشكلة من خلال السمة defer التي تخبر المتصفح أن عليه إكمال تحميل شيفرة HTML عندما يصل إلى السمة <script>: <script src="script.js" defer></script> ستُحمّل شيفرة HTML و جافا سكريبت بالتوازي في هذه الحالة وستعمل الشيفرة جيدًا. ملاحظة: لا حاجة لاستخدام الحدث DOMContentLoaded في الشيفرة الخارجية لأن السمة defer تحل المشكلة. ولم نستخدم السمة defer في الشيفرة الداخلية لأنها تعمل فقط مع السكربت الخارجي. ومن الحلول القديمة التي استخدمت لحل لهذه المشكلة هو وضع شيفرة جافا سكريبت في نهاية الملف وقبل الموسم <body/> وبالتالي تُحمّل الشيفرة بعد إنتهاء تحميل وتفسير عناصر HTML. أما مشكلة هذا الحل هو إيقاف عمل شيفرة جافا سكريبت حتى يكتمل تحميل شجرة DOM الخاصة بملف HTML، وهذا ما يسبب مشكلة أداء كبيرة في مواقع الويب الأكبر، وسيبطئ الموقع. السمة async والسمة defer توجد في الواقع طريقتان عصريتان لتفادي مشكلة حجب الشيفرة باستخدام السمتين async و defer، وسنلقي نظرة عليهما. ستُحمّل السكربتات باستخدام async دون إيقاف تحميل الصفحة أثناء تقدم تحميل السكربت. لكن بمجرد اكتمال تحميل السكربت سيُنفَّذ هذا السكربت مما يمنع تصيير الصفحة، ولن تضمن بأي شكل تنفيذ السكربتات بترتيب محدد. ومن الأفضل استخدام هذه السمة عندما لا تتعلق السكربتات في الصفحة ببعضها البعض ولا بأي سكربت آخر في الصفحة. وبالنسبة للسكربتات التي تُحمّل باستخدام defer ستُحمّل بالترتيب الذي تظهر فيه ضمن الصفحة، ولن تعمل حتى يكتمل تحميل الصفحة، وهذا مفيد في السكربتات التي تتطلب اكتمال تكوين شجرة DOM الخاصة بالملف (وكمثال عليها السكربتات التي تعدّل عنصر أو أكثر في الصفحة). إليك تمثيلًا بصريًا لأساليب تحميل السكربتات وما الذي تعنيه لصفحتك: فلو كان لديك مثلًا عناصر <script> التالية: <script async src="js/vendor/jquery.js"></script> <script async src="js/script2.js"></script> <script async src="js/script3.js"></script> لا يمكنك الاعتماد على ترتيب تحميل العناصر السابقة، فقد يُحمّل jquery.js قبل أو بعد script2.js و script3.js، وإن حدث ذلك، ستوّلد أية دالة ضمن السكربتين الأخيرين خطأً إن اعتمدت على السكربت jquery لأنه قد لا يكون معرّفًا عند تنفيذها. لهذا استخدم السمة async إن كان لديك مجموعة من السكربتات التي تعمل في الخلفية وتريد فقط أن تضعها في مكانها المناسب بالسرعة الممكنة. فقد يكون لديك مثلًا ملفات بيانات خاصة بلعبة وتريدها أن تكون جاهزة عندما تبدأ اللعبة فعلًا، إذ لا بد من عرض مقدمة اللعبة وعناوينها والمساهمين فيها دون أن تُحجب حتى يُحمّل السكربت وأثناء ذلك يُحمّل السكربت بهدوء في الخلفية. وإن أردت تنفيذ السكربتات وفق تسلسل ظهورها (كما في الأسفل)، استخدم السمة defer، وستُنفَّذ حالما يُحمّل السكربت ومحتوى الصفحة: <script defer src="js/vendor/jquery.js"></script> <script defer src="js/script2.js"></script> <script defer src="js/script3.js"></script> نضمن في المثال الثاني أن jquery.js سيُحمّل قبل script2.js الذي يُحمّل قبل script3.js. ولن تُنفَّذ السكربتات حتى يكتمل تحميل الصفحة، وهذا أمر مفيد إن اعتمدت السكربتات على وجود شجرة DOM جاهزة وفي مكانها. باختصار: تخبر السمتان async و defer المتصفح أن يحمّل السكربتات في خيط منفصل بينما تحمّل بقية محتويات الصفحة في خيط مختلف في نفس الوقت وبالتالي لن يُحجب عرض محتوى الصفحة أثناء عملية إحضاره. تُنفّذ السكربتات التي تستخدم السمة async حالما ينتهي تحميلها، وسيحجب هذا عرض محتوى الصفحة أثناء التنفيذ ولا يمكن أن تضمن ترتيب السكربتات التي ستُنفَّذ. تُحمّل السكربتات التي تستخدم السمة defer بالترتيب الذي تظهر فيه وتُنفَّذ بمجرد انتهاء تحميل كل شيء. إن كان لابد من تنفيذ السكربت مباشرة بعد تحميله ولا يتعلق تنفيذه بسكربتات أو عناصر أخرى يفضّل استخدام async. إن كان لا بد من الانتظار حتى ينتهي تفسير الملف واعتمد السكربت على سكربتات أخرى أو على تكوين شجرة DOM الخاصة بالملف، استخدم defer وضع السكربتات (العناصر <script>) بالترتيب الذي تريده حتى يُنفذها المتصفح بنفس الترتيب. التعليقات في جافا سكريبت من الممكن كتابة تعليقات ضمن شيفرة جافا سكريبت كما هو الحال في HTML و CSS، وتُكتب هذه التعليقات لتزويد المطوّرين الذين يقرؤون الشيفرة (أو لك شخصيًا إن عدت إليها بعد فترة) بإرشادات عن طريقة عمل الشيفرة، وبالطبع يتجاهل المتصفح هذه التعليقات ولا يحللها. يُنصح باستخدام التعليقات ما أمكن فهي مفيدة وخاصة في التطبيقات الضخمة، وهنالك طريقتين لإدراج التعليقات: على شكل سطر وحيد بعد إشارتي شرطة أمامية //. // I am a comment أسطر متعددة مكتوبة بين النصين */ و /*. /* I am also a comment */ فمثلًا، يمكن إضافة التعليقات التالية إلى آخر مثال شرحناه: // Function: creates a new paragraph and appends it to the bottom of the HTML body. function createParagraph() { const para = document.createElement("p"); para.textContent = "You clicked the button!"; document.body.appendChild(para); } /* 1. Get references to all the buttons on the page in an array format. 2. Loop through all the buttons and add a click event listener to each one. When any button is pressed, the createParagraph() function will be run. */ const buttons = document.querySelectorAll("button"); for (const button of buttons) { button.addEventListener("click", createParagraph); } ملاحظة: كثرة التعليقات أفضل عادة من التعليقات القليلة، لكن انتبه إن رأيت نفسك تكتب تعليقات كثيرة لتشرح المتغيرات مثلًا (والتي يجب أن تكون واضحة وبديهية) أو لتشرح عمليات بسيطة (عندها قد تكون شيفرتك معقدة). خلاصة هكذا نكون قد خطونا أولى خطواتنا في جافا سكريبت. فقد بدأنا بتمهيد نظري لنمنحك فكرة عن استخدامات جافا سكريبت واﻷشياء التي يمكن أن تفعلها بها. كما رأينا خلال تقدم المقال بعض اﻷمثلة عن كتابة الشيفرة وتعلمنا كيف نضع الشيفرة في مكانها المناسب ضمن بقية شيفرة موقع الويب، إضافة إلى أشياء عديدة أخرى. قد تبدو جافا سكريبت صعبة قليلًا للوهلة الأولى، لكن لا تقلق فما نتعلمه في سلسلة المقالات التالية، سيأخذك خطوة خطوة وبأسلوب واضح حتى تصل إلى المرحلة التي تبني فيها أمثلتك الخاصة باستخدام جافا سكريبت ترجمة -وبتصرف- لمقال What's JavaScript اقرأ أيضًا تعلم لغة جافا سكريبت أساسيات لغة جافاسكربت تعلم جافا سكريبت من الصفر للاحتراف الدليل السريع إلى لغة البرمجة جافاسكريبت JavaScript
-
نراجع في هذا المقال بعض ميزات تخطيط الصفحات في CSS مثل القيم المختلفة للخاصية display وبعدها سنتعرف على بعض المفاهيم التي نغطيها تباعًا في سلسلة المقالات هذه، ونستعرض بإيجاز الخطوط العامة لتقنيات تخطيط الصفحات والتي نتوسع فيها مقالاتنا اللاحقة. عليك قبل البدء في قراءة هذا المقال أن: تطلع على أساسيات HTML كما شرحناها في سلسلة المقالات أساسيات HTML. تتفهم أساسيات CSS كما شرحناها في سلسلة المقالات خطواتك الأولى في CSS. تتيح لك تقنيات تخطيط الصفحات المعتمدة على CSS احتواء عناصر الصفحة والتحكم في موضعها بالنسبة إلى موقعها الافتراضي في الانسياب الاعتيادي أو التقليدي للعناصر أو بالنسبة إلى موضع بقية العناصر المحيطة بها أو بالنسبة إلى الحاوية الأم أو بالنسبة إلى نافذة العرض الأساسية أو نافذة المتصفح. وسنغطي في هذا المقال التقنيات التالية بشيء من التفصيل: الانسياب الاعتيادي للعناصر Normal flow الخاصية Display الصندوق المرن Flexbox تخطيط الشبكة Grid تعويم العناصر Floats توضيع العناصر Positioning تخطيط الجدول Table layout التخطيط متعدد الأعمدة Multiple-column layout لكل تقنية من هذه التقنيات استخداماتها وإيجابياتها وسلبياتها، ولم تصمم أي تقنية لتعمل بمفردها، وبالتالي حين تفهم سبب تصميم تخطيط معين ستمتلك القدرة على اتخاذ قرار استخدام التخطيط المناسب للمهمة التي تواجهك. الانسياب الاعتيادي لعناصر الصفحة وهو الأسلوب الذي يستخدمه المتصفح افتراضيًا في ترتيب العناصر في صفحات HTML عندما لا تتخذ أي خطوة لترتيب هذه العناصر. لنلق نظرة على المثال التالي: <p>I love my cat.</p> <ul> <li>Buy cat food</li> <li>Exercise</li> <li>Cheer up friend</li> </ul> <p>The end!</p> سيرتب المتصفح عناصر الصفحة السابقة افتراضيًا كالتالي: See the Pen css-layout-1 by Hsoub Academy (@HsoubAcademy) on CodePen. لاحظ كيف يعرض المتصفح عناصر HTML وفق ترتيب ظهورها في الشيفرة تمامًا ومتلاصقة عنصرًا فوق الآخر. إذ يعرض أولًا الفقرة النصية ثم القائمة غير المرتبة ومن ثم يعرض الفقرة النصية الثانية. تُدعى العناصر التي تُرتب فوق بعضها بالعناصر الكتلية block elements، بينما تُدعى العناصر التي تظهر إلى جانب بعضها في السطر ذاته -مثل الكلمات الموجودة في الفقرة- بالعناصر السطرية inline elements. ملاحظة: يُدعى الاتجاه الذي تُرتب فيه العناصر الكتلية (من الأعلى للأسفل في مثالنا) باتجاه الكتلة والذي قد يكون عموديًا في المحتوى المكتوب باللغة الإنكليزية أو العربية مثلً لأن اتجاه الكتابة فيها أفقي. كما يكون اتجاه الكتلة أفقيًا عندما يُكتب المحتوى بلغة تنساب حروفها من الأعلى للأسفل مثل اليابانية. أما الاتجاه السطري فهو اتجاه انسياب الكلمات في المحتوى. سيزود الانسياب الاعتيادي التخطيط المناسب للكثير من العناصر بالطريقة ضمن الصفحة، لكن ستحتاج في التخطيطات الأكثر تعقيدًا إلى تغيير هذا السلوك مستخدمًا بعض الأدوات التي تتيحها CSS. مع ذلك، عليك أن تبدأ من صفحة HTML مهيكلة جيدًا كي تكون قادرًا على التعامل مع الطريقة التي تُرتب فيها العناصر افتراضيًا بدلًا من معارضتها. تُستخدم الأساليب التالية في CSS لتغيير الترتيب الافتراضي للعناصر: الخاصية display: تغيّر القيم الأساسية لهذه الخاصية وهي block أو inline أو inline-block كيفية سلوك العناصر في الانسياب الاعتيادي، كأن يجعل عنصرًا كتليًا مثلًا يسلك سلوك عنصر سطري (نموذج الصندوق في CSS وستجد أيضًا أساليب كاملة للتخطيط تُفعّل عند اختيار قيم معينة للخاصية display مثل grid و flexbox أو التي تغيّر توضّع العنصر داخل العنصر الأب). التعويم floats: وذلك بتطبيق الخاصية، فاختيار القيمة left لهذه الخاصية مثلًا تدفع العناصر لتلتف نحو الجانب اليساري للعنصر كما في الصور التي يحاذيها نص إلى اليسار في مجلة. الخاصية position: تتيح لك هذه الخاصية ضبط موقع صندوق العنصر ضمن صناديق أخرى ضبطًا دقيقًا. يُعد الوضع static وضعًا افتراضيًا في الانسياب الاعتيادي، ومن الممكن أيضًا وضع العنصر بشكل مختلف باستخدام القيم الأخرى لهذه الخاصية، كأن يكون موقع العنصر ثابتًا أعلى المتصفح. تخطيط الجدول: يمكن استخدام الميزات التي تسمح بتنسيق جداول HTML مع العناصر الأخرى من خلال الخاصية display والقيمة table والخاصيات الأخرى المرتبطة بها. التخطيط متعدد الأعمدة: توضّع خاصيات الأعمدة -columns محتوى الكتلة ضمن أعمدة بشكل مشابه لما تراه في الصحف. الخاصية display تعتمد الأساليب الرئيسية في تخطيط الصفحات باستخدام CSS على استخدام قيم الخاصية display، إذ تسمح هذه القيم في تغيير الطريقة الافتراضية التي تُعرض بها الأشياء. ولكل عنصر قيمة افتراضية لهذه الخاصية في الانسياب الاعتيادي، أي الطريقة الاعتيادية التي يسلكها هذا العنصر. فعناصر الفقرة النصية <p> المكتوبة باللغة العربية مثلًا تتوضع فوق بعضها لأن التنسيق الافتراضي للعرض هو display: block. ولو أنشأت رابطًا ضمن فقرة نصية فسيبقى الرابط على نفس السطر مع بقية كلمات الفقرة ولن يقفز إلى السطر الثاني لأن التنسيق الافتراضي لعرض هذا العنصر هو display: inline. بالإمكان تغيير العرض التقليدي بتغيير قيم الخاصية display. فعنصر القائمة مثلًا <li> يسلك سلوك الكتلة افتراضيًا، أي تظهر هذه العناصر في المحتوى المكتوب بالعربية أو الانجليزية تحت بعضها. وعندما نضبط الخاصية display على inline، ستُعرض العناصر إلى جانب بعضها كفقرة نصية اعتيادية. تظهر أهمية هذه الطريقة في تغيير طريقة عرض أي عنصر من عناصر HTML دون المساس بدلالته (أي الغرض من استخدامه). بالإضافة إلى إمكانية تغيير طريقة عرض العناصر من كتلية إلى سطرية وبالعكس، ستجد الكثير من أساليب التخطيط التي تبدأ باستخدام الخاصية display. لكن ما يحدث عادة هو الحاجة إلى استخدام قيم أخرى لهذه الخاصية، وأكثرها أهمية في سياق مناقشتنا هما القيمتان display: flex و display: grid. مفهوم الصندوق المرن Flexbox يأتي مصطلح الصندوق المرن Flexbox كاسم مختصر لوحدة من وحدات CSS تُعرف بتخطيط الصندوق المرن Flexible Box Layout، وقد صممت هذه الوحدة لتسهيل ترتيب الأشياء في اتجاه واحد كأن تكون في صفٍ أو عمود. ولاستخدام الصندوق المرن لابد من تطبيق الخاصية display:flex على العنصر الأب الذي ترغب في ترتيب العناصر ضمنه بشكل مرن، وبالتالي ستصبح هذه العناصر مرنة. وسنرى ذلك في المثال البسيط التالي: ضبط قيمة الخاصية display على flex تعرض شيفرة HTML السابقة عنصرًا حاويًا <div> يمتلك صنف التنسيق wrapper ويضم داخله ثلاث عناصر من نفس النوع. ستُعرض هذه العناصر افتراضيًا كعناصر كتلية تحت بعضها البعض طالما أن لغة الكتابة سطرية مثل الإنكليزية في هذا المثال. لكن عند إضافة الخاصية display: flex إلى تنسيق العنصر الأب، سترتب العناصر نفسها على شكل أعمدة ثلاث، إذ تصبح هذه العناصر عناصر مرنة وتتأثر ببعض القيم الأساسية التي يطبقها الصندوق المرن على العنصر الأب. وقد ظهرت تلك العناصر ضمن سطر واحد وثلاث أعمدة لأن القيمة الأساسية للخاصية flex-direction في العنصر الأب هي row. وتمتد هذه العناصر أيضًا من ناحية الارتفاع لأن القيمة الأساسية للخاصية align-items للعنصر الأب هي stretch، أي أن العناصر تمتد بالاتجاه العمودي لتشغل ارتفاع العنصر الحاوي (العنصر الأب) والذي يحدده في هذه الحالة ارتفاع العنصر الأطول من بين العناصر الثلاث. تصطف العناصر إلى جانب بعضها بدءًا من طرف الحاوية وتترك أية مساحات فارغة لم يشغلها أي من هذه العناصر لتأتي بعدها وحتى نهاية السطر. .wrapper { display: flex; } <div class="wrapper"> <div class="box1">One</div> <div class="box2">Two</div> <div class="box3">Three</div> </div> سيرتب المتصفح عناصر الصفحة السابقة الآن كالتالي: See the Pen css-layout-2 by Hsoub Academy (@HsoubAcademy) on CodePen. ضبط خاصيات المرونة إضافة إلى الخاصيات التي يمكن تطبيقها على الحاوية المرنة، ستجد بعض الخاصيات التي يمكن تطبيقها على العناصر المرنة داخله أيضًا. يمكن لهذه الخاصيات بمشاركة أشياء أخرى أن تغيّر ترتيب العناصر المرنة كي تتمكن من التمدد والتقلص وفقًا للمساحة المتاحة لها. كمثال على ذلك، يمكن إضافة الخاصية flex إلى جميع الأبناء وإعطائها القيمة 1 مما يجعل العناصر تتمدد لتملأ العنصر الأب بدلًا من ترك مساحات فارغة في نهاية السطر. فإن كانت هناك مساحات إضافية ستغدو هذه العناصر أوسع وتتقلص إن لم تكن هناك مساحات كافية. وإن أضفت عناصر إضافية إلى الحاوية سيصغر حجم جميع العناصر لتوفر مكانًا للعنصر الجديد وستشغل العناصر كلها كامل مساحة الحاوية. .wrapper { display: flex; } .wrapper > div { flex: 1; } <div class="wrapper"> <div class="box1">One</div> <div class="box2">Two</div> <div class="box3">Three</div> </div> سنحصل على النتيجة التالية: See the Pen css-layout-3 by Hsoub Academy (@HsoubAcademy) on CodePen. ملاحظة: ما ذكرناه مقدمة مختصرة جدًا عما يمكن فعله في تخطيط الصندوق المرن وسنرى تفاصيل أوفى في مقال قادم. وكما صُمم تخطيط الصندوق المرن ليرتب العناصر باتجاه واحد، صُمم تخطيط الشبكة لترتيبها باتجاهين أي ضمن صفوف وأعمدة. ضبط الخاصية display على القيمة grid كما هو الحال في الصندوق المرن، نطبّق تخطيط الشبكة بإسناد القيمة الخاصة به إلى خاصية العرض display:grid. يستخدم المثال التالي نفس شيفرة HTML التي استخدمناها في المثال السابق. وإضافة إلى استخدام الخاصية display: grid، سنعرف بعض مسارات الصفوف والأعمدة في العنصر الأب باستخدام الخاصيتين grid-template-rows و grid-template-columns على التوالي. وقد عرفنا ثلاث أعمدة اتساع كل منها 1fr (جزء واحد من كل) وصفين ارتفاع كل منهما 100px. لا حاجة لوضع أي قواعد تنسيق للعناصر الأبناء لأنها تُرتب تلقائيًا ضمن الشبكة: .wrapper { display: grid; grid-template-columns: 1fr 1fr 1fr; grid-template-rows: 100px 100px; gap: 10px; } <div class="wrapper"> <div class="box1">One</div> <div class="box2">Two</div> <div class="box3">Three</div> <div class="box4">Four</div> <div class="box5">Five</div> <div class="box6">Six</div> </div> ستكون النتيجة كالتالي: See the Pen css-layout-4 by Hsoub Academy (@HsoubAcademy) on CodePen. وضع العناصر ضمن الشبكة بمجرد أن تبني الشبكة، ستتمكن من وضع عناصر أخرى ضمنها بشكل صريح بدلًا من الاعتماد على سلوك التوضع التلقائي. نعرّف في المثال التالي الشبكة نفسها التي عرفناها سابقًا، لكن بوجود ثلاث عناصر أبناء فقط. وضبطنا سطري البداية و النهاية باستخدام الخاصيتين grid-column و grid-row مما يجعل العنصر يمتد على عدة مسارات. .wrapper { display: grid; grid-template-columns: 1fr 1fr 1fr; grid-template-rows: 100px 100px; gap: 10px; } .box1 { grid-column: 2 / 4; grid-row: 1; } .box2 { grid-column: 1; grid-row: 1 / 3; } .box3 { grid-row: 2; grid-column: 3; } <div class="wrapper"> <div class="box1">One</div> <div class="box2">Two</div> <div class="box3">Three</div> </div> ستظهر النتيجة على هذا النحو: See the Pen css-layout-4 by Hsoub Academy (@HsoubAcademy) on CodePen. ملاحظة: يعرض المثالان السابقان فكرة بسيطة عن قوة تخطيط الشبكة، وسنفصل أكثر في مقال قادم. لنتابع في بقية هذا المقال بعض التخطيطات الأخرى الأقل أهمية كتخطيطات رئيسية لصفحة الويب، لكنها مفيدة في مهام أخرى. وستجد عند فهم طبيعة المهام التي يؤديها كل تخطيط أن التخطيط الملائم لمكوّن معين من صفحتك سيكون واضحًا أغلب الأحيان. تعويم العنصر: الخاصية float يغير تعويم العنصر سلوكه وعناصر الكتلة الأخرى التي تأتي بعده في الانسياب الاعتيادي. تتوضع العناصر المعومة إلى اليمين أو اليسار وتُنقل من مكانها ثم يعوم حولها بقية المحتوى. للخاصية القيم التالية: left: تعويم العنصر إلى اليسار. right: تعويم العنصر إلى اليمين. none: لا تسمح بتعويم العنصر وهي القيمة الافتراضية. inherit: قيمة الخاصية float للعنصر هي نفسها قيم هذه الخاصية في العنصر الأب. نعوّم في المثال التالي عنصر <div> إلى اليسار ونحدد له هامشًا margin نحو اليمين لدفع المحتوى المحيط به قليلًا. يعطينا هذا التنسيق تأثير التفاف النص حول صندوق العنصر المعوّم، وهذا أهم ما تريد معرفته عن التعويم كما يُستخدم في تصميم الويب المعاصر. <h1>Simple float example</h1> <div class="box">Float</div> <p> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla luctus aliquam dolor, eu lacinia lorem placerat vulputate. Duis felis orci, pulvinar id metus ut, rutrum luctus orci. Cras porttitor imperdiet nunc, at ultricies tellus laoreet sit amet. Sed auctor cursus massa at porta. Integer ligula ipsum, tristique sit amet orci vel, viverra egestas ligula. Curabitur vehicula tellus neque, ac ornare ex malesuada et. In vitae convallis lacus. Aliquam erat volutpat. Suspendisse ac imperdiet turpis. Aenean finibus sollicitudin eros pharetra congue. Duis ornare egestas augue ut luctus. Proin blandit quam nec lacus varius commodo et a urna. Ut id ornare felis, eget fermentum sapien. </p> .box { float: left; width: 150px; height: 150px; margin-right: 30px; } ستظهر النتيجة على هذا النحو: See the Pen css-layout-5 by Hsoub Academy (@HsoubAcademy) on CodePen. ملاحظة: سنشرح التعويم بشكل كامل في مقال لاحق عن خاصيات التعويم. فقد استخدم تلك الخاصيات قبل ظهور تقنيات الصندوق المرن والشبكة لإنشاء تخطيطات متعددة الأعمدة. وقد تصادف هذه التخطيطات في بعض مواقع الويب لهذا سنغطيها أيضًا في مقال مستقل. تقنيات ضبط موقع العنصر تساعدك هذه التقنيات في نقل عنصر من المكان الذي ينبغي أن يحتله ضمن الانسياب الاعتيادي للعناصر إلى مكان آخر. ولا يُعد تموضع العنصر أسلوبًا في إنشاء تخطيطات رئيسية للصفحة، بل هو أقرب إلى ترتيب وضبط الموقع المخصص لكل عنصر في الصفحة بدقة. لكنك ستجد تقنيات مفيدة للحصول على أنماط تخطيط مخصصة تعتمد على استخدام الخاصية position. إن فهم فكرة التموضع ستساعدك على فهم الانسياب الاعتيادي للعناصر، وما الذي يعنيه نقل العنصر خارج هذا الانسياب. يوجد عمومًا خمسة أنواع تجدر معرفتها لتموضع العناصر: التوضع الساكن Static positioning: وهو الوضع الافتراضي لجميع العناصر، ويعني ببساطة وضع العنصر في مكانه الاعتيادي ضمن تخطيط الصفحة. التوضع النسبي Relative positioning: يساعدك على تعديل موقع عنصر في الصفحة وذلك بنقله بالنسبة إلى مكانه الاعتيادي، إضافة إلى جعله فوق عناصر أخرى في الصفحة Overlapping. التوضع المطلق Absolute positioning: يُخرج العنصر تمامًا من الانسياب العتيادي وكأنه يقع ضمن طبقة خاصة به. وبهذه الطريقة ستتمكن من تثبيت العنصر بالنسبة إلى حواف أقرب عنصر أب ثابت الموقع (وإن لم يكن هناك واحد سيكون هذا العنصر بالطبع <html>). لهذا الوضع أهميته في إنشاء تخطيطات معقدة مثل الصناديق متعددة النوافذ التي تتراكب فيها نوافذ المحتوى فوق بعضها لتُعرض وتختفي عند الطلب، أو لوحات المعلومات التي تضبط لتكون خارج الشاشة ثم تظهر إلى جانب الصفحة بالنقر على زر تحكم مخصص. التوضع الثابت Fixed positioning: يشابه الوضع المطلق إلا أنه يُثبِّت موضع العنصر بالنسبة إلى نافذة عرض المتصفح وليس لموقع عنصر آخر. لهذا الوضع فائدته في إنشاء تأثيرات هامة مثل قوائم التنقل التي تبقى دائمًا في نفس المكان على الشاشة بينما يكون المحتوى متحركًا عند تمرير الصفحة. التوضع اللاصق Sticky positioning: طريقة جديدة لتوضيع العناصر تجعلها تتصرف وكأنها في الوضع النسبي position: relative حتى تصل إلى حد معين بالنسبة لنافذة العرض عنها تتصرف وكأنها في الوضع الثابت position: fixed. مثال بسيط عن تموضع العناصر كي نألف العمل مع تقنيات التخطيط السابقة، سنعرض مثالين سريعين لكل منهما بنية HTML نفسها (ترويسة تليها ثلاثة فقرات نصية) كالتالي: <h1>Positioning</h1> <p>I am a basic block level element.</p> <p class="positioned">I am a basic block level element.</p> <p>I am a basic block level element.</p> تُنسق شيفرة HTML افتراضيًا باستخدام شيفرة CSS التالية: body { width: 500px; margin: 0 auto; } p { background-color: rgb(207, 232, 220); border: 2px solid rgb(79, 185, 227); padding: 10px; margin: 10px; border-radius: 5px; } إليك خرج الشيفرة السابقة على المتصفح: See the Pen css-layout-6 by Hsoub Academy (@HsoubAcademy) on CodePen. التموضع النسبي: يتيح لنا التوضع النسبي إزاحة العنصر خارج نطاق الانسياب الاعتيادي، بمعنى إمكانية إنجاز مهام مثل تحريك أيقونة قليلًا لتحاذي نص أو عنوان. لفعل ذلك، بالإمكان تطبيق قواعد التنسيق التالية: .positioned { position: relative; top: 30px; left: 30px; } أعطينا في شيفرة التنسيق السابقة القيمة relative للخاصية position العائدة إلى الفقرة الموجودة في الوسط. بالطبع لن يظهر تأثير ذلك قبل أن نضبط أيضًا قيمًا للخاصيتين top و left، إذ تجعلان العنصر ينزاح إلى الأسفل واليمين. قد ترى أن ما حدث هو عكس ما تتوقعه، لكن فكر بالموضوع على أنك دفعت العنصر من جانبيه العلوي واليساري وبالتالي ستكون النتيجة انزياحه نحو الأسفل واليمين. إليك نتيجة الشيفرة السابقة: See the Pen css-layout-7 by Hsoub Academy (@HsoubAcademy) on CodePen. التموضع المطلق ويستخدم لإخراج العنصر كليًا من مجرى الانسياب الاعتيادي، وإعادة توضيعه بإزاحته مقدارًا محددًا عن حواف الكتلة التي تحتويه. بالعودة إلى مثالنا السابق (دون تطبيق التوضع)، سنضيف قواعد التنسيق التالية لإنجاز التوضع المطلق: .positioned { position: absolute; top: 30px; left: 30px; } أعطينا في شيفرة التنسيق السابقة القيمة absolute للخاصية position العائدة إلى الفقرة الموجودة في الوسط، واستخدمنا الخاصيتين top و left كما سبق. إليك النتيجة: See the Pen css-layout-8 by Hsoub Academy (@HsoubAcademy) on CodePen. كما ترى، الأمر مختلف تمامًا هذه المرة! لقد انفصل العنصر بالكامل عن تخطيط الصفحة وبقي أعلاها، بينما بقيت الفقرتين النصيتين الأخريين في مكانهما دون أن تتأثرا بموقع الفقرة التي غيرنا أسلوب توضعها. وكذلك نجد اختلاف تأثير الخاصيتين top و left على الموقع في حالتي التوضع النسبي والمطلق. إذ تُحسب الإزاحة في حالة التوضع المطلق بالنسبة لأعلى ويسار الصفحة. التموضع الثابت يزيل التموضع الساكن العنصر من مجرى الانسياب الاعتيادي كما يفعل التوضع المطلق، لكن الإزاحة ستُطبق في هذه الحالة بالنسبة غلى نافذة العرض وليس بالنسبة إلى العنصر الأب (الكتلة الحاوية). وطالما أن العنصر يبقى ثابتًا بالنسبة إلى نافذة العرض، يمكن استخدام هذا التموضع لإحداث تأثيرات مميزة كالقوائم التي تبقى في مكانها عند تمرير محتوى الصفحة. وكمثال عن هذا التوضع، نجد شيفرة HTML تضم ثلاث فقرات نصية يسبقها صندوق <div> نضبط خاصية position له على القيمة fixed. <h1>Fixed positioning</h1> <div class="positioned">Fixed</div> <p> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla luctus aliquam dolor, eu lacinia lorem placerat vulputate. Duis felis orci, pulvinar id metus ut, rutrum luctus orci. </p> <p> Cras porttitor imperdiet nunc, at ultricies tellus laoreet sit amet. Sed auctor cursus massa at porta. Integer ligula ipsum, tristique sit amet orci vel, viverra egestas ligula. Curabitur vehicula tellus neque, ac ornare ex malesuada et. </p> <p> In vitae convallis lacus. Aliquam erat volutpat. Suspendisse ac imperdiet turpis. Aenean finibus sollicitudin eros pharetra congue. Duis ornare egestas augue ut luctus. Proin blandit quam nec lacus varius commodo et a urna. Ut id ornare felis, eget fermentum sapien. </p> .positioned { position: fixed; top: 30px; left: 30px; } وهذه النتيجة: See the Pen css-layout-9 by Hsoub Academy (@HsoubAcademy) on CodePen. التموضع اللاصق وهي الأسلوب الأخير الذي نناقشه فيما يخص التوضّع. تمزج هذه القيمة بين التوضع النسبي والتوضع الثابت، فعندما نُطبق القاعدة position: sticky على عنصر سيتحرك مع المحتوى عند تمريره حتى يصل إلى إزاحة محددة سلفًا بالنسبة لنافذة العرض وعندها يبقى في هذا الموقع كما لو طبقنا القاعدة position: fixed. .positioned { position: sticky; top: 30px; left: 30px; } النتيجة: See the Pen css-layout-10 by Hsoub Academy (@HsoubAcademy) on CodePen. تخطيط الجدول تُعد جداول HTML طريقة جيدة في عرض البيانات القابلة للجدولة، لكنها استُخدمت قبل سنوات عدة -حين لم تكن CSS مدعومة جيدًا من قبل المتصفحات- من قبل المطورين لتخطيط صفحة ويب بأكملها. إذ استخدمت أسطر وأعمدة الجدول لاحتواء ترويسة الصفحة وتذييلها وأعمدتها، ونفع الأمر حينها مع وجود مشاكل جدية تتعلق بمرونة الجداول وكم الشيفرة الكبير اللازم لصياغتها وصعوبة تنقيحها، ناهيك عن الأخطاء الدلالية في استخدامها، فهي دلاليًا غير مخصصة لاحتواء عناصر أخرى بل لعرض البيانات وبالتالي ستجد قارئات الشاشة صعوبة في تتبع تخطيطات الجداول. تُعرض جداول HTML ضمن صفحة الويب وفقًا لمجموعة من خاصيات CSS تُعرّف تخطيط الجدول، ويمكن استخدام نفس تلك الخاصيات لترتيب عناصر أخرى غير الجداول بطريقة تُوصف أحيانًا بجداول CSS. يعرض المثال القادم هذا النمط من الاستخدام، وتجدر الملاحظة هنا أن استخدام تخطيط جداول CSS يُعد طريقة قديمة حاليًا، ولا يجب استخدامه إلا لدعم المتصفحات الأقدم التي لا تدعم تخطيط الشبكة أو الصندوق المرن. لنلق نظرة على مثالنا المكون من توصيف بسيط لنموذج HTML لكل عنصر إدخال فيه عنوان، كما وضعنا عنوانًا ضمن فقرة نصية، وغلفنا كل زوج (عنصر إدخال/ عنوان) ضمن عنصر <div> لتنسيق التخطيط. <form> <p>First of all, tell us your name and age.</p> <div> <label for="fname">First name:</label> <input type="text" id="fname" /> </div> <div> <label for="lname">Last name:</label> <input type="text" id="lname" /> </div> <div> <label for="age">Age:</label> <input type="text" id="age" /> </div> </form> بالنسبة لشيفرة CSS فمعظمها معروفة ماعدا استخدام الخاصية display. إذ ضبطت طريقة عرض العناصر <form> و <div> و <label> و <input> لتظهر على شكل جدول وصفوف في جدول وخلايا على الترتيب. ستظهر كل العناصر مبدئيًا كما لو أنها ضمن جدول معياري مما يجعل العناوين وعناصر الدخل على نفس السوية تلقائيًا. كل ما علينا فعله بعد ذلك تغيير الأبعاد قليلًا وضبط الهوامش وغير ذلك ليبدو مظهر العناصر أفضل. لاحظ كيف طبقنا قاعدة التنسيق ;display: table-caption على الفقرة النصية التي تمثل العنوان لتبدو وكأنها عنوان جدول، وكذلك القاعدة ;caption-side: bottom كي يظهر عنوان الجدول في الأسفل لأجل التنسيق فقط حتى لو كانت الفقرة قبل عناصر الإدخال <input> في الشيفرة المصدرية. html { font-family: sans-serif; } form { display: table; margin: 0 auto; } form div { display: table-row; } form label, form input { display: table-cell; margin-bottom: 10px; } form label { width: 200px; padding-right: 5%; text-align: right; } form input { width: 300px; } form p { display: table-caption; caption-side: bottom; width: 300px; color: #999; font-style: italic; } إليك نتيجة الشيفرة السابقة: See the Pen css-layout-11 by Hsoub Academy (@HsoubAcademy) on CodePen. ملاحظة: لن يُغطي موضوع تخطيط الجدول أكثر كونه تقنية تخطيط قديمة. التخطيط متعدد الجداول تزودنا وحدة التخطيط متعدد الجداول في CSS بآلية لترتيب المحتوى ضمن أعمدة كما تُكتب الأعمدة في صحيفة. وعلى الرغم من أن قراءة الأعمدة أقل فائدة في صفحات ويب لأن الزائر يُضطر إلى تمرير المحتوى إلى الأعلى والأسفل باستمرار، لكن تقنية ترتيب المحتوى ضمن أعمدة تبقى مهمة بحد ذاتها. ولكي نحول كتلة إلى حاوية متعددة الأعمدة، نستخدم الخاصية column-count التي تخبر المتصفح كم عدد الأعمدة التي نحتاجها أو الخاصية column-width التي تخبر المتصفح أن يملأ الحاوية بأكبر عدد ممكن من الأعمدة ذات الاتساع المحدد. نبدأ في مثالنا التالي بشيفرة HTML ضمن عنصر الحاوية <div> الذي يمتلك الصنف container. <div class="container"> <h1>Multi-column Layout</h1> <p> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla luctus aliquam dolor, eu lacinia lorem placerat vulputate. Duis felis orci, pulvinar id metus ut, rutrum luctus orci. Cras porttitor imperdiet nunc, at ultricies tellus laoreet sit amet. Sed auctor cursus massa at porta. </p> <p> Nam vulputate diam nec tempor bibendum. Donec luctus augue eget malesuada ultrices. Phasellus turpis est, posuere sit amet dapibus ut, facilisis sed est. Nam id risus quis ante semper consectetur eget aliquam lorem. </p> <p> Vivamus tristique elit dolor, sed pretium metus suscipit vel. Mauris ultricies lectus sed lobortis finibus. Vivamus eu urna eget velit cursus viverra quis vestibulum sem. Aliquam tincidunt eget purus in interdum. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. </p> </div> أسندنا القيمة 200px للخاصية column-width كي ندفع المتصفح إلى إنشاء أكبر عدد ممكن من الأعمدة ذات الاتساع 200 بكسل ضمن الحاوية، وسيجري تقاسم أية مساحات فارغة زائدة بين الأعمدة. .container { column-width: 200px; } وإليك نتيجة الكود السابق: See the Pen css-layout-12 by Hsoub Academy (@HsoubAcademy) on CodePen. الخلاصة ناقشنا في هذا المقال جميع تقنيات تخطيط صفحات الويب التي يجدر معرفتها بصورة موجزة، وسنتابع الحديث عن هذه التقنيات بالتفصيل وحالات استخدامها المختلفة مقالات أخرى. ترجمة -وبتصرف- للمقال: Introduction to CSS layout اقرأ أيضًا استخدام خطوط الكتابة في الويب مع CSS تعرف على أساسيات لغة CSS التحكم في تخطيط الصفحة وضبط محاذاة العناصر في CSS وحدات القياس والقيم في CSS
-
أظهرت الاستقالات الفردية الضخمة التي بدأت في 2021، وخاصةً في الولايات المتحدة أنّ عددًا لا بأس به من الموظفين قد تركوا أعمالهم لأنها غير مناسبة، فما الذي قاد هذا التغير في السلوك؟ لقد تركوا عملهم بحثًا عن فرص أخرى تحمل تحديًا وقيمةً وحماسًا أكبر. سنلقي نظرةً في هذا المقال على أهمية رغبة الموظف وحماسه لعمله، وكيف يؤثر ذلك على بقاء الموظف في منصبه، والخطوات التي قد تتبعها الشركات لتحسين انخراط الموظفين بفعالية في أعمالهم. أهمية المشاركة الفعالة للموظف في عمله وعلاقتها باستمرارية عمله يترابط مفهوما المشاركة في العمل والمحافظة على الموظفين، فالموظفون المتحمسون لأعمالهم أكثر التزامًا بالعمل وبالفريق وبالشركة مقارنةً بغيرهم؛ إذ يشعر هؤلاء بالرضا والسعادة في عملهم ويستمتعون بما يقدمونه مع شعور بالتقدير، لهذا فمن غير المحتمل أن يتخلوا عن شركتهم. تعرف الشركات التي موظفوها راضون بأعمالهم أنّ تحسين انخراط الموظفين في العمل لا يتم بمبادرة واحدة، فالأعمال التي تستقطب أفضل المواهب وينخفض فيها معدل ترك الموظفين لأعمالهم تتفهم جيدًا أنّ سعادة فريق العمل ورضاه ومشاركته بفعالية بما يُسند إليهم من مهام لا بدّ وأن تكون في أولوياتها وجزءًا من ثقافتها. لنلق الآن نظرةً أعمق إلى العلاقة بين انخراط الموظفين الفعّال بأعمالهم والمحافظة على المتحمسين منهم ضمن قوام الشركة باستكشاف الطرق المضمونة لتنفيذ الأمر. هل يقل احتمال ترك الموظف لعمله إن كان متحمسا له؟ استنادًا إلى بعض الدراسات التي أجراها موقع Gallup، فإنّ الشركات التي ينخرط فيها أكثر من 75% من موظفيها في أعمالهم بحماس، تنخفض فيها نسبة ترك العمل بمقدار 43% مقارنةً بالشركات التي تقل نسبة الموظفين المتحمسين لعملهم عن 25%. وبكلمات أخرى، سيقل احتمال استقالة الموظفين كثيرًا في الشركات التي تعي أهمية حماسة موظفيها لأعمالهم وترسخ ذلك في ثقافتها مقارنةً بتلك التي لا تولي أهميةً لحماسة موظفيها. هل تزداد إنتاجية الموظفين المتحمسين لوظائفهم؟ نعم، سينجز الموظف المتحمس لعمله أكثر مما ينجزه غيره. واستنادًا إلى تقرير نفس الموقع عن حالة مواقع العمل عالميًا لعام 2021، يكلّف الموظفون الأقل حماسًا للمشاركة في العمل شركاتهم 18% من الكتلة المخصصة سنويًا للمرتبات على إنتاجية ضائعة. وبطبيعة الحال، تزيد الإنتاجية المرتفعة من نجاح الأعمال. هل تزداد سعادة الموظفين الأكثر حماسا لعملهم؟ يختلف مفهومي الموظف السعيد والموظف المتحمس للعمل لكنهما يتبادلان التأثير، فالموظف المتحمس لعمله تعريفًا هو الموظف الذي يتابع عمله يوميًا ويعتقد بأنّ عمله يتطلب التحدي ويشعر بقيمة ما يفعله؛ فهم يحبون ما يفعلونه ويشعرون بأنّ هذا العمل يلائم مهاراتهم جيدًا وأنّ لهم مكانتهم في الفريق والشركة. تترجم هذه الميزات إلى مستوىً أعلى من الرضا ويمنح الموظف تجربةً أغنى وهدفًا من وراء ما يفعله، وهذه المشاعر هي دافع حقيقي نحو السعادة. كيف تقيس مدى حماسة الموظف وانخراطه في عمله؟ لا بدّ من تكامل الجهود لتحسين انخراط الموظفين في أعمالهم على مستوى المديرين وفريق العمل والشركة ككل إن أرادت الحفاظ على أفضل الموظفين لديها، لكن الأمر صعب خاصةً إن لم تعِ بوضوح من أين تبدأ وأين ستصل. قياس مدى حماسة موظفيك للعمل ضرورة ملحة: يمكن أن يساعد ذلك المديرين على تجميع بيانات ومعلومات وأفكار قد يحتاجونها لفهم ما ينفع وما لا ينفع، وما الذي ينبغي فعله للتطوير. هنالك العديد من المؤشرات التي يمكن تحديدها منها: استطلاعات جس النبض قد يصعب فهم التجربة التي يعيشها موظفوك، لهذا تُعدّ استطلاعات جس النبض المنتظمة مهمةً في متابعة نبض الأحداث التي تدور في شركتك. ستساعدك هذه الاستطلاعات على تحديد ما يدفع الموظفين إلى الحماسة في العمل، وما يبعدهم عن الانخراط بفعالية؛ ومن ثم اتخاذ التدابير اللازمة لتحسين ذلك (وبالتالي المحافظة على موظفيك في المحصلة). وهذا الاستطلاع شديد الفائدة للمديرين الذي يقودون فرقًا موزّعة. اللقاء وجها إلى وجه تنحصر المسؤولية في معرفة مدى حماس الموظف أو إهماله لعمله على المدير وحده، ومن هنا تظهر أهمية اللقاءات الفردية وجهًا لوجه وبانتظام، إذ تصنع هذه اللقاءات مساحةً للموظف كي يتحدث عن تجربته مع مديريه ويتشارك معهم الرؤى حول ما يحفزه خصوصًا وما يحبطه، ليستفيد منها المديرون في تحسين خبراتهم ويقود إلى مستوى أعلى من الانخراط في العمل. لقاءات ترك الخدمة إن قرر موظفك ترك العمل وتقديم استقالته، فمن الجيد عندها أن تستغل الفرصة لتعرف السبب الكامن وراء قراره، وما الذي يمكن للشركة فعله لزيادة حماس موظفيها لعملهم. حاول أن تركز في هذه اللقاءات على حماسة الموظف وانخراطه في العمل؛ وخاصةً على السبب الذي يبعد الموظف عن واجباته أو يشعره بذلك. لقاءات العمل المستمرة وهي وسيلة لمواجهة انسحاب الموظفين من شركتك. تساعدك هذه اللقاءات مع موظفيك الحاليين على فهم الأسباب التي قد تدفع الموظف للبقاء أو الرحيل (لتفادي لقاءات ترك الخدمة لاحقًا). ومن أبرز منافع هذه اللقاءات هي المعلومات التي تقدمها لك كي تنجز استراتيجية ناجحة ترفع معدل الاحتفاظ بالموظفين وتلبي حاجاتهم. إن حماسة الموظفين للعمل الذي يُسند إليهم أمر أساسي، خاصةً في مسألة بقائهم أو رحيلهم. وقياس الانخراط في العمل هو الاستراتيجية الأنسب في جمع المعلومات التي تحتاجها لبناء موقع عمل يجذب الموظفين للاستمرار في العمل. أربع استراتيجيات قيادية لزيادة انخراط الموظفين في أعمالهم باختصار، إن أردت المحافظة على المواهب ضمن شركتك، اجعل اندفاع الموظفين إلى العمل في أعلى قائمة أولوياتك وامنحه معناه الحقيقي، فاتخاذ خطوات فعالة في تحسين انخراط الموظفين في العمل سيؤثر جدًا على احتمال استمرارهم في العمل لديك. وفقًا لموقع Gallup مثلًا، سنجد أن: إذًا، كيف ستفعل ذلك بالضبط؟ لنلق نظرةً على أربع استراتيجيات يمكن للقادة اتباعها لزيادة انخراط الموظفين في نشاط الشركة وزيادة رضاهم وتقليل عدد حالات ترك العمل. افسح المجال للمناقشة حول الانخراط في العمل قد تعتقد بأنك تعرف ما عليك فعله لزيادة حماس موظفيك، وذلك من وجهة نظرك بصفتك قائد؛ لكن إن أردت أن تعرف تمامًا ما يلزم لبناء موقع عمل أكثر تحفيزًا، فعليك أن تسأل موظفيك. إن حل أي مسألة بانفتاح وإفساح المجال للحوار الصريح مع الفريق بما في ذلك جلسات العصف الذهني لخلق مبادرات أكثر إبداعًا، والحوارات الشخصية وجهًا لوجه مع أعضاء الفريق حول ما يعزز الانخراط في العمل، هو الأسلوب الأنجع في دعم الموظفين. كما أنّ خلق همساحات للنقاش الجماعي مع الفريق أو الفردي أمر أساسي، فقد تدير مثلًا جلسة عصف ذهني تطلب فيها رأي الفريق عما يمكن للإدارة فعله لتحسين اندفاعهم نحو إنجاز العمل، ثم ترتب بعد ذلك جلسات نقاش فردية لتقف على تفاصيل أعمق من كل فرد من أفراد الفريق على حدة. موظفوك في النهاية هم من سيندفع إلى العمل بنشاط أو يتخاذل، لهذا عليك أن تسألهم رأيهم في إنجاز الأمر إن أردت أن تميل الكفة إلى صالح شركتك. الإشادة بالموظفين سيكون للإشادة البسيطة بالموظف أثر طويل الأمد على مستوى انخراطه في أداء عمله. ووفقًا لموقع McKinsey، فقد ترفع الشركات نسبة الانخراط في العمل بواقع 55% إن لم تغفل حاجة الموظف لتلقي الثناء في الموقع الذي يعمل به. ابحث عن سبل تثني من خلالها على موظفيك إن أردت تعزيز انخراطهم في العمل، وإليك بعض الأمثلة: إن كنت تعقد اجتماعًا أو مراجعة لمشروع سيكون فريق العمل مكتملًا، فابدأ الاجتماع بالإشادة بالمساهمة الخاصة التي قدمها كل عضو من أعضاء الفريق لنجاح هذا المشروع. إذا أدى أحد أعضاء الفريق عملًا يفوق ما هو مطلوب منه، فتأكد من أن تشيد بجهوده وأن تعبر عن امتنانك لما أنجزه. وإن كنت ستفعل ذلك، فاحرص على أن يكون الأمر على مرأى أو مسمع أحد مديريه أو قادته. إن شعرت بأن أحد موظفيك قد أُرهق من العمل، فيمكنك أن تحدثه جانبًا مشيدًا بقدرته على الاطلاع بعدة مسؤوليات في الوقت ذاته واسأله إن كان بإمكانك فعل أي شيء للتخفيف من أعبائه. أظهر لهم أهمية ما ينجزون أن يلمس الموظف أهمية ما ينجزه هو أمر أساسي يدفعه نحو مزيد من الحماسة والانخراط في عمله. ووفقًا لاستطلاع رأي نفذه موقع "McKinsey"، فقد تشهد الشركات ارتفاعًا في نسبة انخراط العاملين يقارب 49% عندما تتماشى قيم الشركة مع الغايات الفردية للموظفين. أظهر لموظفيك كيف ولماذا يشكل ما ينجزون من أعمال جزءًا مفصليًا من مسيرة نجاح الشركة؛ فعندما تطلق مثلًا مشروعًا جديدًا، فلا تبدأه بتوزيع الأدوار والواجبات مباشرةً، بل تحدث عن أهمية المشروع وأثره على الشركة بكل من فيها، واشرح أهمية كل دور سيؤديه فريق العمل في تحقيق الأهداف طويلة الأجل للشركة. أو إن واجه بعض الموظفين الجدد صعوبةً في التعامل مع واجباتهم الجديدة، فخصص لهم وقتًا لتشرح لهم رؤية شركتك وأهدافها وقيمها وكيف سيساهم العمل من مواقعهم في دعم قيم وأهداف الشركة. استخدم برمجيات مناسبة للمساعدة في قياس مؤشرات حماسة الموظفين لأعمالهم إنّ الإبقاء على المواهب في الشركة أمر جوهري، والمحافظة على الموظفين المتميزين أساس سوق العمل وينبغي أن تضع الأعمال تعزيز انخراط الموظفين في أعمالهم في أعلى قائمة الأولويات؛ لهذا إن أردت الإبقاء على مستويات الانخراط مرتفعة، فلا بدّ من قياسها باستمرار وبفعالية، وهنا يُنصح باستخدام بعض البرمجيات المخصصة لذلك، مثل بعض البرمجيات التي تتيح لك إمكانية قياس العديد من مؤشرات انخراط موظفيك في العمل، واستطلاع رأي موظفيك وتحديد مستوى حماسهم، ويعطيك بيانات لاستخدامها في خلق قوة عاملة أكثر التزامًا بالعمل، وبالتالي شركةً أكثر نجاحًا. ترجمة -وبتصرف- للمقال The importance of employee engagement and retention for the future of work لصاحبته Rachel Steben. اقرأ أيضًا الفرق بين دوران الموظفين والاحتفاظ بهم مقابلة البقاء وسبب إجرائها أفكار فعالة لتحفيز الموظفين في الظروف الصعبة
-
سنعمل في هذا المقال على بناء كاميرا لشاشة لمس في محرك الألعاب جودو كي يتمكن اللاعب من تحريك الكاميرا حول محورها وتكبير وتصغير وتدوير الكاميرا عن طريق اللمس. تهيئة الكاميرا ثنائية البعد افتح محرك الألعاب جودو وأنشئ مشروعك الأول، وابدأ بعقدة من النوع CameraD2، ولجعلها حساسة للمس، سنضيف سكريبت يدير العملية، وذلك بالنقر عليها بالزر الأيمن للفأرة ثم اختيار "إلحاق نص برمجي attach script" ولنسمّه "TouchCameraController.gd". ملاحظة أضفنا أيقونة إلى المشهد لكي نرى الكائن الذي يتحرك. إعداد الكاميرا نحتاج بداية إلى مجموعة من المتغيرات للتحكم بالكاميرا التي تمثلها في جودو العقدة Camera2D. تمثل هذه المتغيرات سرعة الحركة المحورية وسرعة التكبير والتصغير، وسرعة الدوران، وأخرى للتحكم في إمكانية التحريك أو التكبير أو التدوير. وإضافة إلى ذلك، ننشئ متغيرات لتخزين حالة الكاميرا ومدخلات اللمس. إليك طريقة تعريف المتغيرات: extends Camera2D @export var zoom_speed: float = 0.1 @export var pan_speed: float = 1.0 @export var rotation_speed: float = 1.0 @export var can_pan: bool @export var can_zoom: bool @export var can_rotate: bool var start_zoom: Vector2 var start_dist: float var touch_points: Dictionary = {} var start_angle: float var current_angle: float قمنا في البداية بتوسيع الصنف Camera2D في Godot الذي يمثل كاميرا ثنائية الأبعاد 2D في جودو ثم حددنا مجموعة المتغيرات التي يمكن تعديلها وهي: zoom_speed: قيمة عددية تحدد سرعة عملية التكبير والتصغير للكاميرا. pan_speed: قيمة عددية تحدد سرعة عملية التحريك الجانبي للكاميرا. rotation_speed: قيمة عددية تحدد سرعة عملية تدوير الكاميرا. can_pan: قيمة بوليانية تحدد إمكانية التحريك الجانبي للكاميرا. can_zoom: قيمة بوليانية تحدد إمكانية التكبير أو التصغير للكاميرا. can_rotate: قيمة بوليانية تحدد إمكانية تدوير الكاميرا. ثم عرفنا المتغيرات التي ستخزن معلومات عن اللمس وهي: start_zoom لتخزين قيمة بداية عملية التكبير أو التصغير الحالي للكاميرا. start_dist لتخزين المسافة بين نقاط اللمس عند بداية التكبير أو التصغير. touch_points لتخزين مواقع نقاط اللمس على الشاشة لاستخدامها في حسابات التكبير والتدوير. start_angle لتخزين زاوية بداية التدوير لحساب الزاوية المطلوبة للتدوير. current_angle لتخزين الزاوية الحالية لعملية التدوير لتحديث تدوير العنصر المرئي بشكل مستمر. تحريك الكاميرا حول محورها لنبدأ اﻵن بتحريك الكاميرا حول محورها، وهي الحركة اﻷبسط التي تنفذها الكاميرا في المشهد. سنحتاج إلى وسيلة لترصّد بعض أحداث الدخل، لهذا سنتجاوز override الدالة (input(event_ للتحقق من أحداث اللمس touch والسحب drag على الشاشة والاستجابة لها بالشكل المناسب: func _input(event): if event is InputEventScreenTouch: _handle_touch(event) elif event is InputEventScreenDrag: _handle_drag(event) تخزّن الدالة (handle_touch(event_ موقع كل نقطة لُمست على الشاشة ضمن القاموس touch_points وتستخدم دليل اللمس touch index كمفتاح، وهذا أساسي لتتبع المواقع التي تُلمس على الشاشة. كما سنضبط قيمة المتغير start_dist على 0 إن كان عدد النقاط التي لُمست أقل من 2: func _handle_touch(event: InputEventScreenTouch): if event.pressed: touch_points[event.index] = event.position else: touch_points.erase(event.index) if touch_points.size() < 2: start_dist = 0 بإمكاننا تحريك الكاميرا حول محورها بالاستفادة من نقطة لمس واحدة، وذلك باستخدام الدالة (handle_drag(event_ المعرفة كالتالي: func _handle_drag(event: InputEventScreenDrag): touch_points[event.index] = event.position if touch_points.size() == 1 and can_pan: offset -= event.relative * pan_speed إن شغّلت اللعبة على محاكي أندرويد سترى كيف تتحرك الكاميرا: إضافة ميزة التكبير والتصغير سنضيف اﻵن إمكانية التكبير والتصغير (تقريب وإبعاد)، لهذا سنعدّل الدالة (handle_touch(event_ كي نعالج حالة وجود نقطتي لمس والتي سنحسب فيها المسافة اﻷولية بينهما ونخزّنها: func _handle_touch(event: InputEventScreenTouch): if event.pressed: touch_points[event.index] = event.position else: touch_points.erase(event.index) if touch_points.size() == 2: var touch_point_positions = touch_points.values() start_dist = touch_point_positions[0].distance_to(touch_point_positions[1]) start_zoom = zoom start_dist = 0 نضيف بعد ذلك إمكانية التحكم بالتكبير والتصغير إلى الدالة handle_drag(event)_، فعند وجود نقطتي لمس، نحسب المسافة الحالية بينهما وبناء عليها نضبط التكبير والتصغير: func _handle_drag(event: InputEventScreenDrag): touch_points[event.index] = event.position # Handle 1 touch point if touch_points.size() == 1 and can_pan: offset -= event.relative * pan_speed # Handle 2 touch points elif touch_points.size() == 2 and can_zoom: var touch_point_positions = touch_points.values() var current_dist = touch_point_positions[0].distance_to(touch_point_positions[1]) var zoom_factor = start_dist / current_dist zoom = start_zoom / zoom_factor limit_zoom(zoom) # This is about to be created! نستخدم الدالة (limit_zoom(zoom لمنع التكبير أو التصغير من تجاوز الحدود وسيقف التكبير أو التصغير عند حد معين: func limit_zoom(new_zoom: Vector2): if new_zoom.x < 0.1: zoom.x = 0.1 if new_zoom.y < 0.1: zoom.y = 0.1 if new_zoom.x > 10: zoom.x = 10 if new_zoom.y > 10: zoom.y = 10 انقر على زر التشغيل لترى النتيجة: إضافة الدوران لنضف أخيرًا إمكانية تدوير الكاميرا، لهذا سنقيس الزاوية الأولية بين نقطتي اللمس من خلال الدالة handle_touch(event)_: func _handle_touch(event: InputEventScreenTouch): if event.pressed: touch_points[event.index] = event.position else: touch_points.erase(event.index) if touch_points.size() == 2: if touch_points.size() == 2: var touch_point_positions = touch_points.values() start_dist = touch_point_positions[0].distance_to(touch_point_positions[1]) start_angle = get_angle(touch_point_positions[0], touch_point_positions[1]) start_zoom = zoom elif touch_points.size() < 2: start_dist = 0 نضيف بعد ذلك إمكانية التحكم بالدوران ضمن الدالة handle_drag(event)_: func _handle_drag(event: InputEventScreenDrag): touch_points[event.index] = event.position if touch_points.size() == 1: if can_pan: offset -= event.relative * pan_speed elif touch_points.size() == 2: var touch_point_positions = touch_points.values() var current_dist = touch_point_positions[0].distance_to(touch_point_positions[1]) var current_angle = get_angle(touch_point_positions[0], touch_point_positions[1]) #this will be created below var zoom_factor = start_dist / current_dist if can_zoom: zoom = start_zoom / zoom_factor if can_rotate: rotation -= (current_angle - start_angle) * rotation_speed start_angle = current_angle # حدث الزاوية اﻷولية إلى الزاوية الحالية limit_zoom(zoom) تٌستخدم الدالة (get_angle(p1, p2 لحساب الزاوية: func get_angle(p1: Vector2, p2: Vector2) -> float: var delta = p2 - p1 return fmod((atan2(delta.y, delta.x) + PI), (2 * PI)) جرّب تدوير اﻷيقونة اﻵن! السكريبت المكتمل إليك الشيفرة الكاملة: extends Camera2D @export var zoom_speed: float = 0.1 @export var pan_speed: float = 1.0 @export var rotation_speed: float = 1.0 @export var can_pan: bool @export var can_zoom: bool @export var can_rotate: bool var start_zoom: Vector2 var start_dist: float var touch_points: Dictionary = {} var start_angle: float var current_angle: float func _ready(): start_zoom = zoom func _input(event): if event is InputEventScreenTouch: _handle_touch(event) elif event is InputEventScreenDrag: _handle_drag(event) func _handle_touch(event: InputEventScreenTouch): if event.pressed: touch_points[event.index] = event.position else: touch_points.erase(event.index) if touch_points.size() == 2: var touch_point_positions = touch_points.values() start_dist = touch_point_positions[0].distance_to(touch_point_positions[1]) start_angle = get_angle(touch_point_positions[0], touch_point_positions[1]) start_zoom = zoom elif touch_points.size() < 2: start_dist = 0 func _handle_drag(event: InputEventScreenDrag): touch_points[event.index] = event.position if touch_points.size() == 1: if can_pan: offset -= event.relative * pan_speed elif touch_points.size() == 2: var touch_point_positions = touch_points.values() var current_dist = touch_point_positions[0].distance_to(touch_point_positions[1]) var current_angle = get_angle(touch_point_positions[0], touch_point_positions[1]) var zoom_factor = start_dist / current_dist if can_zoom: zoom = start_zoom / zoom_factor if can_rotate: rotation -= (current_angle - start_angle) * rotation_speed start_angle = current_angle # Update the start_angle to the current_angle for the next drag event limit_zoom(zoom) func limit_zoom(new_zoom: Vector2): if new_zoom.x < 0.1: zoom.x = 0.1 if new_zoom.y < 0.1: zoom.y = 0.1 if new_zoom.x > 10: zoom.x = 10 if new_zoom.y > 10: zoom.y = 10 func get_angle(p1: Vector2, p2: Vector2) -> float: var delta = p2 - p1 return fmod((atan2(delta.y, delta.x) + PI), (2 * PI)) الخلاصة صممنا في هذا المقال سكريبت كاميرا تتجاوب مع أفعال اللمس على الشاشة في محرك الألعاب جودو 4، ويمكنها التحرك حول محورها أو التكبير والتصغير أو الدوران. انتبه فقط إلى ضرورة ربط السكريبت بعقدة من النوع Camera2D واستمتع بعملك! ترجمة -وبتصرف- لمقال Building a touchscreen camera in Godot 4: A comprehensive guide اقرأ أيضًا تشغيل محرك الألعاب جودو على بعض أنواع العتاد غير المدعوم تعرف على أشهر محركات الألعاب تعرف على أشهر لغات برمجة الألعاب مدخل إلى محرك الألعاب جودو