|
3 weeks ago | |
---|---|---|
.. | ||
README.md | 3 weeks ago | |
assignment.md | 4 weeks ago |
README.md
پروژه تراریوم بخش ۳: دستکاری DOM و Closure
طراحی توسط Tomomi Imura
آزمون پیش از درس
مقدمه
دستکاری DOM یا "مدل شیء سند" یکی از جنبههای کلیدی توسعه وب است. طبق گفته MDN، "مدل شیء سند (DOM) نمایش دادهای از اشیاء است که ساختار و محتوای یک سند در وب را تشکیل میدهند." چالشهای مربوط به دستکاری DOM در وب اغلب دلیل استفاده از فریمورکهای جاوااسکریپت به جای جاوااسکریپت خالص برای مدیریت DOM بوده است، اما ما خودمان این کار را انجام خواهیم داد!
علاوه بر این، این درس ایدهای از یک Closure در جاوااسکریپت را معرفی میکند، که میتوانید آن را به عنوان یک تابع درون تابع دیگر تصور کنید، به طوری که تابع داخلی به محدوده تابع خارجی دسترسی دارد.
Closureها در جاوااسکریپت موضوعی گسترده و پیچیده هستند. این درس به ایده ابتدایی اشاره میکند که در کد این تراریوم، یک Closure وجود دارد: یک تابع داخلی و یک تابع خارجی که به گونهای ساخته شدهاند که تابع داخلی به محدوده تابع خارجی دسترسی داشته باشد. برای اطلاعات بیشتر در مورد نحوه عملکرد این موضوع، لطفاً به مستندات گسترده مراجعه کنید.
ما از یک Closure برای دستکاری DOM استفاده خواهیم کرد.
DOM را به عنوان یک درخت تصور کنید که تمام روشهایی را که میتوان یک سند صفحه وب را دستکاری کرد، نشان میدهد. APIهای مختلفی (رابطهای برنامهنویسی کاربردی) نوشته شدهاند تا برنامهنویسان بتوانند با استفاده از زبان برنامهنویسی مورد نظر خود به DOM دسترسی پیدا کرده و آن را ویرایش، تغییر، بازآرایی و مدیریت کنند.
نمایشی از DOM و کد HTML که به آن ارجاع میدهد. از Olfa Nasraoui
در این درس، پروژه تعاملی تراریوم خود را با ایجاد جاوااسکریپتی که به کاربر اجازه میدهد گیاهان را در صفحه دستکاری کند، تکمیل خواهیم کرد.
پیشنیاز
شما باید HTML و CSS تراریوم خود را ساخته باشید. تا پایان این درس، قادر خواهید بود گیاهان را با کشیدن و رها کردن به داخل و خارج از تراریوم منتقل کنید.
وظیفه
در پوشه تراریوم خود، یک فایل جدید به نام script.js
ایجاد کنید. این فایل را در بخش <head>
وارد کنید:
<script src="./script.js" defer></script>
توجه: هنگام وارد کردن یک فایل جاوااسکریپت خارجی به فایل HTML از
defer
استفاده کنید تا جاوااسکریپت فقط پس از بارگذاری کامل فایل HTML اجرا شود. همچنین میتوانید از ویژگیasync
استفاده کنید که به اسکریپت اجازه میدهد در حین تجزیه فایل HTML اجرا شود، اما در مورد ما، مهم است که عناصر HTML قبل از اجرای اسکریپت کشیدن به طور کامل در دسترس باشند.
عناصر DOM
اولین کاری که باید انجام دهید این است که به عناصری که میخواهید در DOM دستکاری کنید، ارجاع دهید. در مورد ما، این عناصر ۱۴ گیاه هستند که در حال حاضر در نوارهای کناری منتظر هستند.
وظیفه
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 آن به دنبال عنصری با یک Id خاص میگردید. به یاد دارید که در اولین درس HTML به هر تصویر گیاه یک Id جداگانه دادید (id="plant1"
)؟ اکنون از آن تلاش استفاده خواهید کرد. پس از شناسایی هر عنصر، آن را به تابعی به نام dragElement
که در یک لحظه خواهید ساخت، ارسال میکنید. بنابراین، عنصر در HTML اکنون قابلیت کشیدن دارد، یا به زودی خواهد داشت.
✅ چرا عناصر را با Id ارجاع میدهیم؟ چرا نه با کلاس CSS؟ ممکن است به درس قبلی در مورد CSS مراجعه کنید تا به این سؤال پاسخ دهید.
Closure
اکنون آمادهاید تا Closure dragElement
را ایجاد کنید، که یک تابع خارجی است که یک یا چند تابع داخلی را در بر میگیرد (در مورد ما، سه تابع خواهیم داشت).
Closureها زمانی مفید هستند که یک یا چند تابع نیاز به دسترسی به محدوده تابع خارجی داشته باشند. در اینجا یک مثال آورده شده است:
function displayCandy(){
let candy = ['jellybeans'];
function addCandy(candyType) {
candy.push(candyType)
}
addCandy('gumdrops');
}
displayCandy();
console.log(candy)
در این مثال، تابع displayCandy
تابعی را در بر میگیرد که یک نوع آبنبات جدید را به آرایهای که قبلاً در تابع وجود دارد، اضافه میکند. اگر این کد را اجرا کنید، آرایه candy
تعریف نشده خواهد بود، زیرا یک متغیر محلی است (محلی برای Closure).
✅ چگونه میتوانید آرایه candy
را قابل دسترسی کنید؟ سعی کنید آن را خارج از Closure منتقل کنید. به این ترتیب، آرایه به جای اینکه فقط در محدوده محلی Closure باقی بماند، به صورت سراسری در دسترس خواهد بود.
وظیفه
زیر اعلان عناصر در 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
تنظیم میکنید. اینها متغیرهای محلی هستند که برای هر عنصر هنگام افزودن قابلیت کشیدن و رها کردن در Closure دستکاری خواهند شد. تراریوم با این عناصر کشیدهشده پر خواهد شد، بنابراین برنامه باید مکان آنها را پیگیری کند.
علاوه بر این، terrariumElement
که به این تابع ارسال میشود، یک رویداد pointerdown
اختصاص داده میشود، که بخشی از web APIs طراحیشده برای کمک به مدیریت DOM است. onpointerdown
زمانی اجرا میشود که یک دکمه فشار داده شود، یا در مورد ما، یک عنصر قابل کشیدن لمس شود. این رویداد در هر دو مرورگر وب و موبایل کار میکند، با چند استثنا.
✅ رویداد onclick
پشتیبانی بسیار بیشتری در مرورگرهای مختلف دارد؛ چرا از آن در اینجا استفاده نمیکنید؟ به نوع دقیق تعامل صفحهای که میخواهید ایجاد کنید فکر کنید.
تابع Pointerdrag
عنصر terrariumElement
آماده است تا کشیده شود؛ وقتی رویداد onpointerdown
اجرا میشود، تابع pointerDrag
فراخوانی میشود. این تابع را درست زیر این خط اضافه کنید: terrariumElement.onpointerdown = pointerDrag;
:
وظیفه
function pointerDrag(e) {
e.preventDefault();
console.log(e);
pos3 = e.clientX;
pos4 = e.clientY;
}
چندین اتفاق میافتد. اول، شما رویدادهای پیشفرضی که معمولاً در pointerdown
رخ میدهند را با استفاده از e.preventDefault();
غیرفعال میکنید. به این ترتیب کنترل بیشتری بر رفتار رابط کاربری دارید.
وقتی فایل اسکریپت را به طور کامل ساختید، به این خط بازگردید و آن را بدون
e.preventDefault()
امتحان کنید - چه اتفاقی میافتد؟
دوم، فایل index.html
را در یک پنجره مرورگر باز کنید و رابط کاربری را بررسی کنید. وقتی روی یک گیاه کلیک میکنید، میتوانید ببینید که چگونه رویداد 'e' ثبت میشود. به جزئیات رویداد نگاه کنید تا ببینید چقدر اطلاعات با یک رویداد pointerdown جمعآوری میشود!
سپس، توجه کنید که چگونه متغیرهای محلی pos3
و pos4
به e.clientX
تنظیم میشوند. میتوانید مقادیر e
را در پنجره بررسی مشاهده کنید. این مقادیر مختصات x و y گیاه را در لحظهای که روی آن کلیک میکنید یا آن را لمس میکنید، ثبت میکنند. شما به کنترل دقیق رفتار گیاهان هنگام کلیک و کشیدن آنها نیاز دارید، بنابراین مختصات آنها را پیگیری میکنید.
✅ آیا اکنون واضحتر شده است که چرا کل این برنامه با یک Closure بزرگ ساخته شده است؟ اگر اینطور نبود، چگونه میتوانستید محدوده هر یک از ۱۴ گیاه قابل کشیدن را مدیریت کنید؟
تابع اولیه را با افزودن دو دستکاری دیگر رویداد pointer زیر pos4 = e.clientY
کامل کنید:
document.onpointermove = elementDrag;
document.onpointerup = stopElementDrag;
اکنون شما مشخص میکنید که میخواهید گیاه همراه با نشانگر هنگام حرکت کشیده شود و حرکت کشیدن زمانی که گیاه را از انتخاب خارج میکنید متوقف شود. onpointermove
و onpointerup
همه بخشی از همان API هستند که onpointerdown
است. رابط کاربری اکنون خطاهایی ایجاد میکند زیرا هنوز توابع elementDrag
و stopElementDrag
را تعریف نکردهاید، بنابراین آنها را در مرحله بعد بسازید.
توابع elementDrag و stopElementDrag
شما Closure خود را با افزودن دو تابع داخلی دیگر که مدیریت میکنند چه اتفاقی میافتد وقتی یک گیاه را میکشید و کشیدن آن را متوقف میکنید، کامل خواهید کرد. رفتاری که میخواهید این است که بتوانید هر گیاه را در هر زمان بکشید و آن را در هر جایی از صفحه قرار دهید. این رابط کاربری کاملاً بدون محدودیت است (به عنوان مثال، هیچ منطقه رهاسازی وجود ندارد) تا به شما اجازه دهد تراریوم خود را دقیقاً همانطور که دوست دارید طراحی کنید، با افزودن، حذف و جابجایی گیاهان.
وظیفه
تابع 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';
}
در این تابع، شما بسیاری از موقعیتهای اولیه ۱-۴ را که به عنوان متغیرهای محلی در تابع خارجی تنظیم کردهاید، ویرایش میکنید. چه اتفاقی در اینجا میافتد؟
هنگام کشیدن، 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 تنظیم نکنید؟
اکنون پروژه خود را تکمیل کردهاید!
🥇تبریک میگوییم! شما تراریوم زیبای خود را به پایان رساندید.
🚀چالش
یک رویداد جدید به Closure خود اضافه کنید تا کاری بیشتر با گیاهان انجام دهید؛ به عنوان مثال، با دوبار کلیک روی یک گیاه، آن را به جلو بیاورید. خلاق باشید!
آزمون پس از درس
مرور و مطالعه شخصی
در حالی که کشیدن عناصر در صفحه به نظر ساده میرسد، روشهای زیادی برای انجام این کار و مشکلات زیادی وجود دارد که بسته به اثری که میخواهید ایجاد کنید، ممکن است پیش بیاید. در واقع، یک API کشیدن و رها کردن کامل وجود دارد که میتوانید امتحان کنید. ما از آن در این ماژول استفاده نکردیم زیرا اثری که میخواستیم کمی متفاوت بود، اما این API را در پروژه خود امتحان کنید و ببینید چه چیزی میتوانید به دست آورید.
اطلاعات بیشتری درباره رویدادهای pointer در مستندات W3C و مستندات وب MDN پیدا کنید.
همیشه قابلیتهای مرورگر را با استفاده از CanIUse.com بررسی کنید.
تکلیف
سلب مسئولیت:
این سند با استفاده از سرویس ترجمه هوش مصنوعی Co-op Translator ترجمه شده است. در حالی که ما تلاش میکنیم دقت را حفظ کنیم، لطفاً توجه داشته باشید که ترجمههای خودکار ممکن است حاوی خطاها یا نادرستیهایی باشند. سند اصلی به زبان اصلی آن باید به عنوان منبع معتبر در نظر گرفته شود. برای اطلاعات حساس، ترجمه حرفهای انسانی توصیه میشود. ما هیچ مسئولیتی در قبال سوءتفاهمها یا تفسیرهای نادرست ناشی از استفاده از این ترجمه نداریم.