یکی از اولین آموزشهای من در اینجا در Tuts+ در سال 2015، ایجاد یک منوی کشویی خارج از بوم را با نسخه جی کوئری پوشش داد. mmenu.js. اگر بررسی کنید پروژه نمایشی، متوجه خواهید شد که منو شامل چندین سطح است.
آماده برای یک چالش دیگر؟!
1. علامت گذاری HTML را تعریف کنید
نشانه گذاری برای منوی تلفن همراه ما شامل عناصر زیر است:
- آ
header
با یکnav
درون آن. - آ
main
جایی که محتوای اصلی صفحه ما زندگی می کند.
درون nav
، ما دو قرار می دهیم div
س اولین مورد را خواهد داشت header-bar
کلاس، در حالی که دومی خواهد داشت menu-wrapper
یکی
را .header-bar
از سه عنصر تشکیل خواهد شد:
- منوی جابجایی
- لوگوی شرکت
- حساب توییتر این شرکت
درون .menu-wrapper
، سه قرار می دهیم div
s با کلاس از list-wrapper
. ما با آنها تماس خواهیم گرفت پانل ها برای سادگی. موارد قابل توجه:
- در پنل اول، ساختار منو با آیتم های منوی والد و فرزند را مشخص می کنیم. برای انجام این کار، از یک نشانه گذاری معمولی با لیست های نامرتب تو در تو استفاده می کنیم.
- پانل های دوم و سوم حاوی الف خواهند بود بازگشت دکمه و یک خالی
div
با کلاس ازsub-menu-wrapper
. اطلاعات بیشتر در مورد شغل آنها:- دکمه برگشت به ما کمک می کند تا یک سطح بالاتر برویم. گفته شد، از سطح سه به سطح دو و از سطح دو به سطح یک.
- را
.sub-menu-wrapper
پانل دوم حاوی پیوندهای سطح دوم خواهد بود. به همین ترتیب،.sub-menu-wrapper
پانل سوم پیوندهای سطح سوم را نگه می دارد. ما این پیوندها را به صورت پویا از طریق جاوا اسکریپت درج خواهیم کرد.
با در نظر گرفتن تمام موارد فوق، نشانه گذاری زیر ظاهر می شود:
<header class="page-header"> <nav> <div class="header-bar"> <button class="toggle-menu" type="button"> MENU </button> <a href="" class="brand">BRAND</a> <a href="" class="social" target="_blank" title=""> <svg xmlns=" width="24" height="24" viewBox="0 0 24 24"> <path d="M24 4.557c-.883.392-1.832.656-2.828.775 1.017-.609 1.798-1.574 2.165-2.724-.951.564-2.005.974-3.127 1.195-.897-.957-2.178-1.555-3.594-1.555-3.179 0-5.515 2.966-4.797 6.045-4.091-.205-7.719-2.165-10.148-5.144-1.29 2.213-.669 5.108 1.523 6.574-.806-.026-1.566-.247-2.229-.616-.054 2.281 1.581 4.415 3.949 4.89-.693.188-1.452.232-2.224.084.626 1.956 2.444 3.379 4.6 3.419-2.07 1.623-4.678 2.348-7.29 2.04 2.179 1.397 4.768 2.212 7.548 2.212 9.142 0 14.307-7.721 13.995-14.646.962-.695 1.797-1.562 2.457-2.549z" /> </svg> </a> </div> <div class="menu-wrapper"> <div class="list-wrapper"> <ul class="menu level-1"> <li> <a href="" class="nested">Categories </a> <ul class="sub-menu level-2"> <li> <a href="" class="nested">Living Room </a> <ul class="sub-menu level-3">...</ul> </li> <li> <a href="">Dining Room</a> </li> ... </ul> </li> <li> <a href="" class="nested">Featured Products</a> <ul class="sub-menu level-2">...</ul> </li> ... </ul> </div> <div class="list-wrapper"> <button type="button" class="back-one-level"> <svg xmlns=" width="20" height="20" viewBox="0 0 24 24"> <path d="M16.67 0l2.83 2.829-9.339 9.175 9.339 9.167-2.83 2.829-12.17-11.996z" /> </svg> <span>Back</span> </button> <div class="sub-menu-wrapper"></div> </div> <div class="list-wrapper"> <button type="button" class="back-one-level"> <svg xmlns=" width="20" height="20" viewBox="0 0 24 24"> <path d="M16.67 0l2.83 2.829-9.339 9.175 9.339 9.167-2.83 2.829-12.17-11.996z" /> </svg> <span>Back</span> </button> <div class="sub-menu-wrapper"></div> </div> </div> </nav> </header> <main class="page-main">...</main>
2. سبک های اصلی را مشخص کنید
بیایید اکنون روی سبکهای هدر منوی موبایل خود تمرکز کنیم.
برای سادگی، تمام سبکها را بررسی نمیکنم، اما با کلیک بر روی زبانه CSS از آنها راحت باشید. پروژه نمایشی.
چند نکته قابل توجه:
- هدر یک عنصر با موقعیت ثابت خواهد بود و حداکثر عرض آن 600 پیکسل است.
- را
.header-bar
ارتفاع ثابت 60 پیکسل خواهد داشت. - را
.menu-wrapper
کاملاً در موقعیت قرار می گیرد و زیر آن می نشیند.header-bar
. علاوه بر این، ارتفاعی برابر با ارتفاع دید منهای خواهد داشت.header-bar
قد. در نهایت، در ابتدا پنهان خواهد شد.
سبک های مرتبط:
/*CUSTOM VARIABLES HERE*/ .page-header { position: fixed; top: 0; left: 50%; transform: translateX(-50%); width: 100%; max-width: 600px; margin: 0 auto; color: var(--white); } .page-header .header-bar { display: flex; justify-content: space-between; align-items: center; height: 60px; padding: 0 20px; background: var(--header-bar-bg); } .page-header .menu-wrapper { display: none; position: absolute; top: 60px; left: 0; width: 100%; height: calc(100vh - 60px); overflow: hidden; }
بیایید به سبک های پانل ادامه دهیم.
- همه آنها قد والدین خود را دریافت می کنند و دارند
overflow-y: auto
. این ویژگی تضمین میکند که در صورت وجود تعداد زیادی لینک منو، یک نوار اسکرول ظاهر میشود. - به خصوص پانل های دوم و سوم به طور پیش فرض کاملاً خارج از صفحه نمایش قرار می گیرند.
سبک های مرتبط:
/*CUSTOM VARIABLES HERE*/ .page-header .list-wrapper { height: 100%; padding: 30px 20px; overflow-y: auto; background: var(--menu-bg); } .page-header .list-wrapper:nth-child(2), .page-header .list-wrapper:nth-child(3) { position: absolute; top: 0; left: 0; right: 0; transform: translateX(100%); backface-visibility: hidden; transition: transform 0.5s; }
در مرحله بعد، همه منوهای تودرتو را از پنل اول و سوم پنهان می کنیم:
.page-header .list-wrapper:nth-child(1) > ul > li > .sub-menu, .page-header .list-wrapper:nth-child(2) .level-3 { display: none; }
در ادامه، چند سبک را برای پیوندهای منوی تلفن همراه تنظیم می کنیم، به ویژه:
- پیوندهایی که منوی تودرتو را باز می کنند، زیر خط کشیده می شوند.
- برای نشان دادن فعال بودن یا شناور بودن یک پیوند، رنگ و کاراکتر دیگری به آن می دهیم.
سبک های مرتبط:
/*CUSTOM VARIABLES HERE*/ .page-header .menu-wrapper a { display: inline-block; position: relative; padding: 5px 0; } .page-header .menu-wrapper a.nested { text-decoration: underline; } .page-header .menu-wrapper a:hover, .page-header .menu-wrapper a.is-active { color: var(--orange); } .page-header .menu-wrapper a:hover::before, .page-header .menu-wrapper a.is-active::before { content: "✦"; position: absolute; top: 50%; right: -20px; transform: translateY(-50%); color: var(--orange); } .page-header .back-one-level { display: flex; align-items: center; margin-bottom: 40px; }
در نهایت، چند سبک ساده برای دکمه برگشت مشخص می کنیم.
اینجا اند:
/*CUSTOM VARIABLES HERE*/ .page-header .back-one-level { display: flex; align-items: center; margin-bottom: 40px; } .page-header .back-one-level svg { fill: var(--white); margin-right: 10px; }
3. جاوا اسکریپت را اضافه کنید
پس از تنظیم سبک ها، زمان آن است که اقدامات مورد نیاز برای آشکار کردن سطوح منوی تودرتو با انیمیشن اسلاید را مورد بحث قرار دهیم.
تغییر منو
در اینجا یک GIF متحرک است که وضعیت تغییر منو را نشان می دهد:
هر بار که روی دکمه جابجایی کلیک می کنیم، اقدامات زیر را انجام خواهیم داد:
- قابلیت مشاهده منو را از طریق تغییر دهید
is-visible
کلاس اگر پنهان باشد ظاهر می شود و بالعکس. - بررسی کنید که آیا منو بسته است. اگر این شرط برآورده شود، موارد زیر را انجام می دهیم:
- را حذف کنید
is-visible
کلاس از پنل دوم و سوم اگر دارند. - را حذف کنید
is-active
کلاس از پیوندهای منوی فعال در صورت وجود چنین عناصری.
- را حذف کنید
در اینجا کد جاوا اسکریپت مورد نیاز است:
const pageHeader = document.querySelector(".page-header"); const toggleMenu = pageHeader.querySelector(".toggle-menu"); const menuWrapper = pageHeader.querySelector(".menu-wrapper"); const listWrapper2 = pageHeader.querySelector(".list-wrapper:nth-child(2)"); const listWrapper3 = pageHeader.querySelector(".list-wrapper:nth-child(3)"); const isVisibleClass = "is-visible"; const isActiveClass = "is-active"; toggleMenu.addEventListener("click", function () { // 1 menuWrapper.classList.toggle(isVisibleClass); // 2 if (!this.classList.contains(isVisibleClass)) { // 1 listWrapper2.classList.remove(isVisibleClass); listWrapper3.classList.remove(isVisibleClass); // 2 const menuLinks = menuWrapper.querySelectorAll(".is-active"); for (const menuLink of menuLinks) { menuLink.classList.remove(isActiveClass); } } });
و سبک های مربوطه:
.page-header .menu-wrapper.is-visible { display: block; } .page-header .list-wrapper:nth-child(2), .page-header .list-wrapper:nth-child(3) { transition: transform 0.5s; } .page-header .list-wrapper:nth-child(2).is-visible, .page-header .list-wrapper:nth-child(3).is-visible { transform: none; }
سطح دو را باز کنید
در اینجا یک GIF متحرک است که نحوه نمایش منوهای سطح دوم را نشان می دهد:
هر بار که روی پیوند منوی سطح اول (آنهایی که قابل مشاهده هستند) کلیک می کنیم، بررسی می کنیم که آیا منوی تودرتو به عنوان خواهر و برادر دارد یا خیر. اگر این شرط برآورده شود، اقدامات زیر را انجام خواهیم داد:
- از عملکرد پیش فرض آن جلوگیری کنید.
- اختصاص دهید
is-active
کلاس به آن - یک کپی عمیق از خواهر و برادرش ایجاد کنید.
- این گره جدید ایجاد شده را به آن اضافه کنید
.sub-menu-wrapper
از پنل دوم - پانل دوم را با انیمیشن اسلاید نشان دهید.
در اینجا کد جاوا اسکریپت مورد نیاز است:
... for (const level1Link of level1Links) { level1Link.addEventListener("click", function (e) { const siblingList = level1Link.nextElementSibling; if (siblingList) { // 1 e.preventDefault(); // 2 this.classList.add(isActiveClass); // 3 const cloneSiblingList = siblingList.cloneNode(true); // 4 subMenuWrapper2.innerHTML = ""; subMenuWrapper2.append(cloneSiblingList); // 5 listWrapper2.classList.add(isVisibleClass); } }); }
و سبک های مرتبط با انیمیشن:
.page-header .list-wrapper:nth-child(2) { transition: transform 0.5s; } .page-header .list-wrapper:nth-child(2).is-visible { transform: none; }
سطح سه را باز کنید
در اینجا یک GIF متحرک وجود دارد که نحوه نمایش منوهای سطح سوم را نشان می دهد:
به یاد داشته باشید که به طور پیش فرض، هیچ محتوایی در داخل وجود ندارد .sub-menu-wrapper
از پنل دوم در قسمت قبل نحوه دریافت محتوا را با اجرای کپی عمیق توضیح دادیم.
در اینجا نمونه ای از نشانه گذاری ایجاد شده است:
در مرحله بعد، به محض اینکه شخصی روی پیوند منوی سطح دوم کلیک کرد، باید برخی از اقدامات را انجام دهیم. با این حال، مشکل این است که این پیوندها به صورت پویا تولید می شوند و بخشی از DOM اولیه نیستند. که گفت، click
رویداد برای این عناصر کار نمی کند:(
خوشبختانه راه حل به اندازه کافی ساده است. با تشکر از هیئت رویداد، ما آن را ضمیمه می کنیم click
رویداد به پانل والد که بخشی از DOM است. سپس، از طریق target
ویژگی آن رویداد، ما عناصری را که رویداد روی آنها رخ داده است بررسی میکنیم تا مطمئن شویم که اینها پیوندهایی با یک خواهر و برادر زیر منو هستند.
با فرض اینکه اینها عناصر هدف هستند، برای هر یک از آنها، کارهای زیر را انجام خواهیم داد (مشابه قسمت قبل):
- از عملکرد پیش فرض آن جلوگیری کنید.
- اختصاص دهید
is-active
کلاس به آن - یک کپی (عمیق) از خواهر و برادرش ایجاد کنید. توجه داشته باشید که اگر یک کپی عمیق انجام دهیم یا نه، هیچ تغییری ایجاد نخواهد شد، زیرا منوی ما شامل سه سطح است.
- این گره جدید ایجاد شده را به آن اضافه کنید
.sub-menu-wrapper
از پانل سوم - پانل سوم را با انیمیشن اسلاید نشان دهید.
در اینجا کد جاوا اسکریپت مورد نیاز است:
... listWrapper2.addEventListener("click", function (e) { const target = e.target; if (target.tagName.toLowerCase() === "a" && target.nextElementSibling) { const siblingList = target.nextElementSibling; // 1 e.preventDefault(); // 2 target.classList.add(isActiveClass); // 3 const cloneSiblingList = siblingList.cloneNode(true); // 4 subMenuWrapper3.innerHTML = ""; subMenuWrapper3.append(cloneSiblingList); // 5 listWrapper3.classList.add(isVisibleClass); } });
و سبک های مربوط به انیمیشن:
.page-header .list-wrapper:nth-child(3) { transition: transform 0.5s; } .page-header .list-wrapper:nth-child(3).is-visible { transform: none; }
یک سطح به عقب برگردید
در اینجا یک GIF متحرک است که نحوه عملکرد دکمه های برگشت را نشان می دهد:
هر بار که روی دکمه بازگشت کلیک می کنیم، اقدامات زیر را انجام می دهیم:
- پانل والد دکمه بازگشت هدف را پیدا کنید و آن را بردارید
is-visible
کلاس - را حذف کنید
is-active
کلاس از لینک فعال خواهر یا برادر قبلی پانل مادر.
در اینجا کد جاوا اسکریپت مورد نیاز است:
... for (const backOneLevelBtn of backOneLevelBtns) { backOneLevelBtn.addEventListener("click", function () { // 1 const parent = this.closest(".list-wrapper"); parent.classList.remove(isVisibleClass); // 2 parent.previousElementSibling .querySelector(".is-active") .classList.remove(isActiveClass); }); }
و دوباره، چند سبک CSS همراه برای این عمل وجود دارد.
اضافی: متن برگشتی را به روز کنید
این آموزش با درخواست یکی از خوانندگان ما با یک مرحله اضافی به روز شده است (ایده عالی!)
اکنون که منوی موبایل ما به روشی که میخواهیم کار میکند، میتوانیم انواع مختلفی از آن بسازیم. برای مثال، بیایید فرض کنیم که میخواهیم کدهای سخت را جایگزین کنیم بازگشت متن با متن از پیوند منو که روی آن کلیک می شود.
برای سازگاری، HTML خود را همانطور که هست حفظ می کنیم و فقط کمی جاوا اسکریپت اضافه می کنیم (با علامت گذاری شده در اضافی نظر) به این صورت:
... // EXTRA const backLabel2 = listWrapper2.querySelector(".back-one-level span"); const backLabel3 = listWrapper3.querySelector(".back-one-level span"); toggleMenu.addEventListener("click", function () { menuWrapper.classList.toggle(isVisibleClass); if (!this.classList.contains(isVisibleClass)) { // EXTRA OPTIONALLY backLabel2.textContent = ""; backLabel3.textContent = ""; } }); for (const level1Link of level1Links) { level1Link.addEventListener("click", function (e) { const siblingList = level1Link.nextElementSibling; if (siblingList) { // EXTRA backLabel2.textContent = level1Link.textContent; } }); } listWrapper2.addEventListener("click", function (e) { const target = e.target; if (target.tagName.toLowerCase() === "a" && target.nextElementSibling) { ... // EXTRA backLabel3.textContent = target.textContent; } }); ...
نتیجه
در این آموزش، با بهره گیری از برخی سبک های رایج CSS و API جاوا اسکریپت DOM، یک منوی تلفن همراه چند سطحی ایجاد کردیم. مهمتر از همه، نشانه گذاری اولیه ما شامل لیست های تو در تو ساده برای ایجاد منو است. این بدان معناست که ما میتوانیم آن را به یک سیستم پویا تبدیل کنیم و از ویژگیهای یک CMS مانند وردپرس فقط با چند تغییر استفاده کنیم.
من مطمئن هستم که راه حل های دیگری برای ساخت چنین منوهایی وجود خواهد داشت، شاید موثرتر یا در دسترس تر از این راه حل. رویکرد ما به شدت از مزایای استفاده می کند cloneNode()
روشی که گره های تکراری ایجاد می کند، بنابراین باید با احتیاط از آن استفاده کرد، به خصوص اگر زیر منوهای ما دارای شناسه باشند.
در اینجا یک یادآوری از تمرین امروز ما (این است پویا نمونه پشت متن):
اگر این تمرین را دوست داشتید و در زمان شما برای ساختن چنین راه حلی از ابتدا صرفه جویی کرد، فراموش نکنید که به آن ❤️ بدهید. به علاوه، اگر میخواهید نسخه دیگری یا افزونهای از این منو را ببینید، به ما خبر دهید!
مثل همیشه، خیلی ممنون که خواندید!