Magento Mobile Off-canvas Navigation Menu – [Tutorial]

Long live m-commerce!

These days, going mobile is one of the best decisions anyone could ever make for their eCommerce site. This trend keeps growing bigger and bigger, and there is no stopping it. How to make it to the top? Providing the best user experience is the best place to start.

If you have done some research on mobile menus, you are already aware that there has not been a lack of UX creativity from web designers and developers for the rest of us to play with. The off-canvas navigation menu is one of those many solutions, and I’m going to show you the proper way to make this mobile menu style work with Magento.

Related: Responsive Design Vs. Mobile Websites

Demac Media is going Mobile!

What is an off-canvas menu?

It is a menu that stays hidden outside of the website’s wrapper and slides in from (generally) the left side of the screen pushing the content with it after the menu button is tapped. Once you tap the menu button again, the off-canvas menu slides back out of view. This menu functionality can be accomplished entirely with CSS3.

off-canvas-example

Files that you will need to edit and/or create:

  • app/design/frontend/yourpackage/yourtheme/layout/local.xml
  • app/design/frontend/yourpackage/yourtheme/template/offcanvas
  • app/design/frontend/yourpackage/yourtheme/template/offcanvas/offcanvasleft.phtml
  • app/design/frontend/yourpackage/yourtheme/template/offcanvas/template
  • app/design/frontend/yourpackage/yourtheme/template/offcanvas/template/links.phtml
  • app/design/frontend/yourpackage/yourtheme/template/offcanvas/template/search.phtml
  • app/design/frontend/yourpackage/yourtheme/template/offcanvas/template/categories.phtml
  • app/design/frontend/yourpackage/yourtheme/template/page/html/header.phtml
  • app/design/frontend/yourpackage/yourtheme/template/page/html/mobilenav.phtml
  • app/design/frontend/yourpackage/yourtheme/template/page/1column.phtml
  • app/design/frontend/yourpackage/yourtheme/template/page/2columns-left.phtml
  • app/design/frontend/yourpackage/yourtheme/template/page/2columns-right.phtml
  • app/design/frontend/yourpackage/yourtheme/template/page/3columns.phtml
  • app/design/frontend/yourpackage/yourtheme/template/page/empty.phtml
  • skin/frontend/yourpackage/yourtheme/css/yourtheme.css

STEP 1: Declare your layout blocks.

We will be creating an entirely new tree structure for our off-canvas navigation menu, so go ahead to your local.xml file and create your parent block:

<reference name="root">
    <block type="core/text_list" name="off_canvas" as="offCanvas">
    </block>
</reference>

It needs to be created at the ‘root’ level because it will live outside of all of the content and the type=”core/text_list” tells the block to automatically display all of its children.

When something is off-canvas it can be on either the top, bottom, left or right sides, so let’s declare a block with the ‘left’ word in its name since that’s the position we want it in.

<reference name="root">
    <block type="core/text_list" name="off_canvas" as="offCanvas">
        <block type="core/template" name="off_canvas_left" template="offcanvas/offcanvasleft.phtml">
        </block>
    </block>
</reference>

This time we give it a type=”core/template” which is the most general block type and contains all the methods we will need for this. We will create the template file later on.

Now, it is time to declare the children blocks of content that the off-canvas menu will contain and render. In this tutorial we will have three: a search bar, the category navigation, and extra navigation links. Place these inside of ‘off_canvas_left’.

Search:

<block type="core/template" name="off.canvas.search" as="offCanvasSearch" template="offcanvas/template/search.phtml"/>

Category Navigation:

This one will consist of two blocks

<block type="core/text_list" name="off.canvas.categories" as="offCanvasCategories">
    <block type="page/html_topmenu" name="catalog.mobile" template="offcanvas/template/categories.phtml"/>
</block>

We use the type=”page/html_topmenu” because it is the same type Magento uses for the topmenu.phtml file to render and we will be using the same kind of menu.

Extra navigation links:

<block type="page/template_links" name="off.canvas.links" as="offCanvasLinks" template="offcanvas/template/links.phtml"/>

Again, this block type is the same Magento uses to render lists of links.

Here is the end result:

<reference name="root">
    <block type="core/text_list" name="off_canvas" as="offCanvas">
        <block type="core/template" name="off_canvas_left" template="offcanvas/offcanvasleft.phtml">
            <block type="core/template" name="off.canvas.search" as="offCanvasSearch" template="offcanvas/template/search.phtml"/>
            <block type="core/text_list" name="off.canvas.categories" as="offCanvasCategories">
                <block type="page/html_topmenu" name="catalog.mobile" template="offcanvas/template/categories.phtml"/>
            </block>
            <block type="page/template_links" name="off.canvas.links" as="offCanvasLinks" template="offcanvas/template/links.phtml"/>
        </block>
   </block>
</reference>

Now that we have all of our off-canvas blocks declared, we can proceed to declaring the block for our mobile navigation in the header.

Mobile Navigation:

This one goes outside of the <reference name=”root”></reference> block entirely and in the “header” block instead. This is where you will place the menu icon, and other important mobile navigation buttons.

<reference name=”header”>
    <block type="core/template" name="mobile_nav" as="mobileNav" template="page/html/mobilenav.phtml"/>
</reference>

And that is it for layout!

STEP 2: Create the template files.

File: app/design/frontend/yourpackage/yourtheme/template/offcanvas/offcanvasleft.phtml

Code:

<input type="checkbox" class="off-canvas-check" id="off-canvas-left-check">

<div id="off-canvas-left" class="off-canvas">
    <div class="main-nav-wrapper">
        <div class="off-canvas-search mobile-search">
            <?php echo $this->getChildHtml('offCanvasSearch') ?>
        </div>
        <div class="off-canvas-categories">
            <?php echo $this->getChildHtml('offCanvasCategories') ?>
        </div>
        <div class="off-canvas-links">
            <?php echo $this->getChildHtml('offCanvasLinks') ?>
        </div>
    </div>
</div>

The first line is a check box because we will be using the :checked pseudo class to make this mobile menu work. I’ll show you how a bit later.

After that, you can see two wrapping divs that contain three children divs with the content blocks we declared earlier: search, categories, and links.

File: app/design/frontend/yourpackage/yourtheme/template/offcanvas/template/search.phtml

Code:

<?php $catalogSearchHelper = $this->helper('catalogsearch'); ?>

<form id="mobile_search_form" action="<?php echo $catalogSearchHelper->getResultUrl() ?>" method="get">
    <div class="form-search">
        <label for="mobile_search"><?php echo $this->__('Search site:') ?></label>
        <span class="search-icon"></span>
        <input id="mobile_search"
               type="text"
               name="<?php echo $catalogSearchHelper->getQueryParamName() ?>"
               value="<?php echo $this->__('Search') ?>"
               onblur="if(this.value==''){ this.value='Search';}"
               onfocus="if(this.value=='Search'){ this.value='';}"
               class="input-text"
               maxlength="<?php echo $catalogSearchHelper->getMaxQueryLength(); ?>"
        />
        <button type="submit" title="<?php echo $this->__('Go') ?>" class="button">
            <span><span><?php echo $this->__('Go') ?></span></span></button>
        <a href="<?php echo $catalogSearchHelper->getAdvancedSearchUrl(); ?>"><?php echo $this->__('Advanced Search'); ?></a>

        <div id="mobile_search_autocomplete" class="search-autocomplete"></div>
        <script type="text/javascript">
            //<![CDATA[
            var searchForm = new Varien.searchForm('mobile_search_form', 'mobile_search', '<?php echo $this->__('Search') ?>');
            searchForm.initAutocomplete('<?php echo $catalogSearchHelper->getSuggestUrl() ?>', 'mobile_search_autocomplete');
            //]]>
        </script>
    </div>
</form>

This is literally just a copy and paste from Magento’s template/catalogsearch/form.mini.phtml but we’ve made a few edits here. The most important change is the form’s id=”” which needs to be different from Magento’s or else there would be a JavaScript conflict and one of the search bars would not render. You will need to change all of the other IDs as well for the input and the autocomplete, in this case we just added the word “mobile” before them. We also took the liberty to add an onblur=”” and onfocus=”” attributes to the search input bar to fancy it up. Don’t forget to update the IDs on the JavaScript snippet at the bottom.

File: app/design/frontend/yourpackage/yourtheme/template/offcanvas/template/categories.phtml

Code:

<?php $_menu = $this->getHtml('level-top', '') ?>
<?php if($_menu): ?>
    <div class="nav-container">
        <ul id="nav-mobile">
            <?php echo $_menu ?>
        </ul>
    </div>
<?php endif ?>

This one is a copy and paste from Magento’s template/page/html/topmenu.phtml, the only change here is the <ul id=””>. You could also just write a code snippet here that calls only the top level categories of the store which would make styling easier, but may not be as good for the user experience. It all depends on the kind of navigation functionality you want/need.

File: app/design/frontend/yourpackage/yourtheme/template/offcanvas/template/links.phtml

Code:

<div class="links">
    <ul>
        <?php $_links = $this->getLinks(); ?>
        <?php if(count($_links)>0): ?>

            <?php foreach($_links as $_link): ?>
                <?php if ($_link instanceof Mage_Core_Block_Abstract):?>
                    <?php echo $_link->toHtml() ?>
                <?php else: ?>
                    <li<?php if($_link->getIsFirst()||$_link->getIsLast()): ?> class="<?php if($_link->getIsFirst()): ?>first<?php endif; ?><?php if($_link->getIsLast()): ?> last<?php endif; ?>"<?php endif; ?> <?php echo $_link->getLiParams() ?>>
                        <?php echo $_link->getBeforeText() ?>
                        <span class="link-wrap">
                            <a href="<?php echo $_link->getUrl() ?>" title="<?php echo $_link->getTitle() ?>" <?php echo $_link->getAParams() ?>><?php echo $_link->getLabel() ?></a>
                        </span>
                        <?php echo $_link->getAfterText() ?>
                    </li>
                <?php endif;?>
            <?php endforeach; ?>

        <?php endif; ?>
    </ul>
</div>

The code you see in there is the proper way to create a list of links in Magento. The links that will get rendered by this template will have to be defined in the layout xml by referencing the name of the block <reference name=”off.canvas.links”></reference> and placing an <action method=”addLink”></action> inside of it with the appropriate parameters. Take a look at Magento’s customer.xml file to see an example on how to do this.

File: app/design/frontend/yourpackage/yourtheme/template/page/html/header.phtml

Code:

<?php echo $this->getChildHtml('mobileNav'); ?>

This one is easy, you just need to call the child block we declared in the header block earlier. Preferably below ‘topMenu’. You will later on show and hide this with CSS and media queries but, for anything to render we first need to create the following template file.

File: app/design/frontend/yourpackage/yourtheme/template/page/html/mobilenav.phtml

Code:

<div class="mobile-menu hide-desktop" id="icon-nav">

    <div class="left menu-icon-wrap">
        <label for="off-canvas-left-check" id="menu-icon">
            <div>
                <span class="title"><?php echo $this->__('Menu'); ?></span>
                <span class="first"></span>
                <span class="second"></span>
                <span class="last"></span>
            </div>
        </label>
    </div>

    <div class="right">
        <div class="links">
            <?php echo $this->getChildHtml('loginLinks') ?>
            <span class="div-line">|</span>
            <a href="#" class="stores-link">
                <?php echo $this->__('Stores'); ?>
            </a>
        </div>
        <div class="bag-icon-wrap">
            <a href="<?php echo $this->getUrl('checkout/cart') ?>">
                <span class="label">
                    <?php echo $this->__('Bag'); ?>
                </span>
                <?php $count = $this->helper('checkout/cart')->getSummaryCount(); ?> <?php /* get total items in cart */?>
                <?php if ($count==0):?>
                    <?php echo '<span class="mobile-cart-count">0</span>';?>
                <?php endif; ?>
                <?php if ($count>0):?>
                    <?php echo '<span class="mobile-cart-count">' . $this->__('%s',$count) . '</span>';?>
                <?php endif; ?>
            </a>
        </div>
    </div>

</div>

This is only an example of the content code you could have in this file.

Here we have a ‘menu-icon-wrap’ div for, as the name suggests, our menu icon. Next to that we have placed some basic links: the login links which are in their own block, and a link to the store locator. And finally, we have as a bonus to this tutorial the code that will display the cart item count right on your mobile navigation. The styling is up to you.

You will notice that inside of the ‘menu-icon-wrap’ we have a label. This is the label that goes together with the input check box we placed inside of offcanvasleft.phtml. Just make sure the ID of your check box matches the for=”” attribute of your label.

Files:
app/design/frontend/yourpackage/yourtheme/template/page/1column.phtml
app/design/frontend/yourpackage/yourtheme/template/page/2columns-left.phtml
app/design/frontend/yourpackage/yourtheme/template/page/2columns-right.phtml
app/design/frontend/yourpackage/yourtheme/template/page/3columns.phtml
app/design/frontend/yourpackage/yourtheme/template/page/empty.phtml

Code:

All of these will need the same code edit. Inside of the <body> you will need to wrap the <div class=”wrapper”> with two other wrapping divs, one called ‘outer-wrap’ and another called ‘inner-wrap’. In-between the ‘outer’ and ‘inner’ divs is where you will call the off-canvas menu block, like so:

<body<?php echo $this->getBodyClass()?' class="'.$this->getBodyClass().'"':'' ?>>
<div id="outer-wrap" class="outer-wrap">
    <?php echo $this->getChildHtml('offCanvas') ?>
    <div id="inner-wrap" class="inner-wrap">
        <div class="wrapper">
            <?php /* Magento page content here */ ?>
        </div>
        <?php echo $this->getAbsoluteFooter() ?>
    </div>
</div>
</body>

And that is it for templates! Now last step.

STEP 3: CSS3 tricks.

Here is how we create the sliding animation

.mobile-menu {
    /*Styles are up to you*/
}

#off-canvas-left-check {
    display: none;
}

.outer-wrap {
    width: 100%;
    height: 100%;
    position: relative;
    overflow-x: hidden;
}

.inner-wrap {
    -webkit-transition:all 0.5s ease;
    -moz-transition:all 0.5s ease;
    -o-transition:all 0.5s ease;
    transition:all 0.5s ease;
    position: relative;
    left: 0;
}

#off-canvas-left {
    -webkit-transition:all 0.5s ease;
    -moz-transition:all 0.5s ease;
    -o-transition:all 0.5s ease;
    transition:all 0.5s ease;
    display: block;
    height: 100%;
    left: 0;
    margin-left: -75%;
    overflow: hidden;
    position: absolute;
    width: 75%;
}

#off-canvas-left-check:checked + #off-canvas-left {
    display: block;
    left: 75%;
    margin-right: 0;
    overflow-y: visible;
    z-index: 100;
}

#off-canvas-left-check:checked ~ .inner-wrap {
    position: relative;
    display: block;
    overflow-y: visible;
    z-index: 0;
    left: 75%;
}

@media only screen and (min-width:"940px") {

    .mobile-menu,
    #icon-nav {
        display: none;
    }

  #off-canvas-left-check:checked + #off-canvas-left {
      display: none;
  }

  #off-canvas-left-check:checked ~ .inner-wrap {
      left: 0;
  }
}

Lines 5-7:
We don’t want or need to see the check box, so we better display:none it.

Lines 9-23:
Just some styles and transitions for the wrappers to work better with the off-canvas menu. For the outer-wrap we did overflow-x:hidden to avoid getting a scrolling bar once the off-canvas is open. For the inner-wrap we added a left:0 for the animation to have a starting position. And last but not least we need position:relative here because we will need to do position:absolute on the #off-canvas-left inside of it.

Lines 25-36:
This is our off-canvas menu. The first part here is, of course, the transitions that will allow the sliding animation to happen. We use position:absolute to position the menu relative to the browser window, height:100% to make it reach the bottom of the window at all times, and overflow:hidden so that we can put it outside of its container box without increasing the container’s width. Then we give it a 75% width and a left margin of the same amount but negative so that it’s fully hidden outside of the browser.

Lines 38-44:
Here is where we finally get to use the magic of CSS3. We use the pseudo-class :checked to target the check box when checked. It’ll be checked when the user taps the label that is related to it (the menu button is the label). We use the ‘+’ selector to target only the immediate sibling element with the ‘nav-menu-mobile’ class, and then add the necessary styles for it to slide to the right.

Lines 46-52:
Here we use the :checked pseudo-class again but this time we use the ‘~’ selector which targets any next siblings with the class ‘inner-wrap’ which is the site’s content wrap. We want to simultaneously slide this to the right while the off-canvas slides to the right as well. We need left:75% so that they both move the same distance and look synced. We need position:relative here for left:75% to work.

Lines 54-68:
And finally some media queries to hide the mobile navigation and off-canvas menu from 940px and up. We also want to return ‘inner-wrap’ to left:0. The transitions will make it look nice when you’re resizing the browser window.

Related: Go Mobile Or Go Home!