This site uses cookies for analytics, personalized content and ads. By continuing to browse this site, you agree to this use.
Author Image Saturday, 26 May 2018

Mobile friendly multi level dropdown menu plugin jQuery


Trong thiết kế website, menu là một thành phần hầu như không thể thiếu. Đặc biệt với sự phát triển mạnh mẽ của công nghệ như hiện nay, việc tạo menu đáp ứng được tính tương thích với mọi thiết bị là vấn đề các webmaster rất quan tâm

Qua khảo sát một vài trang cung cấp template blogger miễn phí nổi tiếng như templatesyard, sora templates,... tôi thấy họ làm rất tỉ mỉ, trau chuốt menu cho PC, tuy nhiên đến phần menu cho mobile thì làm rất qua loa chỉ sử dụng plugin selectnav (chuyển menu về thẻ select) để responsive, nên khi chuyển sang giao diện mobile làm trang trở nên rất thô


Sau



Hiện nay trên mạng không thiếu cách tạo menu có khả năng responsive, và trong bài viết này tôi sẽ giới thiệu cho bạn một plugin giúp menu có thể hiển thị tốt trên mobile

Trước tiên bạn xem demo, thay đổi kích thước trang demo để kiểm tra responsive

 DEMO

Trong bài viết này, menu sử dụng font awesome 4, nếu bạn dùng phiên bản 5 thì phải cập nhật lại code file js

Để tích hợp được menu này bạn chỉ cần làm như sau

Tích hợp jQuery nếu chưa có

<script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js' type='text/javascript'/>

CSS

.stellarnav,.stellarnav li{position:relative;line-height:normal}
.stellarnav{width:100%;z-index:9900}
.stellarnav ul{margin:0;padding:0;text-align:center}
.stellarnav li{list-style:none;display:block;margin:0;padding:0;vertical-align:middle}
.stellarnav li a{padding:15px;display:block;text-decoration:none;color:#777;font-size:inherit;font-family:inherit;box-sizing:border-box;-webkit-transition:all .3s ease-out;-moz-transition:all .3s ease-out;transition:all .3s ease-out}
.stellarnav>ul>li{display:inline-block}
.stellarnav>ul>li>a{padding:20px 40px}
.stellarnav ul ul{top:auto;width:220px;position:absolute;z-index:9900;text-align:left;display:none;background:#ddd}
.stellarnav li li{display:block}
.stellarnav ul ul ul{top:0;left:220px}
.stellarnav>ul>li:hover>ul>li:hover>ul{opacity:1;visibility:visible;top:0}
.stellarnav>ul>li.drop-left>ul{right:0}
.stellarnav li.drop-left ul ul{left:auto;right:220px}
.stellarnav.light,.stellarnav.light ul ul{background:rgba(255,255,255,1)}
.stellarnav.light li a{color:#000}
.stellarnav.light .call-btn-mobile,.stellarnav.light .close-menu,.stellarnav.light .location-btn-mobile,.stellarnav.light .menu-toggle{color:rgba(0,0,0,1)}
.stellarnav.dark,.stellarnav.dark ul ul{background:rgba(0,0,0,1)}
.stellarnav.dark li a{color:#FFF}
.stellarnav.dark .call-btn-mobile,.stellarnav.dark .close-menu,.stellarnav.dark .location-btn-mobile,.stellarnav.dark .menu-toggle{color:rgba(255,255,255,1)}
.stellarnav.fixed{position:fixed;width:100%;top:0;left:0;z-index:9999}
body.stellarnav-noscroll-x{overflow-x:hidden}
.stellarnav li.has-sub>a:after{content:'\f107';font-family:FontAwesome;margin-left:10px}
.stellarnav li li.has-sub>a:after{content:'\f105';font-family:FontAwesome;margin-left:10px}
.stellarnav li.drop-left li.has-sub>a:after{float:left;content:'\f104';font-family:FontAwesome;margin-right:10px}
.stellarnav.hide-arrows li li.has-sub>a:after,.stellarnav.hide-arrows li.drop-left li.has-sub>a:after,.stellarnav.hide-arrows li.has-sub>a:after{display:none}
.stellarnav .dd-toggle{display:none;position:absolute;top:0;right:0;padding:0;width:48px;height:48px;text-align:center;z-index:9999;border:0}
.stellarnav .dd-toggle i{position:absolute;margin:auto;top:33%;left:0;right:0;-webkit-transition:transform .3s ease-out;-moz-transition:transform .3s ease-out;transition:transform .3s ease-out}
.stellarnav.mobile>ul>li>a.dd-toggle{padding:0}
.stellarnav li.call-btn-mobile,.stellarnav li.location-btn-mobile{display:none}
.stellarnav li.open>a.dd-toggle i{-webkit-transform:rotate(135deg);-ms-transform:rotate(135deg);-o-transform:rotate(135deg);transform:rotate(135deg)}
.stellarnav .call-btn-mobile,.stellarnav .close-menu,.stellarnav .location-btn-mobile,.stellarnav .menu-toggle{display:none;text-transform:uppercase;text-decoration:none;color:#777;padding:15px;box-sizing:border-box}
.stellarnav .full{width:100%}
.stellarnav .half{width:50%}
.stellarnav .third{width:33%;text-align:center}
.stellarnav .location-btn-mobile.third{text-align:center}
.stellarnav .location-btn-mobile.half{text-align:right}
.stellarnav.light .half,.stellarnav.light .third{border-left:1px solid rgba(0,0,0,.15)}
.stellarnav.light.left .half,.stellarnav.light.left .third,.stellarnav.light.right .half,.stellarnav.light.right .third{border-bottom:1px solid rgba(0,0,0,.15)}
.stellarnav.light .half:first-child,.stellarnav.light .third:first-child{border-left:0}
.stellarnav.dark .half,.stellarnav.dark .third{border-left:1px solid rgba(255,255,255,.15)}
.stellarnav.dark.left .half,.stellarnav.dark.left .third,.stellarnav.dark.right .half,.stellarnav.dark.right .third{border-bottom:1px solid rgba(255,255,255,.15)}
.stellarnav.dark.left .menu-toggle,.stellarnav.dark.right .menu-toggle,.stellarnav.light.left .menu-toggle,.stellarnav.light.right .menu-toggle{border-bottom:0}
.stellarnav.dark .half:first-child,.stellarnav.dark .third:first-child{border-left:0}
.stellarnav.mobile,.stellarnav.mobile.fixed{position:static}
.stellarnav.mobile ul{position:relative;display:none;text-align:left;background:rgba(221,221,221,1)}
.stellarnav.mobile.active>ul,.stellarnav.mobile>ul>li{display:block}
.stellarnav.mobile.active{padding-bottom:0}
.stellarnav.mobile>ul>li>a{padding:15px}
.stellarnav.mobile ul ul{position:relative;opacity:1;visibility:visible;width:auto;display:none;-moz-transition:none;-webkit-transition:none;-o-transition:color 0 ease-in;transition:none}
.stellarnav.mobile ul ul ul{left:auto;top:auto}
.stellarnav.mobile li.drop-left ul ul{right:auto}
.stellarnav.mobile li a{border-bottom:1px solid rgba(255,255,255,.15)}
.stellarnav.mobile li.has-sub a{padding-right:50px}
.stellarnav.mobile>ul{border-top:1px solid rgba(255,255,255,.15)}
.stellarnav.mobile.light li a{border-bottom:1px solid rgba(0,0,0,.15)}
.stellarnav.mobile.light>ul{border-top:1px solid rgba(0,0,0,.15)}
.stellarnav.mobile li a.dd-toggle,.stellarnav.mobile.light li a.dd-toggle{border:0}
.stellarnav.mobile .call-btn-mobile,.stellarnav.mobile .close-menu,.stellarnav.mobile .dd-toggle,.stellarnav.mobile .location-btn-mobile,.stellarnav.mobile .menu-toggle{display:inline-block}
.stellarnav.mobile li.call-btn-mobile{border-right:1px solid rgba(255,255,255,.1);box-sizing:border-box}
.stellarnav.mobile li.call-btn-mobile,.stellarnav.mobile li.location-btn-mobile{display:inline-block;width:50%;text-transform:uppercase;text-align:center}
.stellarnav.mobile li.call-btn-mobile.full,.stellarnav.mobile li.location-btn-mobile.full{display:block;width:100%;text-transform:uppercase;border-right:0;text-align:left}
.stellarnav.mobile li.call-btn-mobile i,.stellarnav.mobile li.location-btn-mobile i{margin-right:5px}
.stellarnav.mobile.light ul{background:rgba(255,255,255,1)}
.stellarnav.mobile.dark ul{background:rgba(0,0,0,1)}
.stellarnav.mobile.dark ul ul{background:rgba(255,255,255,.08)}
.stellarnav.mobile.light li.call-btn-mobile{border-right:1px solid rgba(0,0,0,.1)}
.stellarnav.mobile.top{position:absolute;width:100%;top:0;left:0;z-index:9999}
.stellarnav.mobile li li.has-sub>a:after,.stellarnav.mobile li.drop-left li.has-sub>a:after,.stellarnav.mobile li.has-sub>a:after{display:none}
.stellarnav.mobile.left>ul,.stellarnav.mobile.right>ul{position:fixed;top:0;bottom:0;width:100%;max-width:280px;overflow-x:hidden;overflow-y:auto;-webkit-overflow-scrolling:touch}
.stellarnav.mobile.right>ul{right:0}
.stellarnav.mobile.left .close-menu,.stellarnav.mobile.right .close-menu{display:inline-block;text-align:right}
.stellarnav.mobile.left>ul{left:0}
.stellarnav.mobile.left .call-btn-mobile.half,.stellarnav.mobile.left .call-btn-mobile.third,.stellarnav.mobile.left .close-menu.half,.stellarnav.mobile.left .close-menu.third,.stellarnav.mobile.left .location-btn-mobile.half,.stellarnav.mobile.left .location-btn-mobile.third,.stellarnav.mobile.right .call-btn-mobile.half,.stellarnav.mobile.right .call-btn-mobile.third,.stellarnav.mobile.right .close-menu.half,.stellarnav.mobile.right .close-menu.third,.stellarnav.mobile.right .location-btn-mobile.half,.stellarnav.mobile.right .location-btn-mobile.third{text-align:center}
.stellarnav.mobile.left .menu-toggle.half,.stellarnav.mobile.left .menu-toggle.third,.stellarnav.mobile.right .menu-toggle.half,.stellarnav.mobile.right .menu-toggle.third{text-align:left}
.stellarnav.mobile.left .close-menu.third span,.stellarnav.mobile.right .close-menu.third span{display:none}
.stellarnav.desktop li.mega li{display:inline-block;vertical-align:top;margin-left:-4px}
.stellarnav.desktop li.mega li li{display:block;margin-left:0}
.stellarnav.desktop li.mega ul ul{width:auto}
.stellarnav.desktop>ul>li.mega{position:inherit}
.stellarnav.desktop>ul>li.mega>ul{width:100%}
.stellarnav.desktop>ul>li.mega>ul li.has-sub ul{display:block;position:relative;left:auto}
.stellarnav.desktop>ul>li.mega>ul>li{padding-bottom:15px;box-sizing:border-box}
.stellarnav.desktop li.mega li li a{padding:5px 15px}
.stellarnav.desktop li.mega li.has-sub a:after{display:none}
.stellarnav.desktop>ul>li.mega>ul>li>a{color:#ff0}
@media only screen and (max-width:768px){.stellarnav{overflow:hidden;display:block}.stellarnav ul{position:relative;display:none}}
@media only screen and (max-width:420px){.stellarnav.mobile .call-btn-mobile.third span,.stellarnav.mobile .location-btn-mobile.third span{display:none}}

Chèn code js trước thẻ đóng </head>

<script type='text/javascript'>//<![CDATA[
!function(n){n.fn.stellarNav=function(i,e,s){nav=n(this),e=n(window).width();var a=n.extend({theme:"plain",breakpoint:768,menuLabel:"Menu",sticky:!1,position:"static",openingSpeed:250,closingDelay:250,showArrows:!0,phoneBtn:"",locationBtn:"",closeBtn:!1,scrollbarFix:!1},i);return this.each(function(){if("light"!=a.theme&&"dark"!=a.theme||nav.addClass(a.theme),a.breakpoint&&(s=a.breakpoint),a.menuLabel&&(menuLabel=a.menuLabel),a.phoneBtn&&a.locationBtn)var i="third";else if(a.phoneBtn||a.locationBtn)i="half";else i="full";if("right"==a.position||"left"==a.position?nav.prepend('<a href="#" class="menu-toggle"><i class="fa fa-bars"></i> '+menuLabel+"</a>"):nav.prepend('<a href="#" class="menu-toggle '+i+'"><i class="fa fa-bars"></i> '+menuLabel+"</a>"),a.phoneBtn&&"right"!=a.position&&"left"!=a.position){var t='<a href="tel:'+a.phoneBtn+'" class="call-btn-mobile '+i+'"><i class="fa fa-phone"></i> <span>Call Us</span></a>';nav.find("a.menu-toggle").after(t)}if(a.locationBtn&&"right"!=a.position&&"left"!=a.position){t='<a href="'+a.locationBtn+'" class="location-btn-mobile '+i+'" target="_blank"><i class="fa fa-map-marker"></i> <span>Location</span></a>';nav.find("a.menu-toggle").after(t)}if(a.sticky&&(navPos=nav.offset().top,e>=s&&n(window).bind("scroll",function(){n(window).scrollTop()>navPos?nav.addClass("fixed"):nav.removeClass("fixed")})),"top"==a.position&&nav.addClass("top"),"left"==a.position||"right"==a.position){var l='<a href="#" class="close-menu '+i+'"><i class="fa fa-close"></i> <span>Close</span></a>',o='<a href="tel:'+a.phoneBtn+'" class="call-btn-mobile '+i+'"><i class="fa fa-phone"></i></a>',d='<a href="'+a.locationBtn+'" class="location-btn-mobile '+i+'" target="_blank"><i class="fa fa-map-marker"></i></a>';nav.find("ul:first").prepend(l),a.locationBtn&&nav.find("ul:first").prepend(d),a.phoneBtn&&nav.find("ul:first").prepend(o)}"right"==a.position&&nav.addClass("right"),"left"==a.position&&nav.addClass("left"),a.showArrows||nav.addClass("hide-arrows"),a.closeBtn&&"right"!=a.position&&"left"!=a.position&&nav.find("ul:first").append('<li><a href="#" class="close-menu"><i class="fa fa-close"></i> Close Menu</a></li>'),a.scrollbarFix&&n("body").addClass("stellarnav-noscroll-x"),n(".menu-toggle").on("click",function(i){i.preventDefault(),"left"==a.position||"right"==a.position?(nav.find("ul:first").stop(!0,!0).fadeToggle(a.openingSpeed),nav.toggleClass("active"),nav.hasClass("active")&&nav.hasClass("mobile")&&n(document).on("click",function(i){nav.hasClass("mobile")&&(n(i.target).closest(nav).length||(nav.find("ul:first").stop(!0,!0).fadeOut(a.openingSpeed),nav.removeClass("active")))})):(nav.find("ul:first").stop(!0,!0).slideToggle(a.openingSpeed),nav.toggleClass("active"))}),n(".close-menu").click(function(){nav.removeClass("active"),"left"==a.position||"right"==a.position?nav.find("ul:first").stop(!0,!0).fadeToggle(a.openingSpeed):nav.find("ul:first").stop(!0,!0).slideUp(a.openingSpeed).toggleClass("active")}),nav.find("li a").each(function(){n(this).next().length>0&&n(this).parent("li").addClass("has-sub").append('<a class="dd-toggle" href="#"><i class="fa fa-plus"></i></a>')}),nav.find("li .dd-toggle").on("click",function(i){i.preventDefault(),n(this).parent("li").children("ul").stop(!0,!0).slideToggle(a.openingSpeed),n(this).parent("li").toggleClass("open")});var f=function(){nav.find("li").unbind("mouseenter"),nav.find("li").unbind("mouseleave")};parentItems=nav.find("> ul > li");var r=function(){n(parentItems).each(function(){n(this).hasClass("mega")?(n(this).on("mouseenter",function(){n(this).find("ul").first().stop(!0,!0).slideDown(a.openingSpeed)}),n(this).on("mouseleave",function(){n(this).find("ul").first().stop(!0,!0).slideUp(a.openingSpeed)})):(n(this).on("mouseenter",function(){n(this).children("ul").stop(!0,!0).slideDown(a.openingSpeed)}),n(this).on("mouseleave",function(){n(this).children("ul").stop(!0,!0).delay(a.closingDelay).slideUp(a.openingSpeed)}),n(this).find("li.has-sub").on("mouseenter",function(){n(this).children("ul").stop(!0,!0).slideDown(a.openingSpeed)}),n(this).find("li.has-sub").on("mouseleave",function(){n(this).children("ul").stop(!0,!0).delay(a.closingDelay).slideUp(a.openingSpeed)}))})};function h(){window.innerWidth<=s?(f(),nav.addClass("mobile"),nav.removeClass("desktop"),!nav.hasClass("active")&&nav.find("ul:first").is(":visible")&&nav.find("ul:first").hide(),nav.find("li.mega").each(function(){n(this).find("ul").first().removeAttr("style"),n(this).find("ul").first().children().removeAttr("style")})):(nav.addClass("desktop"),nav.removeClass("mobile"),nav.hasClass("active")&&nav.removeClass("active"),!nav.hasClass("active")&&nav.find("ul:first").is(":hidden")&&nav.find("ul:first").show(),n("li.open").removeClass("open").find("ul:visible").hide(),f(),r(),navWidth=0,navIniPos=0,n(parentItems).each(function(){navWidth+=n(this)[0].getBoundingClientRect().width,navWidth=Math.round(navWidth),n(this).hasClass("mega")&&(n(this).find("ul").first().css({left:navIniPos}),numCols=n(this).attr("data-columns"),2==numCols?n(this).find("li.has-sub").width("50%"):3==numCols?n(this).find("ul").first().children().width("33.33%"):4==numCols?n(this).find("ul").first().children().width("25%"):5==numCols?n(this).find("ul").first().children().width("20%"):6==numCols?n(this).find("ul").first().children().width("16.66%"):7==numCols?n(this).find("ul").first().children().width("14.28%"):8==numCols?n(this).find("ul").first().children().width("12.5%"):n(this).find("ul").first().children().width("25%"))}),parentItems.hasClass("mega")&&nav.find("li.mega ul").css({"max-width":navWidth}))}h(),n(window).on("resize",function(){h()})})}}(jQuery);
//]]></script>

Cấu trúc HTML cho menu

<div class="stellarnav">
    <ul>
        <li><a href="">Dropdown</a>
            <ul>
                <li><a href="#">How deep?</a>
                    <ul>
                        <li><a href="#">Item</a>
                            <ul>
                                <li><a href="#">Item</a>
                                    <ul>
                                        <li><a href="#">Item</a></li>
                                        <li><a href="#">Item</a></li>
                                        <li><a href="#">Item</a></li>
                                        <li><a href="#">Item</a></li>
                                    </ul>
                                </li>
                                <li><a href="#">Item</a></li>
                                <li><a href="#">Item</a></li>
                                <li><a href="#">Item</a></li>
                            </ul>
                        </li>
                        <li><a href="#">Item</a></li>
                        <li><a href="#">Item</a></li>
                        <li><a href="#">Item</a></li>
                    </ul>
                </li>
                <li><a href="#">Item</a></li>
                <li><a href="#">Item</a></li>
                <li><a href="#">Here's a very long item. It can be as long as you want</a></li>
                <li><a href="#">Item</a></li>
            </ul>
        </li>
        <li><a href="">Item 2</a></li>
        <li><a href="">Item 3</a></li>
        <li><a href="">Item 4</a></li>
        <li><a href="">Item 5</a></li>
        <li><a href="">Item 6</a></li>
    </ul>
</div>

Như bạn thấy, plugin này chỉ có một yêu cầu duy nhất đó là bạn đặt 1 thẻ div bao lấy toàn bộ thanh menu, không có yêu cầu class cho các thẻ ul li (kiểu như sub-menu, dropdown,...). Điều này sẽ rất tiện lợi cho bạn nào sử dụng menu đa cấp lớn muốn chuyển qua menu này

Cuối dùng để bật menu bạn dán đoạn js sau (dán sau đoạn js bên trên)

<script type='text/javascript'>//<![CDATA[
jQuery('.stellarnav').stellarNav({
    theme: 'plain',
    breakpoint: 768,
    menuLabel: 'Menu',
    sticky: false,
    position: 'static',
    openingSpeed: 250,
    closingDelay: 250,
    showArrows: true,
    phoneBtn: '',
    locationBtn: '',
    closeBtn: false,
    scrollbarFix: false
});
//]]></script>

Trong đó bảng config sẽ như sau

Attribute Type Default Description
theme String plain Adds default color to nav. [plain, light, dark]
breakpoint Integer 768 Number in pixels to determine when the nav should turn mobile friendly.
menuLabel String Menu Label (text) for the mobile nav.
sticky Boolean false Makes nav sticky on scroll.
position String static [static, top, left, right] - When set to 'top', this forces the mobile nav to be placed absolutely on the very top of page. When set to 'left' or 'right', mobile nav fades in/out from left or right, accordingly.
openingSpeed Integer 250 Controls how fast the dropdowns open in milliseconds.
closingDelay Integer 250 Controls how long the dropdowns stay open for in milliseconds.
showArrows Boolean true Shows dropdown arrows next to the items that have sub menus.
phoneBtn String [empty] Adds a click-to-call phone link to the top of menu - i.e.: "18009084500".
locationBtn String [empty] Adds a location link to the top of menu - i.e.: "/location/", "http://site.com/contact-us/".
closeBtn Boolean false Adds a close button to the end of nav.
scrollbarFix Boolean false Fixes horizontal scrollbar issue on very long navs.

* Mở rộng

Trong thiết kế menu đa cấp, sub-menu thường trượt từ trái sang phải, nếu vô tình parent-menu nằm ở tận cùng bên phải có thể sub-menu của chúng sẽ bị lỗi hiển thị. Trong plugin này, ta có thể khắc phục được điều đó bằng việc đặt class như sau

<div class="stellarnav">
    <ul>
        <li><a href="#">Item</a></li>
        <li><a href="#">Item</a></li>
        <li><a href="#">Item</a></li>
        <li><a href="#">Item</a></li>
        <li class="drop-left"><a href="#">Last Dropdown Item</a>
            <ul>
                <li><a href="#">Item</a></li>
                <li><a href="#">Item</a>
                    <ul>
                        <li><a href="#">Drop left menu item</a></li>
                        <li><a href="#">Drop left menu item</a></li>
                    </ul>
                </li>
            </ul>
        </li>
    </ul>
</div>

Khi đó, sub-menu của class drop-left khi hover sẽ trượt từ phải sang trái, giải quyết được lỗi hiển thị

* Vấn đề tích hợp

Demo và cò kéo thì có vẻ hay, tuy nhiên vấn đề tích hợp vào blog thực sự không đơn giản do nó còn chịu ảnh hưởng rất nhiều từ css blog. Vì thế điều này hoàn toàn phụ thuộc vào bạn, tôi chỉ có một số lời khuyên như sau
  • Xóa toàn bộ code html, js, css của menu cũ
  • Lựa chọn trước cho mình theme của menu để phù hợp với blog của bạn, vì plugin này cung cấp cho bạn 3 loại plain, light, dark, bạn chọn trước theme sau đó chỉ tập trung vào sửa css của theme đó
  • Để lại bình luận nếu bạn gặp khó khăn

Demo 1 template mà tôi đã tích hợp thành công: https://hunghoangvan1001nd.blogspot.com

KẾT LUẬN: đây có thể coi là một thanh menu hoàn chỉnh (đa cấp, responsive) rất đáng để ta bỏ công ra tích hợp vào website. Khi tôi vào một website nào đó tôi thường rất để ý tới thanh menu của họ và cũng không quên check responsive, vì nếu là một người cẩn thận tỉ mỉ, họ rất ít khi để người khác thấy được sơ hở của mình. Việc phá cách khỏi khuôn khổ mỗi template cung cấp cũng giúp bạn khẳng định được giá trị của mình hơn

Chúc các bạn thành công !
Comments:
Bạn được tự do bày tỏ quan điểm nhưng nghiêm cấm spam
  • Chèn ảnh theo mẫu [img]link[/img]
  • Chèn video Youtube theo mẫu [youtube]link[/youtube]
  • Chèn code theo mẫu [pre]code[/pre]. Lưu ý: mã hóa code trước khi bình luận

  • Please wait while i am loading Facebook SDK js