Categories:

Creating an off-canvas side menu using CSS3 (and a touch of JavaScript)

Created: May 7th, 15

One of the most iconic menu patterns of the mobile era must be the off-canvas side menu, which slides in from the edge of the window and displaces the rest of the page horizontally when opened. By sidelining the rest of the page, it focuses the user's attention squarely on the menu itself, creating a user interface that's intuitive to use regardless of device size. In this tutorial, we'll see how to create an off-canvas side menu from scratch using CSS3 only, and with the help of a little JavaScript, polish it off so it's fully ready for real world use.

Demo:  Click on the "drawer" icon at the upper left corner of this page.

The big idea

Lets first lay down the blueprint for how our off-canvas menu will be created. The main steps are:

  • Surround our entire BODY content with a relatively positioned DIV to easily shift it horizontally when the side menu is shown.
  • Create a fixed menu ("position:fixed") that's added outside the relatively positioned DIV above markup wise and initially positioned outside the browser window's left (or right) edge, using either CSS's "left" or "transform: translate3d()" property.

When the menu is opened, we'll shift both of the DIVs above to the right using CSS3's "transform: translate3d()" property for silky smooth transitioning.

Here's the secret diagram of how our menu will fit into an existing page:


Screenshot

The diagram doesn't include three other components involved in the making of our off-canvas menu- a hidden checkbox and corresponding label element to toggle the menu state, plus a semi opaque DIV that masks the rest of the page while the menu is open. We'll cover them at the right time in our tutorial.

Let the fun begin!

Lets take this from the very top- starting with a basic page with some dummy content in it. Step by step we'll see how to integrate an off-canvas menu into it:

<!doctype html>
<head>
	<meta name="viewport" content="width=device-width, initial-scale=1" />
	<style type="text/css">
	/* blank CSS file */
	</style>
</head>
<body>
	Some body content here
	some body content here
</body>
</html>

Here we go!

Step 1: Wrapping the BODY content with a relatively positioned DIV

The first step is to wrap our entire BODY contents with a DIV that's relatively positioned to easily shift the contents horizontally when the menu is opened. Here is the CSS and markup for this wrapper DIV:

#contentarea wrapper CSS

div#contentarea{
	position: relative;
	width: 100%;
	transform: translate3d(0,0,0); /* no shifting of DIV to start */
	transition: transform 0.5s; /* transition the transform property over 0.5s*/
}

#contentarea wrapper markup

<!doctype html>
<head>
	<meta name="viewport" content="width=device-width, initial-scale=1" />
	<style type="text/css">
	/* Menu styles added here */
	</style>
</head>
<body>
	<div id="contentarea">
		Some body content here
		some body content here
	</div>
</body>
</html>

This DIV should surround your existing BODY content in their entirety. In the CSS above and to follow, we've omitted the vendor prefixed versions of CSS3 related properties such as "transform" and "transition" for brevity, though they should be present in the actual code. Turning our attention to the CSS for the #contentarea DIV, we use "transform: translate3d(0,0,0);" to prepare the element for shifting. where a value of "transform: translate3d(250px,0,0);" for example would shift the element 250px to the right. Using the "transform" property instead of CSS's "left" property has the advantage of activating 3D hardware acceleration for smoother transitions. We also use the "transition" property to indicate we wish to transition the "transform" property when the property changes over 0.5 seconds.

Step 2: Defining the basic off-canvas menu

For the next step, lets create the canvas menu skeleton first without the menu contents inside:

nav#offcanvas-menu CSS

nav#offcanvas-menu{ /* Full screen nav menu */
	width: 250px;
	height: 100%;
	top: 0;
	left: 0;
	z-index: 100;
	visibility: hidden; /* this is for browsers that don't support CSS3 translate3d */
	position: fixed; /* fixed menu position */
	transform: translate3d(-250px,0,0); /* shift menu -width to hide it initially */
	box-sizing: border-box; /* include padding/ border as part of declared menu width */
	background: #C1F2BC;
	display: block;
	text-align: center;
	overflow: auto;
	transition: transform 0.5s, visibility 0s 0.5s; /* transition settings. */
}

nav#offcanvas-menu markup

<!doctype html>
<head>
	<meta name="viewport" content="width=device-width, initial-scale=1" />
	<style type="text/css">
	/* Menu styles added here */
	</style>
</head>
<body>

	<nav id="offcanvas-menu">
		Menu contents to be added
	</nav>

	<div id="contentarea">
		Some body content here
		some body content here
	</div>
</body>
</html>

Notice the location of the off canvas menu element (#offcanvas-menu) relative to the content wrapper (#contentarea)- it should be added outside and above it, so the two elements are siblings.

To position the #offcanvas-menu element, well, off canvas, we set the menu's position to "fixed" first, then utilize the CSS3 property transform: "translate3d(-250px,0,0);" to move the menu leftwards the length of the menu width.

Using visibility:hidden for browsers that don't support CSS3's translate3d() value

Inside the #offcanvas-menu element, we've added visibility:hidden for the sake of browsers that don't support CSS3's translate3d(), namely, IE9 and below, for offsetting and hiding the menu initially. Adding this property however makes CSS3 transitions a bit more tricky. What we want is our menu element to be visible immediately when it's transitioning from being closed to open, so the other properties being transitioned are visible to the user during this time. However for the reverse of transitioning from being open to closed, we want our menu element to stay visible until the other properties being transitioned have finished; in other words, we want to set a delay before visibility:hidden is applied in that case. To accomplish the two different behaviours depending on the direction of the transition, initially, we have the following values inside the transition property:

transition: transform 0.5s, visibility 0s 0.5s; /* transition settings. */

Here we are specifying that the transform property should transition over 0.5 seconds, while for the visibility property, it should complete immediately with no transitioning, but only after a delay of 0.5 seconds (the second numeric value specifies the delay). Such a setting takes care of the delay we need when the menu goes from being open to closed- we want a 0.5s delay before the menu is hidden, the time it takes for the other properties being transitioned to complete. Now, this still leaves the other part of the visibility behaviour incomplete, which is for the visibility property to have no delay when being applied whenever the menu is being transitioned from closed to open. We will implement that later.

Note: A good read on the subject of CSS3 transitions involving the visibility property can be found here.

Step 3: Adding a checkbox/label combo to toggle the menu

So far we have a blank off-canvas menu that's positioned outside the browser's edge and invisible to the user. How do we go about revealing it? An elegant, CSS only technique is to define a checkbox and LABEL(s) element combo, then use the checkbox sibling technique to control the state of sibling elements depending on the state of the checkbox itself, in this case, the state of the "#offcanvas-menu" and "#contentarea" elements. If all this sounds a bit overwhelming, fear not, we'll break everything down to bit size morsels below.

Before anything else, first, we'll define a hidden checkbox and accompany LABEL element to toggle the checkbox state. The checkbox should be added as a sibling of the "#offcanvas-menu" and "#contentarea" element, and proceeding them (we'll explain why very shortly). The LABEL element on the other hand can be added anywhere your heart desires, in this case, our heart says inside the "#contentarea" element:

Hidden checkbox and label CSS

input[type="checkbox"]#togglebox {
	/* checkbox used to toggle menu state */
	position: absolute;
	left: 0;
	top: 0;
	visibility: hidden; /* hide checkbox, instead relying on labels to toggle it */
}

label#navtoggler{ /* Main label icon to toggle menu state */
	z-index: 9;
	display: block;
	position: relative;
	font-size: 8px;
	width: 4em;
	height: 2.5em;
	top: 0;
	left: 0;
	text-indent: -1000px;
	border: 0.6em solid black;
	/* border color */
	border-width: 0.6em 0;
	cursor: pointer;
}

label#navtoggler::before{
	/* inner strip inside label */
	content: "";
	display: block;
	position: absolute;
	width: 100%;
	height: 0.6em;
	top: 50%;
	margin-top: -0.3em;
	left: 0;
	background: black; /* stripe background color. Change to match border color of parent label above */
}

Hidden checkbox and label markup

<!doctype html>
<head>
	<meta name="viewport" content="width=device-width, initial-scale=1" />
	<style type="text/css">
	/* Menu styles added here */
	</style>
</head>
<body>

	<input type="checkbox" id="togglebox" />

	<nav id="offcanvas-menu">
		Menu contents to be added
	</nav>

	<div id="contentarea">
		<label for="togglebox" id="navtoggler">Menu</label>
		Some body content here
		some body content here
	</div>
</body>
</html>

Once again, the location of the checkbox element is very important- it should be added above the off canvas menu and #contentarea DIV and as a sibling of the later two. This is to facilitate the checkbox sibling technique that we'll implement next to control the state of both the menu and #contentarea DIV based on the checked state of the checkbox itself. The location of the LABEL element can be arbitrary.

Since the checkbox element is hidden (for aesthetics), we define a corresponding label element to control the checkbox by setting the label's "for" attribute to the ID of the checkbox. The style of the label element creates the popular drawer look using a combination of border and CSS pseudo element.

Lets get a snapshot of what our page looks like so far before continuing:

Step 4: Using the checkbox sibling technique to control the state of sibling elements

Time to install the motor of our off-canvas menu, a mechanism to open/close it plus displace the rest of the content on demand. The CSS only technique basically centers around styling the desired siblings of a checkbox that follow it when it's checked using the sibling selector (~), for example:

input[type="checkbox"]#togglebox:checked ~ div#contentarea{
	/* style of #contentarea DIV when the #togglebox checkbox is checked */
}

The selector #togglebox:checked selects the #togglebox checkbox when it's checked, while all together we are saying: "select the "#contentarea" sibling DIV that follows the checkbox (not proceed it) when the former is checked." The sibling selector (~) of CSS is only capable of selecting siblings of an element that comes after it, which is why the order and hierarchy of the 3 players - the checkbox, the menu and the "#contentarea" DIV here- are so important.

Armed with the ability to style the menu and "#contentarea" DIV differently based on whether the proceeding checkbox is checked or not, all we have to do is style these elements so the menu is expanded and the "#contentarea" shifted to the right by the amount of the menu width when the checkbox is checked. Lets see how this looks in practice:

Selecting and styling the siblings of the #togglebox checkbox when it's checked

input[type="checkbox"]#togglebox:checked ~ div#contentarea{
	margin-left: 10px; /* add some breathing room between menu and contentarea */
	transform: translate3d(250px,0,0); /* shift contentarea 250px to the right */
}

input[type="checkbox"]#togglebox:checked ~ nav#offcanvas-menu{ /* nav state when corresponding checkbox is checked */
	transform: translate3d(0,0,0); /* shift contentarea 250px to the right */
	visibility: visible; /* this is for browsers that don't support CSS3 translate3d in showing the menu */ 
	transition-delay: 0s; /* No delay for applying "visibility:visible" property when menu is going from "closed" to "open" */
}

Live Demo:

Click on the drawer label now- viola, the off-canvas menu now slides into view while nudging aside the rest of the content. When we click on the label again, in turn unchecking it, we undo the styles defined for the two elements, thus closing the menu again.

Continuing with the discussion of using and transitioning the "visibility" property for the sake of browsers that don't support CSS3's translate3d(), here for the #offcanvas-menu's open state, we've set the element's "visibility" to "visible" plus define the transition delay to 0s, or no delay. Recall the reason for this- when the menu is transitioning from being closed to open, what we want is the "visibility" property to be applied immediately so the transitioning of the other properties are visible to the user. Contrast that with when the menu is transitioning from open to closed- in that case we want a delay of 0.5s - the transition time of the other properties, so the transition effects are visible until the very end before hiding the menu.

We now know all the basic parts that go into creating an off-canvas menu. On the next page, lets focus on refinement, by adding and stylizing the content inside the menu and applying an overlay to the page when the menu is open. Lets go!

Next steps- stylizing the menu contents and adding an overlay when the menu is open