|
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
اختصاص داده میشود، که بخشی از وب APIها است که برای کمک به مدیریت 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();
از وقوع رویدادهای پیشفرضی که معمولاً در pointerdown رخ میدهند، جلوگیری میکنید. به این ترتیب کنترل بیشتری بر رفتار رابط کاربری دارید.
وقتی فایل اسکریپت را به طور کامل ساختید، به این خط برگردید و آن را بدون
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 را روی پروژه خود امتحان کنید و ببینید چه چیزی میتوانید به دست آورید.
اطلاعات بیشتری درباره رویدادهای اشارهگر در مستندات W3C و مستندات وب MDN پیدا کنید.
همیشه قابلیتهای مرورگر را با استفاده از CanIUse.com بررسی کنید.
تکلیف
سلب مسئولیت:
این سند با استفاده از سرویس ترجمه هوش مصنوعی Co-op Translator ترجمه شده است. در حالی که ما برای دقت تلاش میکنیم، لطفاً توجه داشته باشید که ترجمههای خودکار ممکن است شامل خطاها یا نادرستیهایی باشند. سند اصلی به زبان اصلی آن باید به عنوان منبع معتبر در نظر گرفته شود. برای اطلاعات حساس، ترجمه حرفهای انسانی توصیه میشود. ما هیچ مسئولیتی در قبال سوءتفاهمها یا تفسیرهای نادرست ناشی از استفاده از این ترجمه نداریم.