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!
- Creating an off-canvas side menu using CSS3 (and a touch of JavaScript)
- Next steps- stylizing the menu contents and adding an overlay when the menu is open
- Using JavaScript to toggle the menu on demand, create IE8 compatibility
Next steps- stylizing the menu contents and adding an overlay when the menu is open