You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Web-Dev-For-Beginners/translations/ar/3-terrarium/3-intro-to-DOM-and-closures/README.md

18 KiB

مشروع التيراريوم الجزء الثالث: التلاعب بـ DOM والإغلاق

DOM والإغلاق

رسم توضيحي بواسطة Tomomi Imura

اختبار ما قبل المحاضرة

اختبار ما قبل المحاضرة

المقدمة

التلاعب بـ DOM، أو "نموذج كائن الوثيقة"، هو جانب أساسي من تطوير الويب. وفقًا لـ MDN، "نموذج كائن الوثيقة (DOM) هو تمثيل البيانات للكائنات التي تشكل هيكل ومحتوى الوثيقة على الويب." التحديات المتعلقة بالتلاعب بـ DOM على الويب غالبًا ما كانت الدافع وراء استخدام أطر عمل JavaScript بدلاً من JavaScript العادية لإدارة DOM، ولكننا سنقوم بذلك بأنفسنا!

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

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

سنستخدم الإغلاق للتلاعب بـ DOM.

فكر في DOM كشجرة تمثل جميع الطرق التي يمكن بها التلاعب بوثيقة صفحة الويب. تم كتابة واجهات برمجية مختلفة (APIs) بحيث يمكن للمبرمجين، باستخدام لغة البرمجة التي يختارونها، الوصول إلى DOM وتحريره وتغييره وإعادة ترتيبه وإدارته بطرق أخرى.

تمثيل شجرة DOM

تمثيل لـ DOM وعلامات HTML التي تشير إليه. من Olfa Nasraoui

في هذا الدرس، سنكمل مشروع التيراريوم التفاعلي الخاص بنا عن طريق إنشاء JavaScript الذي سيسمح للمستخدم بالتلاعب بالنباتات على الصفحة.

المتطلبات الأساسية

يجب أن تكون قد أنشأت HTML وCSS للتيراريوم الخاص بك. بحلول نهاية هذا الدرس، ستكون قادرًا على نقل النباتات داخل وخارج التيراريوم عن طريق سحبها.

المهمة

في مجلد التيراريوم الخاص بك، قم بإنشاء ملف جديد يسمى script.js. قم باستيراد هذا الملف في قسم <head>:

	<script src="./script.js" defer></script>

ملاحظة: استخدم defer عند استيراد ملف JavaScript خارجي إلى ملف HTML للسماح بتنفيذ JavaScript فقط بعد تحميل ملف HTML بالكامل. يمكنك أيضًا استخدام السمة async، التي تسمح بتنفيذ السكربت أثناء تحليل ملف HTML، ولكن في حالتنا، من المهم أن تكون عناصر HTML متاحة بالكامل للسحب قبل تنفيذ سكربت السحب.


عناصر DOM

أول شيء تحتاج إلى القيام به هو إنشاء مراجع للعناصر التي تريد التلاعب بها في DOM. في حالتنا، هي النباتات الـ 14 الموجودة حاليًا في الأشرطة الجانبية.

المهمة

dragElement(document.getElementById('plant1'));
dragElement(document.getElementById('plant2'));
dragElement(document.getElementById('plant3'));
dragElement(document.getElementById('plant4'));
dragElement(document.getElementById('plant5'));
dragElement(document.getElementById('plant6'));
dragElement(document.getElementById('plant7'));
dragElement(document.getElementById('plant8'));
dragElement(document.getElementById('plant9'));
dragElement(document.getElementById('plant10'));
dragElement(document.getElementById('plant11'));
dragElement(document.getElementById('plant12'));
dragElement(document.getElementById('plant13'));
dragElement(document.getElementById('plant14'));

ما الذي يحدث هنا؟ أنت تشير إلى الوثيقة وتبحث في DOM الخاص بها للعثور على عنصر بمعرف معين. تذكر في الدرس الأول عن HTML أنك أعطيت معرفات فردية لكل صورة نبات (id="plant1"). الآن ستستفيد من هذا الجهد. بعد تحديد كل عنصر، تقوم بتمرير هذا العنصر إلى دالة تسمى dragElement التي ستقوم بإنشائها قريبًا. وبالتالي، يصبح العنصر في HTML قابلًا للسحب، أو سيكون كذلك قريبًا.

لماذا نشير إلى العناصر بواسطة المعرف؟ لماذا لا نستخدم الفئة الخاصة بـ CSS؟ قد ترغب في الرجوع إلى الدرس السابق عن CSS للإجابة على هذا السؤال.


الإغلاق

الآن أنت جاهز لإنشاء إغلاق dragElement، وهو دالة خارجية تغلق دالة داخلية أو دوال (في حالتنا، سيكون لدينا ثلاث دوال).

الإغلاقات مفيدة عندما تحتاج دالة أو أكثر إلى الوصول إلى نطاق الدالة الخارجية. إليك مثال:

function displayCandy(){
	let candy = ['jellybeans'];
	function addCandy(candyType) {
		candy.push(candyType)
	}
	addCandy('gumdrops');
}
displayCandy();
console.log(candy)

في هذا المثال، تحيط دالة displayCandy بدالة تضيف نوعًا جديدًا من الحلوى إلى مصفوفة موجودة بالفعل في الدالة. إذا قمت بتشغيل هذا الكود، ستكون المصفوفة candy غير معرفة، لأنها متغير محلي (محلي للإغلاق).

كيف يمكنك جعل المصفوفة candy قابلة للوصول؟ حاول نقلها خارج الإغلاق. بهذه الطريقة، تصبح المصفوفة عالمية، بدلاً من أن تظل متاحة فقط لنطاق الإغلاق المحلي.

المهمة

تحت التصريحات الخاصة بالعناصر في script.js، قم بإنشاء دالة:

function dragElement(terrariumElement) {
	//set 4 positions for positioning on the screen
	let pos1 = 0,
		pos2 = 0,
		pos3 = 0,
		pos4 = 0;
	terrariumElement.onpointerdown = pointerDrag;
}

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

بالإضافة إلى ذلك، يتم تعيين terrariumElement الذي يتم تمريره إلى هذه الدالة إلى حدث pointerdown، وهو جزء من واجهات برمجية للويب المصممة للمساعدة في إدارة DOM. يتم تشغيل onpointerdown عند الضغط على زر، أو في حالتنا، عند لمس عنصر قابل للسحب. يعمل هذا المعالج للأحداث على كل من متصفحات الويب والجوال، مع بعض الاستثناءات.

معالج الحدث onclick لديه دعم أكبر عبر المتصفحات؛ لماذا لا تستخدمه هنا؟ فكر في نوع التفاعل الدقيق الذي تحاول إنشاؤه هنا.


دالة Pointerdrag

يصبح terrariumElement جاهزًا للسحب؛ عندما يتم تشغيل حدث onpointerdown، يتم استدعاء دالة pointerDrag. أضف هذه الدالة مباشرة تحت هذا السطر: terrariumElement.onpointerdown = pointerDrag;:

المهمة

function pointerDrag(e) {
	e.preventDefault();
	console.log(e);
	pos3 = e.clientX;
	pos4 = e.clientY;
}

تحدث عدة أمور. أولاً، تمنع الأحداث الافتراضية التي تحدث عادةً عند الضغط على المؤشر من الحدوث باستخدام e.preventDefault();. بهذه الطريقة، لديك المزيد من التحكم في سلوك الواجهة.

عد إلى هذا السطر عندما تكون قد أنشأت ملف السكربت بالكامل وجربه بدون e.preventDefault() - ماذا يحدث؟

ثانيًا، افتح index.html في نافذة المتصفح، وقم بفحص الواجهة. عندما تنقر على نبات، يمكنك رؤية كيف يتم التقاط حدث 'e'. استكشف الحدث لترى مقدار المعلومات التي يتم جمعها بواسطة حدث واحد عند الضغط على المؤشر!

بعد ذلك، لاحظ كيف يتم تعيين المتغيرات المحلية pos3 و pos4 إلى e.clientX. يمكنك العثور على قيم e في نافذة الفحص. هذه القيم تلتقط إحداثيات x و y للنبات في اللحظة التي تنقر عليه أو تلمسه. ستحتاج إلى التحكم الدقيق في سلوك النباتات أثناء النقر عليها وسحبها، لذا تحتفظ بتتبع إحداثياتها.

هل أصبح من الواضح أكثر لماذا يتم بناء هذا التطبيق بالكامل باستخدام إغلاق كبير؟ إذا لم يكن كذلك، كيف ستحتفظ بالنطاق لكل من النباتات الـ 14 القابلة للسحب؟

أكمل الدالة الأولية بإضافة اثنين من التلاعبات الأخرى بأحداث المؤشر تحت pos4 = e.clientY:

document.onpointermove = elementDrag;
document.onpointerup = stopElementDrag;

الآن تشير إلى أنك تريد أن يتم سحب النبات مع المؤشر أثناء تحريكه، وأن تتوقف حركة السحب عندما تقوم بإلغاء تحديد النبات. onpointermove و onpointerup هما جزءان من نفس واجهة برمجية مثل onpointerdown. ستقوم الواجهة الآن بإلقاء أخطاء لأنك لم تقم بعد بتعريف دالتي elementDrag و stopElementDrag، لذا قم بإنشائهما بعد ذلك.

دالتي elementDrag و stopElementDrag

ستكمل الإغلاق الخاص بك بإضافة دالتين داخليتين إضافيتين ستتعاملان مع ما يحدث عند سحب نبات وعند التوقف عن سحبه. السلوك الذي تريده هو أن تكون قادرًا على سحب أي نبات في أي وقت ووضعه في أي مكان على الشاشة. هذه الواجهة غير متحيزة تمامًا (لا توجد منطقة إسقاط على سبيل المثال) للسماح لك بتصميم التيراريوم الخاص بك تمامًا كما تريد عن طريق إضافة النباتات وإزالتها وإعادة وضعها.

المهمة

أضف دالة elementDrag مباشرة بعد القوس المغلق لـ pointerDrag:

function elementDrag(e) {
	pos1 = pos3 - e.clientX;
	pos2 = pos4 - e.clientY;
	pos3 = e.clientX;
	pos4 = e.clientY;
	console.log(pos1, pos2, pos3, pos4);
	terrariumElement.style.top = terrariumElement.offsetTop - pos2 + 'px';
	terrariumElement.style.left = terrariumElement.offsetLeft - pos1 + 'px';
}

في هذه الدالة، تقوم بالكثير من تحرير المواضع الأولية 1-4 التي قمت بتعيينها كمتغيرات محلية في الدالة الخارجية. ما الذي يحدث هنا؟

أثناء السحب، تعيد تعيين pos1 بجعلها مساوية لـ pos3 (التي قمت بتعيينها سابقًا كـ e.clientX) ناقص القيمة الحالية لـ e.clientX. تقوم بعملية مشابهة لـ pos2. ثم تعيد تعيين pos3 و pos4 إلى إحداثيات X و Y الجديدة للعنصر. يمكنك مشاهدة هذه التغييرات في وحدة التحكم أثناء السحب. ثم تقوم بالتلاعب بأسلوب CSS الخاص بالنبات لتعيين موضعه الجديد بناءً على المواضع الجديدة لـ pos1 و pos2، مع حساب إحداثيات X و Y العلوية واليسرى للنبات بناءً على مقارنة إزاحته بهذه المواضع الجديدة.

offsetTop و offsetLeft هما خصائص CSS تحدد موضع العنصر بناءً على موضع العنصر الأب؛ يمكن أن يكون العنصر الأب أي عنصر ليس موضعه static.

كل هذا إعادة الحساب للمواضع يسمح لك بضبط سلوك التيراريوم ونباتاته بدقة.

المهمة

المهمة النهائية لإكمال الواجهة هي إضافة دالة stopElementDrag بعد القوس المغلق لـ elementDrag:

function stopElementDrag() {
	document.onpointerup = null;
	document.onpointermove = null;
}

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

ماذا يحدث إذا لم تقم بتعيين هذه الأحداث إلى null؟

الآن لقد أكملت مشروعك!

🥇تهانينا! لقد انتهيت من تيراريومك الجميل. التيراريوم النهائي


🚀التحدي

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

اختبار ما بعد المحاضرة

اختبار ما بعد المحاضرة

المراجعة والدراسة الذاتية

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

ابحث عن المزيد من المعلومات حول أحداث المؤشر في وثائق W3C وعلى وثائق MDN.

تحقق دائمًا من قدرات المتصفح باستخدام CanIUse.com.

الواجب

اعمل قليلاً مع DOM


إخلاء المسؤولية:
تم ترجمة هذا المستند باستخدام خدمة الترجمة بالذكاء الاصطناعي Co-op Translator. بينما نسعى لتحقيق الدقة، يرجى العلم أن الترجمات الآلية قد تحتوي على أخطاء أو معلومات غير دقيقة. يجب اعتبار المستند الأصلي بلغته الأصلية المصدر الرسمي. للحصول على معلومات حاسمة، يُوصى بالاستعانة بترجمة بشرية احترافية. نحن غير مسؤولين عن أي سوء فهم أو تفسيرات خاطئة تنشأ عن استخدام هذه الترجمة.