Extracted from the Shopify Timber framework and reworked as a standalone component with added animated hamburger. You will need jQuery but setup is simple. Just copy and paste relevent files.
Created
September 21, 2016 16:48
-
-
Save panoply/e7439f2dd17c94b9105996d4ea589721 to your computer and use it in GitHub Desktop.
Offcanvas Drawer (Standalone)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* Jonathan Snook - MIT License - https://github.com/snookca/prepareTransition */ | |
(function(a){a.fn.prepareTransition=function(){return this.each(function(){var b=a(this);b.one("TransitionEnd webkitTransitionEnd transitionend oTransitionEnd",function(){b.removeClass("is-transitioning")});var c=["transition-duration","-moz-transition-duration","-webkit-transition-duration","-o-transition-duration"];var d=0;a.each(c,function(a,c){d=parseFloat(b.css(c))||d});if(d!=0){b.addClass("is-transitioning");b[0].offsetWidth}})}})(jQuery); | |
// Offcanvas functions | |
window.offCanvas = window.offCanvas || {}; | |
offCanvas.cacheSelectors = function () { | |
offCanvas.cache = { | |
// General | |
$html : $('html'), | |
$body : $(document.body), | |
// Navigation | |
$open : $('.drawer-hamburger'), | |
}; | |
}; | |
offCanvas.init = function () { | |
FastClick.attach(document.body); | |
offCanvas.cacheSelectors(); | |
offCanvas.drawersInit(); | |
offCanvas.openButton(); | |
}; | |
offCanvas.drawersInit = function () { | |
offCanvas.LeftDrawer = new offCanvas.Drawers('LeftDrawer', 'left'); | |
offCanvas.RightDrawer = new offCanvas.Drawers('RightDrawer', 'right'); | |
}; | |
offCanvas.openButton = function () { | |
offCanvas.cache.$open.on('click', function() { | |
$(this).toggleClass('open'); | |
}); | |
}; | |
offCanvas.getHash = function () { | |
return window.location.hash; | |
}; | |
/*============================================================================ | |
Drawer modules | |
- Docs http://shopify.github.io/Timber/#drawers | |
==============================================================================*/ | |
offCanvas.Drawers = (function () { | |
var Drawer = function (id, position, options) { | |
var defaults = { | |
close: '.js-drawer-close', | |
open: '.js-drawer-open-' + position, | |
openClass: 'js-drawer-open', | |
dirOpenClass: 'js-drawer-open-' + position | |
}; | |
this.$nodes = { | |
parent: $('body, html'), | |
page: $('#container'), | |
moved: $('.is-moved-by-drawer') | |
}; | |
this.config = $.extend(defaults, options); | |
this.position = position; | |
this.$drawer = $('#' + id); | |
if (!this.$drawer.length) { | |
return false; | |
} | |
this.drawerIsOpen = false; | |
this.init(); | |
}; | |
Drawer.prototype.init = function () { | |
$(this.config.open).on('click', $.proxy(this.open, this)); | |
this.$drawer.find(this.config.close).on('click', $.proxy(this.close, this)); | |
}; | |
Drawer.prototype.open = function (evt) { | |
// Keep track if drawer was opened from a click, or called by another function | |
var externalCall = false; | |
// Prevent following href if link is clicked | |
if (evt) { | |
evt.preventDefault(); | |
} else { | |
externalCall = true; | |
} | |
// Without this, the drawer opens, the click event bubbles up to $nodes.page | |
// which closes the drawer. | |
if (evt && evt.stopPropagation) { | |
evt.stopPropagation(); | |
// save the source of the click, we'll focus to this on close | |
this.$activeSource = $(evt.currentTarget); | |
} | |
if (this.drawerIsOpen && !externalCall) { | |
return this.close(); | |
} | |
// Notify the drawer is going to open | |
offCanvas.cache.$body.trigger('beforeDrawerOpen.offCanvas', this); | |
// Add is-transitioning class to moved elements on open so drawer can have | |
// transition for close animation | |
this.$nodes.moved.addClass('is-transitioning'); | |
this.$drawer.prepareTransition(); | |
this.$nodes.parent.addClass(this.config.openClass + ' ' + this.config.dirOpenClass); | |
this.drawerIsOpen = true; | |
// Set focus on drawer | |
this.trapFocus(this.$drawer, 'drawer_focus'); | |
// Run function when draw opens if set | |
if (this.config.onDrawerOpen && typeof(this.config.onDrawerOpen) == 'function') { | |
if (!externalCall) { | |
this.config.onDrawerOpen(); | |
} | |
} | |
if (this.$activeSource && this.$activeSource.attr('aria-expanded')) { | |
this.$activeSource.attr('aria-expanded', 'true'); | |
} | |
// Lock scrolling on mobile | |
this.$nodes.page.on('touchmove.drawer', function () { | |
return false; | |
}); | |
this.$nodes.page.on('click.drawer', $.proxy(function () { | |
this.close(); | |
// Revert Hamburger Icon | |
offCanvas.cache.$open.removeClass('open'); | |
return false; | |
}, this)); | |
// Notify the drawer has opened | |
offCanvas.cache.$body.trigger('afterDrawerOpen.offCanvas', this); | |
}; | |
Drawer.prototype.close = function () { | |
if (!this.drawerIsOpen) { // don't close a closed drawer | |
return; | |
} | |
// Notify the drawer is going to close | |
offCanvas.cache.$body.trigger('beforeDrawerClose.offCanvas', this); | |
// deselect any focused form elements | |
$(document.activeElement).trigger('blur'); | |
// Ensure closing transition is applied to moved elements, like the nav | |
this.$nodes.moved.prepareTransition({ disableExisting: true }); | |
this.$drawer.prepareTransition({ disableExisting: true }); | |
this.$nodes.parent.removeClass(this.config.dirOpenClass + ' ' + this.config.openClass); | |
this.drawerIsOpen = false; | |
// Remove focus on drawer | |
this.removeTrapFocus(this.$drawer, 'drawer_focus'); | |
this.$nodes.page.off('.drawer'); | |
// Notify the drawer is closed now | |
offCanvas.cache.$body.trigger('afterDrawerClose.offCanvas', this); | |
}; | |
Drawer.prototype.trapFocus = function ($container, eventNamespace) { | |
var eventName = eventNamespace ? 'focusin.' + eventNamespace : 'focusin'; | |
$container.attr('tabindex', '-1'); | |
$container.focus(); | |
$(document).on(eventName, function (evt) { | |
if ($container[0] !== evt.target && !$container.has(evt.target).length) { | |
$container.focus(); | |
} | |
}); | |
}; | |
Drawer.prototype.removeTrapFocus = function ($container, eventNamespace) { | |
var eventName = eventNamespace ? 'focusin.' + eventNamespace : 'focusin'; | |
$container.removeAttr('tabindex'); | |
$(document).off(eventName); | |
}; | |
return Drawer; | |
})(); | |
// Initialize Timber's JS on docready | |
$(offCanvas.init); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Drawers | |
$drawerNavWidth: 300px; | |
$drawerCartWidth: 300px; | |
$colorDrawers: #f6f6f6; | |
$colorDrawerBorder: darken($colorDrawers, 5%); | |
$colorDrawerText: #333; | |
$drawerTransition: all 0.4s cubic-bezier(0.46, 0.01, 0.32, 1); | |
$zindexDrawer: 9999; | |
@mixin prefixer($property, $value, $prefixes) { | |
@each $prefix in $prefixes { | |
@if $prefix == webkit { | |
-webkit-#{$property}: $value; | |
} @else if $prefix == moz { | |
-moz-#{$property}: $value; | |
} @else if $prefix == ms { | |
-ms-#{$property}: $value; | |
} @else if $prefix == o { | |
-o-#{$property}: $value; | |
} @else if $prefix == spec { | |
#{$property}: $value; | |
} @else { | |
@warn "Unrecognized prefix: #{$prefix}"; | |
} | |
} | |
} | |
@mixin transform($transform) { | |
@include prefixer(transform, $transform, ms webkit spec); | |
} | |
@mixin promote-layer($properties: transform) { | |
-webkit-transform: translateZ(0); // translateZ hack | |
will-change: $properties; // spec | |
} | |
/*============================================================================ | |
#Drawers | |
==============================================================================*/ | |
.is-transitioning { | |
display: block !important; | |
visibility: visible !important; | |
} | |
.js-drawer-open { | |
overflow: hidden; | |
} | |
.drawer { | |
@include promote-layer(); | |
display: none; | |
position: fixed; | |
overflow-y: auto; | |
overflow-x: hidden; | |
-webkit-overflow-scrolling: touch; | |
top: 0; | |
bottom: 0; | |
padding: 20px; | |
max-width: 95%; | |
z-index: $zindexDrawer; | |
color: $colorDrawerText; | |
background-color: $colorDrawers; | |
transition: $drawerTransition; | |
a { | |
color: $colorDrawerText; | |
&:hover, | |
&:focus { | |
opacity: 0.7; | |
} | |
} | |
input, | |
textarea { | |
border-color: $colorDrawerBorder; | |
} | |
} | |
.drawer--left { | |
width: $drawerNavWidth; | |
left: -$drawerNavWidth; | |
border-right: 1px solid $colorDrawerBorder; | |
.js-drawer-open-left & { | |
display: block; | |
@include transform(translateX($drawerNavWidth)); | |
.lt-ie9 & { | |
left: 0; | |
} | |
} | |
} | |
.drawer--right { | |
width: $drawerCartWidth; | |
right: -$drawerCartWidth; | |
border-left: 1px solid $colorDrawerBorder; | |
.js-drawer-open-right & { | |
display: block; | |
@include transform(translateX(-$drawerCartWidth)); | |
.lt-ie9 & { | |
right: 0; | |
} | |
} | |
} | |
#PageContainer { | |
overflow: hidden; | |
} | |
.is-moved-by-drawer { | |
@include promote-layer(); | |
transition: $drawerTransition; | |
.js-drawer-open-left & { | |
@include transform(translateX($drawerNavWidth)); | |
} | |
.js-drawer-open-right & { | |
@include transform(translateX(-$drawerCartWidth)); | |
} | |
} | |
.drawer__header { | |
display: table; | |
height: 50px; | |
width: 100%; | |
margin-bottom: 10px; | |
//border-bottom: 1px solid $colorDrawerBorder; | |
font-size:17px; | |
} | |
.drawer__title, | |
.drawer__close { | |
display: table-cell; | |
vertical-align: middle; | |
} | |
.drawer__title { | |
width: 100%; | |
} | |
.drawer__close { | |
width: 1%; | |
text-align: center; | |
font-size: em(18px); | |
} | |
.drawer__close button { | |
position: relative; | |
right: -20px; | |
height: 100%; | |
padding: 0 20px; | |
color: inherit; | |
&:active, | |
&:focus { | |
background-color: darken($colorDrawers, 5%); | |
} | |
} | |
.drawer-hamburger { | |
border: none; | |
background: none; | |
cursor: pointer; | |
outline: 0; | |
height:75px; | |
width:75px; | |
&:focus { | |
outline:none !important; | |
} | |
span { | |
border-radius: none; | |
display: block; | |
height: 3px; | |
width: 40px; | |
margin: 8px; | |
background-color: black; | |
transition: all ease 300ms | |
} | |
&.open { | |
span:first-child { | |
transform: rotate(-45deg) translate(-10px, 8px); | |
} | |
span:nth-child(2) { | |
transform: rotate(45deg) translateY(2px); | |
} | |
span:last-child { | |
transform: rotate(45deg); | |
opacity: 0 | |
} | |
} | |
} | |
.drawer-accounts { | |
position: absolute; | |
top:60px; | |
right:30px; | |
border: none; | |
background: none; | |
cursor: pointer; | |
font-size:12px; | |
color: #c9c9c9; | |
&:focus, | |
&:hover { | |
color:#111; | |
text-decoration:none; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<div id="LeftDrawer" class="drawer drawer--left"> | |
<div class="drawer__header"> | |
<div class="drawer__title">Title</div> | |
</div> | |
<div class="drawer__navigation"> | |
<ul> | |
<li><a href="#">Example Link</a></li> | |
<li><a href="#">Example Link</a></li> | |
<li><a href="#">Example Link</a></li> | |
<li><a href="#">Example Link</a></li> | |
<li><a href="#">Example Link</a></li> | |
</ul> | |
<hr> | |
Lorem | |
<hr> | |
<ul> | |
<li><a href="#">Example Link</a></li> | |
<li><a href="#">Example Link</a></li> | |
<li><a href="#">Example Link</a></li> | |
</ul> | |
</div> | |
</div> | |
<div class="container-fluid is-moved-by-drawer"> | |
<!-- Button to open drawer --> | |
<button type="button" class="drawer-hamburger js-drawer-open-left" aria-controls="LeftDrawer" aria-expanded="false"> | |
<span> </span><span> </span><span> </span> | |
</button> | |
</div> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment