body, html {
height: 100%;
overflow-x:hidden;
}
#outer-wrapper {
width: 100%;
height: 100%;
position: relative;
-webkit-transition:all .3s ease-in-out;
-moz-transition:all .3s ease-in-out;
-o-transition:all .3s ease-in-out;
transition:all .3s ease-in-out;
}
.show-left-menu #outer-wrapper {
/*left: 200px;*/
-webkit-transform: translate3d(200px,0,0);
-moz-transform: translate3d(200px,0,0);
-ms-transform: translate3d(200px,0,0);
-o-transform: translate3d(200px,0,0);
transform: translate3d(200px,0,0);
}
.show-right-menu #outer-wrapper {
/*left: -200px;*/
-webkit-transform: translate3d(-200px,0,0);
-moz-transform: translate3d(-200px,0,0);
-ms-transform: translate3d(-200px,0,0);
-o-transform: translate3d(-200px,0,0);
transform: translate3d(-200px,0,0);
}
.off-canvas-menu {
width: 200px;
height:100%;
background: #333;
position: absolute;
top:0;
-webkit-box-shadow: inset -1.5em 0 1.5em -0.75em rgba(0, 0, 0, 0.25);
-moz-box-shadow: inset -1.5em 0 1.5em -0.75em rgba(0, 0, 0, 0.25);
box-shadow: inset -1.5em 0 1.5em -0.75em rgba(0, 0, 0, 0.25);
padding-top: 80px;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
nav#left-menu {
left: -200px;
}
nav#right-menu {
left: 100%;
}
#inner-wrapper {
overflow: hidden;
min-height: 100%;
padding-top: 80px;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
#outer-wrapper .navbar.navbar-fixed-top {
position: absolute;
}
.navbar.navbar-fixed-top .btn.btn-navbar.off-canvas-menu-toggle {
display: block;
height: 40px;
width: 40px;
padding: 10px;
background: #444;
margin: 0;
border-radius: 0;
border-right: 1px solid #000;
}
@media (max-width: 767px) {
body {
padding-left: 0;
padding-right: 0;
}
#inner-wrapper {
padding-left: 20px;
padding-right: 20px;
}
.navbar-fixed-top, .navbar-fixed-bottom, .navbar-static-top {
margin-left: 0;
margin-right: 0;
}
}
"use strict";
(function(window) {
window.OffCanvasMenuController = function(options){
options = options || {};
this.$menu = options.$menu;
this.menuExpandedClass = options.menuExpandedClass;
// Escape if the menu is not found.
if(this.$menu.length == 0 || !this.menuExpandedClass)
return;
this.$menuToggle = options.$menuToggle || [];
this.$menuExpandedClassTarget = options.$menuExpandedClassTarget || $('body');
this.position = options.position || 'left';
this.$wrapper = options.wrapper || $('#outer-wrapper');
this.wrapper = this.$wrapper[0];
this.dragHandleOffset = options.dragHandleOffset || this.$menuToggle.outerWidth();
this.expandedWidth = this.$menu.outerWidth();
if(this.$menuToggle.length > 0){
var self = this;
// Set up toggle button:
this.$menuToggle.click(function(){
var method = !self.$menuExpandedClassTarget.hasClass(self.menuExpandedClass) ? 'addClass' : 'removeClass';
self.$menuExpandedClassTarget[method](self.menuExpandedClass);
});
}
// add event listeners
if (this.wrapper.addEventListener) {
this.wrapper.addEventListener('touchstart', this, false);
this.wrapper.addEventListener('touchmove', this, false);
this.wrapper.addEventListener('touchend', this, false);
this.wrapper.addEventListener('touchcancel', this, false);
}
}
window.OffCanvasMenuController.prototype = {
start: null,
handleEvent: function (e) {
switch (e.type) {
case 'touchstart': this.onTouchStart(e); break;
case 'touchmove': this.onTouchMove(e); break;
case 'touchcancel':
case 'touchend': this.onTouchEnd(e); break;
}
},
currentPosition: function(){
return this.position == 'left' ? this.$menu.offset().left + this.expandedWidth
: this.$menu.offset().left;
},
inBounds: function(position){
return (this.position == 'left' && position >= 0 && position <= this.expandedWidth) ||
(position >= -this.expandedWidth && position <= 0);
},
onTouchStart: function(e){
var pageX = e.touches[0].pageX;
// Escape if invalid start touch position
if(this.currentPosition() - this.dragHandleOffset > pageX ||
this.currentPosition() + this.dragHandleOffset < pageX)
return;
this.start = {
startingX: this.currentPosition(),
// get touch coordinates for delta calculations in onTouchMove
pageX: pageX,
pageY: e.touches[0].pageY
};
// reset deltaX
this.deltaX = this.$wrapper.position().left;
// used for testing first onTouchMove event
this.isScrolling = undefined;
// set transition time to 0 for 1-to-1 touch movement
this.wrapper.style.MozTransitionDuration = this.wrapper.style.webkitTransitionDuration = 0;
e.stopPropagation();
},
onTouchMove: function(e){
// Escape if invalid start or not in bounds:
if(!this.start)
return;
this.deltaX = e.touches[0].pageX - this.start.pageX;
// determine if scrolling test has run - one time test
if (typeof this.isScrolling == 'undefined') {
this.isScrolling = !!(this.isScrolling || Math.abs(this.deltaX) < Math.abs(e.touches[0].pageY - this.start.pageY));
}
// if user is not trying to scroll vertically
if (!this.isScrolling) {
// prevent native scrolling
e.preventDefault();
var newPos = this.position == 'left' ? this.start.startingX + this.deltaX
: this.deltaX - ($(window).width() - this.start.startingX);
if(!this.inBounds(newPos))
return;
// translate immediately 1-to-1
this.wrapper.style.MozTransform = this.wrapper.style.webkitTransform = 'translate3d(' + newPos + 'px,0,0)';
e.stopPropagation();
}
},
onTouchEnd: function(e){
// Escape if invalid start:
if(!this.start)
return;
// if not scrolling vertically
if (!this.isScrolling) {
// determine if swipe will trigger open/close menu
var isOpeningMenu = (this.position == 'left' && this.deltaX > 0) ||
(this.position == 'right' && this.deltaX < 0);
// Reset styles
this.$wrapper.attr('style', '');
// open/close menu:
var method = isOpeningMenu ? 'addClass' : 'removeClass';
this.$menuExpandedClassTarget[method](this.menuExpandedClass);
}
// Reset start object:
this.start = null;
e.stopPropagation();
}
}
})(window);
<!DOCTYPE html>
<html>
<head>
<title>Off canvas menu example</title>
<link href="http://netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/css/bootstrap-combined.min.css" rel="stylesheet">
<link href="./styles.css" rel="stylesheet">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="http://netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/js/bootstrap.min.js"></script>
<script src="./off-canvas-menu.js"></script>
<script>
"use strict";
$(document).ready(function(){
new OffCanvasMenuController({
$menu: $('#left-menu'),
$menuToggle: $('#left-menu-toggle'),
menuExpandedClass: 'show-left-menu',
position: 'left'
});
new OffCanvasMenuController({
$menu: $('#right-menu'),
$menuToggle: $('#right-menu-toggle'),
menuExpandedClass: 'show-right-menu',
position: 'right'
});
});
</script>
</head>
<body>
<div id="outer-wrapper">
<div id="inner-wrapper">
<nav id="left-menu" class="off-canvas-menu">
<ul>
<li>
<a href="#">Chapter 1</a>
</li>
<li>
<a href="#">Chapter 2</a>
</li>
<li>
<a href="#">Chapter 3</a>
</li>
<li>
<a href="#">Chapter 4</a>
</li>
<li>
<a href="#">Chapter 5</a>
</li>
</ul>
</nav>
<nav id="right-menu" class="off-canvas-menu">
<ul>
<li>
<a href="#">Chapter 1</a>
</li>
<li>
<a href="#">Chapter 2</a>
</li>
<li>
<a href="#">Chapter 3</a>
</li>
<li>
<a href="#">Chapter 4</a>
</li>
<li>
<a href="#">Chapter 5</a>
</li>
</ul>
</nav>
<div class="navbar navbar-inverse navbar-fixed-top">
<div class="navbar-inner">
<div class="pull-left">
<button type="button" id="left-menu-toggle" class="btn btn-navbar off-canvas-menu-toggle">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
</div>
<div class="pull-right">
<button type="button" id="right-menu-toggle" class="btn btn-navbar off-canvas-menu-toggle">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
</div>
</div>
</div>
<div class="container">
<div class="span12">
<h1>Hey hey hey!</h1>
This example is based on elements of <a href="http://coding.smashingmagazine.com/2013/01/15/off-canvas-navigation-for-responsive-website/">this post.</a>
</div>
</div>
</div>
</div>
</body>
</html>
View an example here.
This example is based on elements of this post.
Incliudes a OffCanvasMenuController to handle touch events bound to the off canvas menu. For example when swiping from the outer left side to the right, the left off canvas menu is dragged along.
new OffCanvasMenuController({
$menu: $('#left-menu'),
$menuToggle: $('#left-menu-toggle'),
menuExpandedClass: 'show-left-menu',
position: 'left'
});