Advanced Responsive Web Design

Who Am I?

Code for Today

Go to

https://github.com/Snugug/code-rwd-sass-compass

to fork and clone the repo

Each challenge's starting point is available as a branch.

If you would like to be caught up quickly, simply check out the branch of the challenge you're on.



Media Queries

Example media queries



  // If the window is at least 500px wide...
  @media (min-width: 500px) { ... }

  // If the window is less than 785px wide...
  @media (max-width: 785px) { ... }

  // If the user is printing the page...
  @media print { ... }

  // If the window is in between 520px and 699px...
  @media all and (min-width: 520px) and (max-width: 699px) { ... }

Breakpoint

Breakpoint is a Compass extension. It makes
media queries much easier to maintain.


$nav-lg: 680px;

.main-nav {
  width: 100%;

  @include breakpoint($nav-lg) {
    width: 60%;
    margin: 0 auto;
  }
}

.main-nav {
  width: 100%;
}
@media (min-width: 680px) {
  .main-nav {
    width: 60%;
    margin: 0 auto;
  }
}
            

Breakpoint Breakdown


Boil down most media queries to one simple value.

Assign that value a meaningful name.

You keep all the styles for a component in one place

Manage media queries by purpose and context.

Single value = min-width

Pass Breakpoint just a number and you get a min-width query.

// THIS IS OUR FIRST BREAKPOINT VARIABLE
$basic: 500px; // <-- YUP, THIS ONE

#main-nav {
  width: 100%
  @include breakpoint($basic) {
    width: 75%;
  }
}
#main-nav {
  width: 100%
}
@media (min-width: 500px) {
  #main-nav {
    width: 75%;
  }
}

Fenced Values

Two values creates a min-width / max-width query.

$big-header: 330px 750px;

#header {
  font-size: 2em;
  @include breakpoint($big-header) {
    font-size: 2.5em;
  }
}
#header {
  font-size: 2em;
}
@media (min-width: 330px) and (max-width: 750px) {
  #header {
    font-size: 2.5em;
  }
}

Explicit Test / Value

If one value is a string, assume a feature/value pair

$too-damn-wide: max-width 1000px;

#hero-image {
  max-width: 100%;
  margin: 2em 0;
  @include breakpoint($too-damn-wide) {
    margin: 0.5em 0;
  }
}
#hero-image {
  max-width: 100%;
  margin: 2em 0;
}
@media (max-width: 1000px) {
  #fifty-seven-chevy {
    margin: 0.5em 0;
  }
}

Combine Tests

String them together to create more complex queries

$tighten-text: (max-width 1000px) (orientation portrait);

#main-article {
  font-size: 1em;
  line-height: 1.375;
  @include breakpoint($tighten-text) {
    line-height: 1.25;
  }
}
#main-article {
  font-size: 1em;
  line-height: 1.375;
}
@media (max-width: 1000px) and (orientation: portrait) {
  #pappas {
    line-height: 1.25;
  }
}

No-query fallbacks

Breakpoint can also output fallbacks for when no media queries are present, such as in IE<9


$breakpoint-no-query-fallbacks: true;
$nav-lg: 500px, 'no-query' '.lte-ie9';

nav {
  @include breakpoint($nav-lg) {
    width: 60%;
    margin-right: 4%;
  }
}

            

@media (min-width: 500px) {
  nav {
    width: 60%;
    margin-right: 4%;
  }
}
.lte-ie9 nav {
  width: 60%;
  margin-right: 4%;
}
            


Just add a conditional class to your <html> element

Challenge 01: Basic Media Queries

  • Install your dependencies
  • Create a media query that will increase the body's font size at:
    • A chosen min-width

Challenge 02: Color Schemes

  • Create a media query that will change the color scheme when:
    • The website is in portrait

Challenge 03: OR Queries

  • Output media queries in em units.
  • Create a media query that will change to a third color scheme when:
    • The body's font size changes in landscape
      - or -
    • A chosen max-height in portrait
      - or -
    • The browser is IE8 or below

Singularity & The Grid

The Grid

The Box Model

Box Model
*, *:before, *:after { @include box-sizing('border-box'); }

The 960 Grid System

  • 12 column grid
  • Each column is 60px wide
  • Each gutter is 20px wide
  • 10px side gutter
  • 960px Centered Container

Setting Up The Grid

$grids: 12; // Number of Columns
$gutters: 1/3; // Gutter to Column ratio, 20px/60px = 1/3

  • $grids: Grid definition. Can be single number for symmetric grids or multiple numbers in relation for asymmetric grids. For symmetric grids, each column is considered to have a width of 1.

  • $gutters: Gutter definition. The width of a single gutter in relation to a column of width 1.

Grid Output Styles

Out of the box, Singularity offers two output styles, float and isolation. The default output style is isolation, but we're going to change it float for now as it will be more familiar to begin with.


$output: 'float';

Creating a Layout

Align to columns using Grid Span:

@include grid-span($span, $position); 

$span is the number of columns to span
$position is what column to start from

#container {
  max-width: 960px;  // Outer Container
  padding: 0 10px;   // Side Gutter
  margin: 0 auto;    // Center Container
  @include clearfix; // Have container clear floats properly
}
.left {
  @include grid-span(6,1);
}
.right {
  @include grid-span(6,7);
}

Challenge 04: Basic Grid

  • Create a basic grid with the following features:
    • 10 Columns
    • Float Output Style
    • Column to Gutter ratio of 1:3
  • Display the debugging grid in the <article>

Challenge 05: Basic Grid Spanning

  • Make Ingredients span the first two columns
  • Make Directions span the next three columns
  • Make Media span the last five columns

Challenge 06: Visual Source Order

  • Without changing your HTML source order or changing output styles…
    • Make Media span the first two columns
    • Make Ingredients span the next three columns
    • Make Directions span the last five columns

Class-based grid systems

Grids should work with the content itself, not impose a class structure. We should not limit ourselves to a 12 column layout with four breakpoints.

Instead, let's design our sites around the content, creating awesome mobile-first layouts.

In short: We deserve better.

Content-first Grid Systems

What is content first?

We are going to build and style our grid based upon the content.

Start with the small screen first, then expand until it looks like shit. TIME FOR A BREAKPOINT!

Stephen Hay

This is the opposite of using a framework like Twitter Bootstrap, where the grid is pre-defined.

The Ideal Grid

Grids provide order to your design and structure to your information.

The ideal grid is specific to your content and
your design, since it is an extension of both.

Singularity is built for this.

Within singularity you can create completely different grids for different breakpoints.

You can also customize each section of the site completely, creating sub-structure within a block of content.

Examples of grids

Asymmetric Grids

Grids where the columns are not the same size

Custom grids for each design allow for more unique designs to better highlight your content

Singularity Extras is very useful when working with asymmetric grids

Types of Asymmetric Grids

  • Custom - Any asymmetric grid created to suit your needs
  • Compound - Created by combining symmetric grids
  • Ratio - Each column is derived from the previous according to a ratio
  • Ratio Spiral - Columns are generated based on an overlaid spiral
  • Snap - Asymmetric grid that takes into account an overlaid symmetric grid's gutters

Custom Grid

// List of column width in relation to each other
$grids: 5 2 3 3 7 9;

Compound Grid

// List of symmetric grids to compound together
$grids: compound(3, 4);

Ratio Grid

// Ratio and number of columns
$grids: ratio(golden(), 4);

Ratio Spiral Grid

// Number of columns and ratio
$grids: ratio-spiral(5, golden());

Snap Grid

// Asymmetric grid and the gutter width of the grid to snap to.
$grids: snap(2 4 4 2, 1/3);

Global Contexts

Settings $grids and $gutters set a global context for them, making them available to use without redeclaring them each time they're needed.

You can set different global contexts to use at different min-width breakpoints

$grids: 12;
$grids: add-grid(2 8 2 at 500px);

$gutters: 1/3;
$gutters: add-gutter(1/4 at 532px);

Global Contexts

When using the breakpoint mixin, Singularity is able to determine which global context you'd like and subsequently use the correct one when you use the grid-span mixin

$grids: 2;
$grids: add-grid(4 at 475px);

$gutters: 1/6;

#nav {
  width: 100%;

  @include breakpoint(500px) {
    @include grid-span(3, 2);
  }
}

Overriding Global Contexts

If you need to override the global contexts, for instance if you need to nest a grid and therefore change the grid you're using, use the layout mixin

$grids: 12;
$gutters: 1/3;

#main {
  @include grid-span(8, 1);

  @include layout(8) {
    .left {
      @include grid-span(4, 1);
    }
    .right {
      @include grid-span(4, 5);
    }
  }
}

Challenge 07: Asymmetric Grids

  • Add singularity-extras to your Gemfile and install through Bundler. The version should be <1.0.0.
  • Require singularity-extras in your config.rb file and import singularity-extras generators into your stylesheets
  • Change your grid definition to a 2 column golden ratio based isolation grid
  • Remove your grid-span calls, they're going to break while we drastically change our grids

Challenge 08: Isolation Span

  • Based on your content, choose a min-width to switch from one column to multiple columns
  • Make Ingredients span the first column
  • Make Directions span the second column
  • Make Media span the first column underneath ingredients

Challenge 09: Isolated VSO

  • Based on your content, choose a min-width to switch from two columns to three columns
  • Add a 3 column golden ratio based grid at that point
  • Adjust the gutter
  • Without changing your HTML source order or changing output styles…
    • Make Media span the first column
    • Make Ingredients span the second column
    • Make Directions span the third column

Toolkit

What Is Toolkit?

Toolkit has been providing for us our box model fix and our fluid images, but it can do so much more

Designed not as a CSS System, but rather a set of tools to build your own, Toolkit makes doing things the right way the easy way

Basic Fluid Media

Provided by Toolkit by default, the basic way to get images to squish and maintain their dimensions is fairly easy CSS

img, video {
  max-width: 100%;
  height: auto;
}

But Embedded Content Isn't So Easy

Intrinsic Ratios

Intrinsic Ratios are a CSS technique that allow you to constrain child elements to a ratio and percentage of its parent


// Intrinsic Ratio mixin comes from [Toolkit](https://github.com/team-sass/toolkit)
.ratio-16-9 {
  @include intrinsic-ratio;
}

.ratio-4-3 {
  @include intrinsic-ratio(4/3);
}

Challenge 10: Intrinsic Ratios

  • Remove the video's display: none.
  • Apply a 16:9 intrinsic ratio to the video

Challenge 11: CSS Carousels

  • Turn the list of images into a CSS Carousel 5 items large
  • Create a carousel animation for 5 items, utilizing the start animation

Challenge 12: Inline Media

  • Based on your content for the min-width and the point you go from one column to two columns, create a fenced query for inline media
  • At your inline media query, position the video and the carousel next to each other using a two column grid
  • The carousel should span the first column
  • The video should span the last column
  • The video should be the same ratio as the carousel

Progressive Enhancement

Progressive Enhancement allows us to provide an enhanced experience to superpowered browsers.

Progressive Enhancement is best done through feature detection, like that provided by Modernizr

Sass plus Modernizr is a powerful one-two punch for progressive enhancement

Modernizr

Modernizr provides test to determine browser support without user-agent sniffing

Each test provides a class, either .test or .no-test class, on our <html> tag, as well as a boolean property at Modernizr.test in JavaScript

.logo {
  .svg & {
    background-image: url('../img/logo.svg');
  }
  .no-svg &,
  .no-js &{
    background-image: url('../img/logo.png');
  }
}
.svg .logo {
  background-image: url("../img/logo.svg");
}
.no-svg .logo, .no-js .logo {
  background-image: url("../img/logo.png");
}

Modernizr can also be bundled with yepnope.js, allowing for conditional loading of additional assets (both CSS and JS) based on passed or failed tests

Scripts are loaded asynchronously and in parallel!

Modernizr.load({
  test: Modernizr.svg,
  yep: '../css/svg.css',
  nope: '../css/no-svg.css'
});

Challenge 13: A Better No Query

  • Change your no-query fallbacks for .lte-ie8 to use a Modernizr test for Media Queries instead of relying upon IE conditional classes
  • Remove your IE conditional classes

Challenge 14: PE Carousels

  • Our CSS Only carousel only looks right if CSS Animations are available. Rewrite our implementation so all of the images are available and look good without animations.

Partial Structure

Partials allow us to divide up our styling into discrete pieces, making organizing and maintaining our styling easy

There are many different ways to organize your partials, this is the standard we will be working with from now on.

Global Partials

Global partials contain styling and development knowledge that are central and can be shared across multiple components.

Examples of shared styling knowledge includes color variables, dark/light contrast mixins, and general typography extendable classes

In your sass folder, create a globalfolder and inside that, a folder apiece for extends, functions, mixins, variables to place respective partials in to. In each folder, create a _all.scss partial for a global import for those folders

sass
style.scss
global
extends
_all.scss
functions
_all.scss
mixins
_all.scss
variables
_all.scss

In your style.scss file, start it with any of your file-specific setup variables (cross-browser/Jacket, etc…, not global setup variables that need to be shared across multiple files, like grid and media query variables), then the Compass extension imports you need for that file, finally followed by your global imports

//////////////////////////////
// Cross Browser Support
$legacy-support-for-ie: false;
$jacket: 'base';

//////////////////////////////
// Normalize
@import "normalize";

//////////////////////////////
// Compass Extensions
@import "toolkit";
@import "breakpoint";
@import "singularitygs";

//////////////////////////////
// Globals
@import "global/variables/all";
@import "global/functions/all";
@import "global/mixins/all";
@import "global/extends/all";

Components

Everything on your site is a component; your messages, your media galleries, your buttons, your navigation.

Each component should be written as a generalized mixin with defaulted, globally scoped variables. Then, each instance of a component should be written as an extendable class. Finally, each extendable class should be extended as a full selector

In your sass folder, create a components folder and inside of that, a folder and a partial a piece for each component. Inside each folder, create _extends.scss, _mixins.scss, _variables.scss partials for that component, and import them into your component partial

sass
style.scss
components
_button.scss
_message.scss
button
_extends.scss
_mixins.scss
_variables.scss
message
_extends.scss
_mixins.scss
_variables.scss
$message-status-color: $primary-color !default;
$message-warning-color: $secondary-color: !default;
$message-error-color: $tertiary-color !default;

$message-border-radius: $std-border-radius !default;
$message-extend: true !default;
@mixin message--core($border-radius: $message-border-radius, $extend: $message-extend) {
  @if $extend and $border-radius == $message-border-radius {
    @extend %message--core;
  }
  @if $extend and not $border-radius == $message-border-radius {
    @extend %message--core;
    @include border-radius: $message-border-radius;
  }
  @else {
    width: 80%;
    margin: 0 auto;
    padding: .5em;
    border: 1px solid black;
    @include border-radius: $message-border-radius;
  }
}
…
$message-extendables-extended: false !default;

@if not ($message-extendables-extended) {
  %message--core {
    @include message--core($extend: false);
  }

  %message--status {
    @include message--core($extend: true);
    @include message--instance($message-status-color);
  }
  …
  %message--error {
    @include message--core($extend: true);
    @include message--instance($message-error-color);
  }
}

$message-extendables-extended: true;
@import "message/variables";
@import "message/mixins";
@import "message/extends";

.message {
  @extend %message-core;
}

.message--status {
  @extend %message--status;
}
.message--error {
  @extend %message--error;
}

Layouts

Layouts get yet a third top level partial folder. Generally there will be a handful of set layouts on a given site, and each layout should get a partial structure similar to components.

In your sass folder, create a layouts folder and inside of that, a folder and a partial a piece for each layout. Inside each folder, create _extends.scss, _mixins.scss, _variables.scss partials for that layout, and import them into your layout partial

sass
style.scss
layouts
_article.scss
_landing.scss
article
_extends.scss
_mixins.scss
_variables.scss
landing
_extends.scss
_mixins.scss
_variables.scss

Challenge 16: Update Globals

  • Update your partials to use the new Global partial structure
  • Clean up your non-partial files so they follow the same patterns

Challenge 17: Update Components

  • Update your partials to use the new Component partial structure

Challenge 18: Element Queries

  • Rewrite the recipe meta information (<footer> in .recipe) to change its layout from block to inline depending on if there is enough room to display them all inline
  • Using identical markup, keep one version where it is and add another into the .media section, under the carousel
  • Hint! Look at github.com/snugug/eq.js and Bower

Grunt

Grunt is a Node.js based task runner that can do just about anything. From running development servers to live reloading sites to hinting, linting, compiling and compressing, any development task you can think of, you can get Grunt to do. And it's all written in JavaScript!

Grunt

package.json

When working with Grunt, you're turning your development area into a Node project regardless of what language you're working in. You can quickly initialize a Node project by running npm init, which will create your package.json.

The primary reason for this is the need to install and use Node modules in order to use Grunt.

{
  "name": "code-rwd-sass-compass",
  "version": "0.0.0",
  "description": "Code for RWD Training",
  "author": "Sam Richard",
  "license": "MIT",
  "devDependencies": {}
}

When working with Grunt, most of what you're working with are development packages. When you go to install a package using npm install, appending --save-dev to the end will save them into your package file.

The minimum packages you need in order to run Grunt and use contributed Grunt tasks are grunt and matchdep

Installed modules will be saved to a node_modules file. Be sure to ignore this folder in your version control! Don't commit it in!

Gruntfile

Your Gruntfile.js file is the brains of the boar. Each project you start gets its own Gruntfile that you get to write from scratch.

A Gruntfile is written in strict JavaScript via Node, none of your jQuery here. You'll be adding configuration to a grunt object and creating tasks. Unlike most of Node, Grunt tasks run in series, not parallel.

(function() {
  'use strict';

  module.exports = function (grunt) {
    // Grunt task configuration
    grunt.initConfig({
      // Configuration goes in here
    });

    // Use matchdep to load all Grunt tasks from package.json
    require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks);

    // Custom Grunt tasks go here.
    // This is the special `default` task
    grunt.registerTask('default', function () {
      console.log('This runs when you run `grunt`');
    });
  };
}());
grunt.initConfig({
  connect: {
    server: {
      options: {
        port: 9001,
        base: '.'
      }
    }
  }
});
//////////////////////////////
// Server Task
//////////////////////////////
grunt.registerTask('server', function () {
  grunt.task.run(['connect']);
});

Challenge 19: Watch and Serve

  • Set up a server task to watch for changes in image, javascript, css, and html, and livereload your page.
  • Only watch for CSS file changes, not Sass changes
  • Add an option to your task to choose to launch your site when the server has started
  • Hint! Take a look at grunt-contrib-connect, grunt-contrib-watch, grunt-open. Make sure to ignore the node module files!

Challenge 20: Hint, Compile, Config

  • Set up JSHint your JavaScript on save
  • Compile your Sass through Grunt; get rid of your config.rb file
  • Set up your and read in your server and Compass configuration options from a config.yml file
  • Hint! Don't livereload your page on Sass changes.

Challenge 21: Production Ready

  • Create a task that will write a Bundler Gemfile based on your required gems. You'll need to add versions. Make sure the Gemfile is up to date on every run of grunt server
  • Create a build task that will hint your JavaScript then minimize your images and SVGs and create a production build of Compass, putting everything into an export folder (with the exact location controlled by the user)
  • Use SEMVER to bump your project version, including tagging and releasing git tags, through a command

Thank You

Slides available at

http://snugug.github.io/rwd-sass-compass