m

Sass & Compass For Designers

i

Bonus section Off Canvas layout with Susy and jQuery

In this site, an 'off-canvas' layout is being used for smaller viewports. Detailing how this technique is achieved is a little beyond the scope of the book so an explanation of how it was done is included here.

One design pattern that has caught my eye recently is 'off-canvas'. I first saw this talked about by Luke Wroblewski here and then brought to life and documented by Jason Weaver here. The idea is that non-critical content is hidden 'off-canvas' (outside the viewport area) and then shown as and when needed.

The technique is used here, borrowing heavily from Jason's example. It makes for a more compelling experience for users with smaller viewports (and JavaScript enabled). If you're reading this on a device with a small viewport (e.g. smart phone) you can see what I'm talking about by clicking the menu button in the header. If you're on a desktop machine, resize the browser window.

The interactivity of this feature could be written with plain JavaScript but I have two problems with that: it would be more difficult to explain the code and far more importantly, I would not do a very good job of writing the plain JavaScript! I’m therefore including the popular JavaScript library jQuery in my project (I’m storing my version of jQuery in the 'plugins.js' file inside the 'js' folder). I’m then writing the jQuery in the 'main.js' file (also inside the 'js' folder):

jQuery used:

// Make the showSidebar function into a variable so it can be easily called
var showSidebar = function() {
  $('body').toggleClass("active");
};

// add/remove classes everytime the window resize event fires
jQuery(window).resize(function(){
  var off_canvas_nav_display = $('.off-canvas-navigation').css('display');
  if (off_canvas_nav_display === 'block') {
    $("body").removeClass("active");
  }
});

jQuery(document).ready(function($) {
  // Toggle for sidebar
  $('#sidebar_button').click(function(e) {
    e.preventDefault();
    showSidebar();
  });
});

Depending on how strong or weak your jQuery skills are, the above jQuery code may make total or no sense. Let me explain its purpose: when a button (#sidebar_button) is clicked, it adds a class of active to the body tag. In addition, if the browser window is resized, it will check to see if the element that wraps the #sidebar_button (.off-canvas-navigation) has it’s CSS set to display: block; if it has, it removes the active class from the body. On it’s own, that won’t make an awful lot of difference. We need styles that will work in tandem.

The styles are primarily defined in the _layouts.scss partial. First of all:

[class^="inner"] {
  @include container;
  overflow: hidden;
}

We're adding overflow: hidden; as this wraps the elements that we are showing and hiding. This stops the browser window trying to add scrollbars (or fit all the content in on some phones) when the navigation content is displayed alongside the main content. Next, the navigation layout is amended quite heavily:

[role="navigation"] {
  @extend %single-transition;
  // Accessibility layout - shows when no JavaScript
  @include span-columns(12 omega,12);

  // If JavaScript present, hide this section 'off-canvas'
  .js & {
    margin-left: -100%;
    @include span-columns(9,12);
    float: left;
    z-index: 2;
  }
  // If button in the 'off-canvas-navigation' area is clicked, 'active' class is applied to the body so the following styles are applied
  .active & {
    margin-left: 0;
    @include span-columns(9,12);
  }
  // Medium plus layout - applied at larger viewports
  @include at-breakpoint($M) {
    // if no JavaScript but media query support apply following:
    @include span-columns(3,12);
    // With JavaScript AND media query support apply following:
    .js & {
      @include span-columns(3,12);
      margin-left: 0;
    }
  }
}

This looks a little daunting so let’s break it down top to bottom. First up is our 'accessibility' layout using the Susy span-columns mixin:

// Accessibility layout - shows when no JavaScript
@include span-columns(12 omega,12);

This is the layout that users will get if they have no JavaScript and no support for CSS media queries, regardless of their viewport size. Next:

// If JavaScript present, hide this section 'off-canvas'
  .js & {
    margin-left: -100%;
    @include span-columns(9 alpha,12);
    float: left;
    z-index: 2;
  }

With this code, if a browser supports JavaScript, Modernizr will have added the js class to the html tag. I’m using the Sass parent selector so that if the [role="navigation"] is preceded by an element with a class of js, the rules enclosed will be applied. The key thing to note is that a negative left-margin of 100% is being used to effectively pull the area 'off canvas'. Next:

// If button in the 'off-canvas-navigation' area is clicked, 'active' class is applied to the body so the following styles are applied
  .active & {
    margin-left: 0;
    @include span-columns(9 alpha,12);
  }

In the same vein, here the parent selector is used so that if an element with a class of active precedes this tag (as will be the case once the button is clicked) these styles will be shown. As it will come later in the CSS, the cascade will apply these styles when relevant (rather than the earlier ones). Key here is that the margin-left has been reset to 0 so that it doesn’t appear 'off canvas'. Final section now:

// Medium plus layout - applied at larger viewports
@include at-breakpoint($M) {
  // if no JavaScript but media query support apply following:
  @include span-columns(3 alpha,12);
  // With JavaScript AND media query support apply following:
  .js & {
    @include span-columns(3 alpha,12);
    margin-left: 0;
  }
}

In this section we are using the Susy at-breakpoint mixin to define a new media query based on our $M variable. The first nested styles apply when in the eventuality that there is media query support but not JavaScript, the second nested styles apply when there is both media query support and JavaScript.

That is the navigation area, now for the main-content div:

.main-content {
  @extend %single-transition;
  // Accessibility layout - shows when no JavaScript
  @include span-columns(12 omega,12);

  // If JavaScript present, apply these rules:
  .js & {
    margin-left: 0;
    float: left;
    z-index: 1;
  }
  // If button in the 'off-canvas-navigation' area is clicked, 'active' class is applied to the body so the following styles are applied
  .active & {
    margin-right: -100%;
  }
  // Medium plus layout - applied at larger viewports
  @include at-breakpoint($M) {
    @include prefix(.5);
    @include span-columns(9 omega,12);
  }
}

This follows similar conventions to the navigation styles: an accessibility layout, then a layout for when JavaScript is supported, then if an active class exists before (meaning the button has been pressed) and finally, media queries for the layout variants. Key to note is the margin-right: -100%; on the active style. This is what drags the main-content partially 'off-canvas'.

For brevity we won’t go through this part section by section but hopefully the comments make it clear enough. Again, that's a great thing about working with Sass, you can heavily comment code to make it understandable for yourself and fellow developers, without creating bloated code when moving to development. Now, to make the off-canvas navigation button, this has been added to the markup, write before the closing div of the .inner-header:

<div class="off-canvas-navigation"><a class="sidebar-button" id="sidebar_button" href="#sidebar">Chapters/Content</a></div>

Obviously the styles for the button can be whatever is needed for your own project. Just remove the button with display:none; at larger viewports when it is unneeded.

Finally, we need a little visual 'oil' to smooth the effect of the off-canvas area becoming visible. This is achieved with a Compass mixin to generate a CSS3 transition effect. In the _placeholders.scss partial file this has been added:

%single-transition {
  @include single-transition(all, .3s, ease, 0s);
}

Then, at the top of the [role="navigation"] and .main-content rules, I’m extending that style:

@extend %single-transition;

Bringing our Sass, Compass and Susy skills together with some jQuery has produced a working off-canvas design for smaller viewports.