Skip to content

Instantly share code, notes, and snippets.

@panoply
Created September 21, 2016 16:48
Show Gist options
  • Save panoply/e7439f2dd17c94b9105996d4ea589721 to your computer and use it in GitHub Desktop.
Save panoply/e7439f2dd17c94b9105996d4ea589721 to your computer and use it in GitHub Desktop.
Offcanvas Drawer (Standalone)

Drawer Offcanvas

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.

/* 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);
// 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;
}
}
<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>&nbsp;</span><span>&nbsp;</span><span>&nbsp;</span>
</button>
</div>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment