ساخت مگامنوی چند سطحی واکنشگرا با CSS – از صفر تا صد


در این راهنما با مراحل ساخت مگامنوی چند سطحی واکنشگرا صرفاً با CSS آشنا میشویم. این نوع کامپوننتها معمولاً با کمک گرفتن از جاوا اسکریپت ساخته میشوند، اما ما در این راهنما روشی را به شما نشان میدهیم که این کار صرفاً با CSS انجام مییابد.
شاید از خود بپرسید چرا باید خودمان را محدود به استفاده از CSS بکنیم؟
- در این حالت نیازی به دستکاری DOM وجود ندارد.
- در این حالت محدود به فریمورک نیستیم، یعنی چه از ریاکت، چه انگولار یا حتی از HTML و CSS ساده استفاده کنید، مگامنوی شما عمل خواهد کرد.
- در این حالت عملکرد خوبی داریم، چون دیگر نیازی به بارگذاری و اجرای جاوا اسکریپت وجود ندارد.
محدودیتهای رویکرد صرفاً CSS
این رویکرد که معرفی میکنیم علاوه بر مزایای خود برخی محدودیتها نیز دارد که به شرح زیر هستند.
دسترسپذیری: استفاده از یک markup معتبر و خوشساخت به افزایش دسترسپذیری کمک میکند. اما برای امور زیر به جاوا اسکریپت نیاز داریم:
افزودن پشتیبانی از کیبورد: فعال و غیر فعال کردن خصوصیتهایی از قبیل aria-expanded.
پشتیبانی مرورگر: مرورگرهای مختلف حالتهای :focus را به طرز متفاوتی اعمال میکنند. برای نمونه Safari حالت :focus را در زمان کلیک اعمال نمیکند. همچنین پشتیبانی داخلی :focus محدود است. این بدان معنی است که این راهحل در همه مرورگرها کار نخواهد کرد.
UX: این راهکار صرفاً به این منظور ارائه میشود که نشان دهیم تنها با استفاده از CSS چه کارهایی میتوان انجام داد، اما این به آن معنی نیست که این رویکرد بهترین تجربه کاربری را فراهم میسازد. جاوا اسکریپت گزینههای بسیار بیشتری برای تنظیم تعاملها در اختیار ما قرار میدهد.
راهاندازی
از آنجا که ما در این راهنما صرفاً از CSS (یعنی LESS) و HTML استفاده میکنیم، مورد خاصی وجود ندارد که بخواهیم راهاندازی کنیم. کافی است یک صفحه HTML باز کنید و یک فایل CSS داشته باشید تا کار را آغاز کنید. همچنین میتوانید به وبسایت CodePen بروید و مراحل کار را با LESS پیگیری کنید. در این راهنما فرض کردهایم که شما درک مناسبی از CSS و یا Less دارید. برخی از خصوصیتهای CSS که در این راهنما مورد استفاده قرار خواهیم داد، به شرح زیر هستند:
- حالتهای مختلف از قبیل :focus و :hover و :focus-within
- سلکتورهای همنیای (sibling) CSS
- مشخصه pointer-events
- انیمیشن و گذارهای CSS
- مشخصه transform
Markup
ما قصد داریم مثال خود را ساده ارائه کنیم. بدین ترتیب تنها چیزی که نیاز داریم که لیست تودرتوی مناسب سئو است:
اینک که هیچ نوع استایلبندی روی این لیست اعمال نکردهایم، ظاهر آن به صورت زیر است:
- هدر لینک Home موبایل و لینکهای Back موبایل باید در لیستهای متناظر خود در بخش انتهایی ظاهر شوند. در ادامه با استفاده از flex-order آنها را به صورت بصری در ابتدای لیست قرار میدهیم.
- لینکی که دارای فلش باز کردن مگامنو یا باز شدن شناور است باید خصوصیت aria-haspopup آن به صورت true تعیین شده باشد. همچنین باید از رفتار پیشفرض مرورگر جلوگیری کنیم. این کار از نظر فنی با استفاده از جاوا اسکریپت انجام مییابد، اما میتوان از <button> به جای یک <a> استفاده کرد.
- مقدار زیادی از CSS به برخی معناشناسیهای خاص مرتبط هستند، از این رو مهم است که از Markup صحیحی استفاده کنیم.
پیش از آغاز کار فهرست الزامات منوی مورد نظر را با هم مرور میکنیم:
- مگامنوی چند سطحی با منوهای شناور
- مگامنوهای مسطح با لیآوت ستونی منعطف
- توانایی افزودن محتوای استاتیک
- واکنشگرایی کامل که روی موبایل به صورت منوی لغزشی ثابت ظاهر میشود.
ایجاد منوی دسکتاپ
در این بخش ابتدا با مراحل ساخت منو برای نمایشگرهای دسکتاپ آشنا میشویم.
استایلبندی و لیآوت مقدماتی
ابتدا باید برخی استایلبندی و لیآوتها را به بخشهای اصلی منو از قبیل نوار منو، لینکهای منو و فلش باز کردن مگامنو اضافه کنیم:
به موارد زیر توجه کنید.
- نوار منو یک کانتینر flexbox است و دارای موقعیت relative است.
- فلش باز کردن مگامنو یک کانتینر flexbox است و دارای موقعیتیابی absolute است و از این رو زیر نوار منو نمایش خواهد یافت.
- لینک Dropdown/flyouts باید دارای خصوصیت aria-haspopup=true باشد و لیستهای همنیای آن پنهان خواهند شد.
کارکرد باز کردن و شناوری
برای ایجاد این کارکرد به طور معمول از جاوا اسکریپت استفاده میشود، اما ما از ترکیبی از ظرفیتهای CSS استفاده میکنیم.
فلش باز کردن مگامنو
نخستین کارکردی که به منوی خود اضافه میکنیم، قابلیت باز کردن منوی باز شونده مگامنو است. برای این که مطمئن شویم این کارکرد مستحکم است، باید آن را با استفاده از ترکیبی از رویکردها اجرا کنیم:
- روی focus: مربوط به لینک
- روی focus-within: مربوط به آیتم لیست
- روی hover: مربوط به خود مگامنو. به این ترتیب مطمئن میشویم که در صورتی که لینک focus را از دست بدهد همچنان بازمیماند و همچنین در حالتی که مرورگر از focus-within پشتیبانی نکند باز خواهد ماند.
متأسفانه این کد در حال حاضر چندان جالبی نیست، بنابراین کمی انیمیشن به آن اضافه میکنیم. میدانیم که شبیهسازی انیمیشن لغزش به پایین (slide-down) بسیار دشوار است، مگر این که ارتفاع ثابتی داشته باشید، بنابراین به جای آن از مشخصه scale مربوط به transform استفاده میکنیم. با تنظیم transform-origin روی بخش فوقانی و انیمیت صرفاً روی محور Y میتوانیم یک انیمیشن لغزش به پایین را شبیهسازی کنیم.
نکته خوب این است که استفاده از transform و به خصوص scale برای انیمیشن ازنظر مرورگر هزینه پایینی دارد که به معنی داشتن عملکرد خوب است.
این انیمیشن تنها به حالت :focus روی لینک اضافه میشود، چون بقیه بخشها صرفاً برای باز نگه داشتن مگامنو استفاده میشوند.
در نهایت باید حالتهای active را به لینکهای نوار منوی خود اضافه کنیم. برای این که مطمئن شویم حالتهای فعال زمانی که منو باز است، به صورت چسبان میمانند، از ترکیبی از رویکردها به صورت زیر استفاده کردهایم:
- روی hover: و focus-within: روی آیتم لیست
- روی focus: روی لینک
با این که از حالت hover روی آیتم لیست استفاده میکنیم، اما استایلبندی همچنان روی خود لینک اعمال خواهد شد.
اکنون منوی ما باید چیزی مانند زیر باشد:
Flyout
گام بعدی مربوط به ساختن کارکرد Flyouts در مگامنوی چند سطحی است. در گام پیشین همه همنیاهای لینکها را با Flyout پنهان کردیم.
ابتدا باید لیآوت مورد نیاز را که شامل 3 ستون است اضافه کنیم. مقیاسبندی این مورد ساده است و امکان داشتن تعداد متفاوتی از ستونها را فراهم میسازد، اما به منظور حفظ سادگی مثال از لیآوت سه ستونی استفاده میکنیم.
برای ایجاد این کارکرد جهت باز کردن flyout-های تودرتو از یک رویکرد مشابه به باز شدن مگامنو کمک میگیریم. Flyout-ها در زمان hover باز میشوند، اما ترجیح بر این است که این کار با focus انجام شود.
این کد نیز کمی ناخوشایند به نظر میرسد، بنابراین مقداری انیمیشن به آن اضافه میکنیم. همان مشکل قبلی این بار نیز وجود دارد چون نمیتوانیم عرض را بدون هیچ مقدار ثابت انیمیت کنیم. بنابراین در این جا نیز از transform scale استفاده میکنیم. از آنجا که flyout-ها به صورت جانبی به سمت بیرون میلغزند، ما مبدأ را به سمت چپ جابجا و محور X را انیمیت میکنیم.
توجه کنید که این انیمیت روی :hover مربوط به list-item اعمال میشود و بدین ترتیب مطمئن میشویم که انیمیشن در زمانی که کرسر از flyout باز شده به لینک بازمیگردد، ریاستارت نخواهد شد.
پیش از آن که ظاهر آن را بررسی کنیم، برخی حالتهای active دیگر به آن اضافه میکنیم.
اینک به صورت زیر در آمده است:
استایلبندی مگامنوی مسطح
از آنجا که مگامنوی ما هم اینک یک flexbox است، تنها چیزی که نیاز داریم این است که مطمئن شویم همه فرزندان مگامنوی مسطح ما فضای یکسانی اشغال میکنند. همچنین مقداری استایلبندی به لینکهای هدر اضافه میکنیم:
نتیجه دسکتاپ
بدین ترتیب کار پیادهسازی کارکردهای مگامنوی دسکتاپ ما به پایان میرسد. در این بخش چند استایل دیگر به آن اضافه میکنیم:
- فلشهای رو به پایین برای لینکهای بازشدنی نوار منو
- فلشهای سمت راست برای لینکهای flyout مگا منو
- حاشیههایی بین ستونهای مگامنو
به این ترتیب منوی ما اینک ظاهر زیبا و جذابی یافته است:
ایجاد منوی موبایل
پیش از آغاز ایجاد منوی موبایل، باید کمی پاکسازی انجام دهیم. همه کدهای بخش قبلی را انتخاب کرده و آن را منسجمتر میسازیم. استایلهای خاص دسکتاپ را به درون یک کوئری مدیا میبریم تا مجبور به بازنویسی این استایلها برای نسخه موبایل نباشیم.
نقطه آغاز گام بعدی به صورت زیر است:
استایلبندی و لیآوتهای مقدماتی
کار خود را با راهاندازی منوی موبایل از طریق افزودن برخی استایلها و لیآوتهای مقدماتی به عناصر مختلف آغاز میکنیم. در بخش موبایل، منوی ما به صورت یک منوی با جهتگیری سمت چپ ثابت است که وقتی روی دکمهای میزنیم به سمت بیرون میلغزد. منوهای این مگامنوی چند سطحی به صورت آکاردئون نمایش مییابند.
تنها چیزی که اینک روی صفحه دیده میشود، دکمه باز کردن منوی موبایل است. خود منو خارج از صفحه قرار دارد و مگامنو خارج از منوی موبایل قرار گرفته است. ما در واقع موقعیت چپ منو را انیمیت میکنیم و به این منظور از transform استفاده نکردهایم. به نظر میرسد که در iOS اگر از transform برای آفست کردن منو استفاده کنیم، کلیک کردن روی دکمه باز کردن منوی موبایل موجب ثبت یک کلیک روی لینک Home درون منو نیز میشود.
نکتهای در خصوص focus: روی موبایل
دستگاههای موبایل عملاً از حالتهای :focus پشتیبانی نمیکنند، اما میتوان از hover: برای شبیهسازی همان کارکرد استفاده کرد. این بدان معنی است که نمیتوانیم از کد مربوط به منوی دسکتاپ روی منوی موبایل استفاده کنیم و منوی موبایل صرفاً روی دستگاههای لمسی استفاده خواهد شد. حالت اول یعنی استفاده از منوی دسکتاپ روی دستگاههای موبایل احتمالاً یک مورد استثنایی است، اما برای این که همه حالتها را پوشش دهیم این مورد را نیز پشتیبانی خواهیم کرد.
برای این که صرفاً دستگاههای لمسی را هدفگیری کنیم، میتوانیم از خصوصیت مدیای hover استفاده کنیم که بررسی میکند آیا سازوکار ورودی اصلی کاربر میتواند روی عناصر hover کند یا نه.
باز کردن منوی موبایل
به این منظور منو باید همنیای دکمه منوی موبایل باشد. همانطور که احتمالاً حدس میزنید، ما از hover: برای تحریک تغییر موقعیت منو در زمان ضربه زدن روی دکمه استفاده میکنیم، اما برای این که از دستگاههای غیر لمسی نیز پشتیبانی کنیم از focus: هم استفاده خواهیم کرد.
برای جلوگیری از این که منو به محض آغاز تعامل با آن بسته شود، باید از hover: و focus-within: روی خود منو استفاده کنیم تا همچنان باز بماند.
اکنون کارکرد مقدماتی برای باز کردن منوی موبایل را داریم.
باز کردن مگامنوها
از همین رویکرد برای باز کردن منوهای مگا نیز استفاده میکنیم.
دکمههای بازگشت
این کار یکی از دشوارترین کارها برای اجرا بدون جاوا اسکریپت محسوب میشود. به خاطر داشته باشید که تنها روش باز کردن منوهای ما استفاده از :focus یا :hover روی یک همنیا یا یک والد منو است. دکمههای بازگشت بخشی از خود منو هستند و به جهت طرز کار CSS تنها میتوانیم والد دکمه بازگشت را برای تغییر موقعیت منو هدفگیری کنیم.
بنابراین چطور میتوانیم مطمئن باشیم که منوی شامل دکمه بازگشت که کلیک شده است، focus خود را از دست میدهد؟
ابتدا باید دکمه بازگشت را از خود لیست حذف کنیم. به این منظور باید یک ارتفاع ثابت روی دکمه تعیین کنیم و سپس از همان مقدار آفست منفی به صورت عمودی استفاده کنیم و مگامنو را به مقدار مثبت آفست کنیم. همچنین مقداری استایلبندی مقدماتی به دکمه بازگشت اضافه میکنیم.
اکنون ظاهر آن همانند قبل است، اما دکمه بازگشت در عمل خارج از خود لیست قرارگرفته است. توجه کنید که کد زیر از همه کلیکها و حالتهای روی یک عنصر جلوگیری میکند:
pointer-events: none;
این بدان معنی است که به طور نظری زمانی که روی دکمه بازگشت کلیک کنیم، در عمل روی آنچه زیر آن است کلیک کردهایم که در این مورد منوی قبلی است. همچنین به آن معنی است که وقتی کلیک میکنیم، مگامنوی باز شده فوکوس خود را از دست میدهد.
این امر موجب میشود که مشکل دیگری پدید آید. از آنجا که ما روی آنچه زیر دکمه قرار دارد کلیک میکنیم در عمل روی لینک Home در سطح نخست منو کلیک کردهایم که موجب آغاز رفتار پیشفرض آن لینک میشود و ما را به یک صفحه میبرد.
برای جلوگیری از این رفتار، باید نمایانی لینک Home را در زمانی که مگامنو باز است، پنهان کنیم در زمانی که روی دکمه بازگشت کلیک میکنیم، لینک پنهان است، اما به محض این که منو باز میشود، لینک دوباره نمایان میشود.
باز کردن Flyout-ها
در این بخش کارکرد باز کردن Flyout-های بعدی را ایجاد میکنیم. ابتدا مقداری استایلبندی اضافه میکنیم:
با استفاده از ترکیبی از گذارها و انیمیشنها، یک کارکرد آکاردئون-مانند تودرتو اضافه خواهیم کرد. در ادامه این کارکرد را پیش از ادامه اضافه میکنیم:
از انیمیشن dropdown که در مورد مگامنوی دسکتاپ استفاده کردیم در این جا نیز استفاده مجدد میکنیم. مشکلی که قبلاً در زمان تلاش برای استفاده از transform برای باز کردن منوی موبایل داشتیم و با کلیک روی دکمه موجب آغاز کلیک روی لینک درون منو میشدیم، در این جا نیز مطرح است. اگر صرفاً مجبور به استفاده از transform باشیم، برای نمونه زمانی که روی باز شدن آیتم دوم کلیک کنیم، در عمل موجب کلیک روی آیتم سوم میشود.
در این جا باید از max-height استفاده کنیم. این حالت شبیه نوعی هک است، اما کار میکند. از آنجا که از یک max-height به عنوان گذار استفاده میکنیم، یک تأخیر اندک در حدود 0.1 ثانیه برای بستن آکاردئون باز قبلی ظاهر میشود. بدین ترتیب از کلیک کردن روی چند آیتم باز جلوگیری میکنیم.
نتیجه نهایی
بدین ترتیب کار ما به پایان رسیده است و موفق شدهایم یک مگامنوی واکنشگرای چند سطحی صرفاً با CSS بسازیم.
سخن پایانی
کد کامل این پروژه را میتوانید در این صفحه (+) مشاهده کنید. CSS بسیار قدرتمند است و در اغلب موارد این قدرت آن دست کم گرفته میشود. ما به طور سنتی عادت کردهایم که برای پیادهسازی کارکردها و تعاملپذیری از جاوا اسکریپت استفاده کنیم، اما این پروژه نشان داد که اگر خلاقیت به خرج بدهیم، ظرفیتهای CSS میتوانند کاملاً ما را شگفتزده کنند.
اما باید توجه داشته باشید که ساخت نسخه صرفاً CSS این مگامنو صرفاً یک آزمون است تا ببینیم بدون جاوا اسکریپت چه کارهایی میتوانیم انجام دهیم. برای ایجاد کامپوننت آماده پروداکشن از این مگامنو باید مقداری جاوا اسکریپت اضافه کنیم تا مطمئن شویم که این کارکرد روی همه مرورگرها ارائه میشود و تجربه روانتری برای کاربر رقم بزنیم و برخی از مشکلات ناشی از رویکرد صرفاً CSS را حذف کنیم.
اگر این مطلب برای شما مفید بوده است، آموزشهای زیر نیز به شما پیشنهاد میشوند:
- مجموعه آموزشهای طراحی سایت با HTML و CSS
- مجموعه آموزشهای برنامهنویسی
- آموزش مقدماتی پیش پردازنده Less و توسعه پذیری CSS
- مفاهیم مقدماتی CSS — آموزش CSS (بخش اول)
- شیوه سازماندهی CSS — آموزش CSS (بخش سوم)
==
سلام ،ممنون از اموزش کاربردیتون.
من میخواهم هر موقع زیر منویی را انتخاب میکنم ارتفاع ul قبلی به اندازه این زیر منو تغییر کند . به عنوان مثال من لینک 1.1 را هاور کردم و زیر منوهای ان که 10 تا هستتد نمایش داده شده میخواهم ارتفاع ul اولی که لینک 1.1 در ان است هم به اندازه همان 10 تا تغییر کند.
چطور این را میتوانم انجام دهم؟
با تشکر
خیلی ممنون از آموزش کامل شما