Monday, October 7, 2013

Animated Responsive Dropdown Menu

Because the navigation bar of Bootstrap provided didn't have and transition effect, so I customize one. Actually it is nearly rebuild the whole UI widget, except the navbar (I tread it as a container) and collapse feature.
PC view of the menu bar.

Mobile view of the menu bar.

Live Demo Here
(You can drag and resize the width of "result" frame in order to view the different visual effect between mobile view and PC view)
As you know, it is good enough to use CSS to build dropdown menu. To make transition animation during hover, you may use CSS3 transition effect. So what is the tricks to make responsive web design? I is nothing difficult but what you need is @media tag in stylesheet. eg:
/* some style for mobile phone view */
.example {
    /* some styles here */
}
/* Style for screen width > 768px,
   Bootstrap tread it as taplet or PC */
@media (min-width: 768px) {
    .example {
        /* some styles here */
    }
}

It is also called mobile first responsive web design, because you may find that the default style is for mobile phone, and the PC style will overwrite it when the screen is wide enough. With this trick, I make two view of the same HTML content, one is the horizontal menu bar, one is the vertical expendable list.
Since in CSS you can only trace the hover event, which is used to show sub-menu in PC view, but in mobile view, we should click on a item and then expend its sub-menu, so what I do next is to add different event handling in different view. Actually it is the main usage of my javascript file, also I extend $.fn to make them controlable by javascript through:
$('.anav-sub').asubnav('toggle');
$('.anav-sub').asubnav('open');
$('.anav-sub').asubnav('close');

Actually it is not very difficult to make such a responsive UI widget, but you may need to spend some times to play with the CSS, the javascript part is relatively simpler. Below is the whole source code of the example:
<!DOCTYPE html>
<html>
<head>
 <title>Animated Responsive Dropdown Menu</title>
 <link href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css" rel="stylesheet">
 <link href="anavbar.css" rel="stylesheet">
 <style type="text/css">
  body {
      margin: 10px;
  }
  @media (min-width: 768px) {
   .navbar-ex2-collapse { float: left; }
  }
 </style>
</head>
<body>
 <!-- Bootstrap Menu Bar -->
 <nav class="navbar navbar-default" role="navigation">
   <div class="navbar-header">
     <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-ex1-collapse">
       <span class="icon-bar"></span>
       <span class="icon-bar"></span>
       <span class="icon-bar"></span>
     </button>
     <a class="navbar-brand" href="#">Bootstrap Menu Bar</a>
   </div>
   <div class="collapse navbar-collapse navbar-ex1-collapse">
     <ul class="nav navbar-nav">
       <li><a href="#">Item 1</a></li>
       <li><a href="#">Item 2</a></li>
       <li class="dropdown">
         <a href="#" class="dropdown-toggle" data-toggle="dropdown">Item 3<b class="caret"></b></a>
         <ul class="dropdown-menu">
           <li><a href="#">Item3.1</a></li>
           <li><a href="#">Item3.2</a></li>
           <li><a href="#">Item3.3</a></li>
         </ul>
       </li>
     </ul>
   </div>
 </nav>

 <div class="spacer" style="height:150px;"></div>

 <!-- My animated responsive navigation bar -->
 <nav id="main_navbar" class="navbar navbar-default anavbar" role="navigation">
  <!-- Navbar header for mobile view -->
  <div class="navbar-header">
   <div class="navbar-brand">
    My Menu Bar
   </div>
   <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-ex2-collapse">
    <span class="sr-only">Toggle navigation</span>
    <span class="icon-bar"></span>
    <span class="icon-bar"></span>
    <span class="icon-bar"></span>
   </button>
  </div>
     
  <!-- Navbar menu -->
  <div class="collapse navbar-collapse navbar-ex2-collapse">
   <ul id="main_nav" class="anav">
    <li><a href="#">Item 1</a></li>
    <li><a href="#">Item 2</a></li>
    <li>
     <a href="#">Item 3<div class="arrow">+</div></a>
     <ul class="anav-sub">
      <li><a href="#">Item 3.1</a></li>
      <li><a href="#">Item 3.2</a></li>
      <li><a href="#">Item 3.3</a></li>
     </ul>
    </li>
   </ul>
  </div>
 </nav>

 <script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
 <script src="//netdna.bootstrapcdn.com/bootstrap/3.0.0/js/bootstrap.min.js"></script>
 <script src="anavbar.js"></script>
</body>
</html>

$(function(){
 var ANAV_CLASS = ".anav",
  ASUBNAV_CLASS = ".anav-sub";
 function getParent(el){
  return $(el).filter(ASUBNAV_CLASS).parent();
 }
 function toggleHandler(event){
  var $this = $(this),
   $subNavbar = $this.children(ASUBNAV_CLASS),
   isVertical = $this.css('float') == 'none' ? true : false;

  if(isVertical && event && event.type == 'click'){
   toggle.call($subNavbar);
   event.preventDefault();
   event.stopPropagation();
  }
  else if(!isVertical && event && event.type == 'mouseenter'){
   toggle.call($subNavbar, true);
  }
  else if(!isVertical && event && event.type == 'mouseleave'){
   toggle.call($subNavbar, false);
  }
 }
 function ASubNav(el){
  this.element = el;
  getParent(el)
   .on('click mouseenter mouseleave', toggleHandler);
 }
 var toggle = ASubNav.prototype.toggle = function(_isOpen){
  var $this = $(this),
   $parent = getParent(this);
   isOpen = typeof _isOpen == 'boolean' ? _isOpen : !$parent.hasClass('open');
  if(isOpen){
   $parent.addClass('open');
   $this.clearQueue().finish().slideDown();
  }else{
   $parent.removeClass('open');
   $this.clearQueue().finish().slideUp();
  }
 };
 ASubNav.prototype.open = function(){
  toggle.call(this, true);
 };
 ASubNav.prototype.close = function(){
  toggle.call(this, false);
 };

 $.fn.asubnav = function(option){
  return this.each(function(){
   var $this = $(this),
    data = $this.data('anav-sub');

   !data && $this.data('anav-sub', (data = new ASubNav(this)));
   if(typeof option == 'string'){
    data[option].call($this);
   }
  });
 }
 $.fn.asubnav.Constructor = ASubNav;

 $(ANAV_CLASS + " " + ASUBNAV_CLASS).asubnav();
});

.anav, .anav-sub {
 position: relative;
 list-style: none;
 margin: 0;
 padding: 0;
}
.anav > li > a,
.anav-sub > li > a {
 text-decoration: none;
 display: block;
}

/* nav collapse button */
.anavbar .navbar-toggle {
 border-color: #ddd;
}
.anavbar .navbar-toggle:hover {
 background: #ddd;
}
.anavbar .navbar-toggle .icon-bar{
 background: #ccc;
}

/* navbar div */
.anavbar {
 min-height: 40px;
 background: background-color: #f8f8f8;
    border: 1px solid #e7e7e7;
}

/* 1st layer ul */
.anav {
 float: left;
 display: block;
 width: 100%;
}
.anav > li {
 position: relative;
}
.anav > li > a {
 padding: 10px 5px;
 color: #777;
}
.anav > li:hover > a {
 color: #333;
}

/* 2nd layer ul */
.anav .anav-sub {
 overflow: hidden;
 display: none;
 white-space: nowrap;
}
.anav .arrow{
 display: inline-block;
    height: 0;
    width: 0;
    overflow: hidden;
    vertical-align: middle;
    margin-left: 4px;
    background: transparent;
    border-top: 4px solid #777;
    border-left: 4px solid transparent;
    border-right: 4px solid transparent;
    border-bottom: 0 solid transparent;
}
.anav > li:hover .arrow {
    border-top-color: #333;
}
.anav .anav-sub > li > a {
 padding: 5px 15px;
 color: #777;
}
.anav .anav-sub > li:hover > a {
 color: #333;
}

/* Responsive */
@media (min-width: 768px) {
 .anav > li { float: left; width: auto; }
 .anav > li > a { padding: 15px; min-width: 85px; text-align: center; }
 .anav .anav-sub {
  position: absolute;
  -webkit-border-radius: 0 0 4px 4px;
  border-radius: 0 0 4px 4px;
  box-shadow: 0 6px 12px rgba(0,0,0,0.175);
  min-width: 160px;
 }
 .open > .anav-sub { padding-bottom: 4px; border: 1px solid #ccc; }
 .anav .anav-sub > li > a { padding: 5px 20px; min-width: 85px; color: #333; }
 .anav .anav-sub > li:hover > a { background: #428bca; color: #fff; }
}

1 comment:

  1. Your blog has given me that thing which I never expect to get from all over the websites. Nice post guys!

    Melbourne Web Developer

    ReplyDelete