Last week I was in Salt Lake City to do a workshop for Deseret Digital. Like my last workshop, we used the time to literally build a website from scratch. I think this live coding style is a decent way to do workshops because it is what real life web development is like. Nothing theoretical, all real working code. The following will be basic notes on what we covered and the template we built. While this certainly isn’t as good of a learning experience as being there in person is, I figured it’s still a useful thing to share with everyone.
Template
We built a simple website.
It’s no thing of great beauty, but feel free to do what you want with it. It was more about covering lots of modern web design techniques n’ stuff.
Notes
The logo is SVG. SVG is vector based so it maintains it’s quality at any size (especially for simple shapes like this). It looks nice and sharp on my Retina laptop as well as the low resolution projector we were using, at any size we scaled it to. Not to mention SVG file sizes are typically small and compress well.
SVG is a native format of Adobe Illustrator, so logo.svg
isn’t some exported file that comes from a master document, it is the master document (as opposed to how png/jpg normally work). Using SVG is as easy as <img src="file.svg">
or background: url(file.svg);
. Unlike icon fonts, a single SVG can have multiple colors and whatnot.
Not every single browser supports SVG. We looked at IE 8 and the Samsung Galaxy Tab in Browserstack (which runs real emulators in real VMs in your browser) and saw the SVG failing. So, we made a PNG fallback graphic. Then we downloaded a custom version of Modernizr and swapped out the src of the image if SVG wasn’t supported.
if (!Modernizr.svg) {
$(".logo img").attr("src", "images/logo.png");
}
We apply a box-shadow
to the logo image, which applies a square shadow around the edge of the image box. Not what we wanted – we wanted the shadow to go around the edges of the stars. We are able to do that with filter: drop-shadow();
instead. Learn more about that important distinction. With SCSS:
.logo {
img { // SVG
@include filter(drop-shadow(2px 2px 2px rgba(black, 0.5)));
}
}
Right from the get-go, we:
- Made a local domain name to work with through MAMP
- Gave ourselves a simple clean directory structure to work with: index.html, /scss/, /css/, /js/, /images/
- We watched that local folder with CodeKit so we could use Sass & Compass as well as get the style injection going which is a nice part of workflow.
The navigation is 8×1 at desktop-ish sizes, 4×2 at tablet-ish sizes, and 2×4 at phone-ish sizes. We made that happen easily through media queries in our CSS.
To be as smart as possible with media queries, we essentially “name” them with a custom user @mixin as outlined here. Then we can use them wherever without repeating ourselves as well as “nest” them with other style block declarations. This keeps the properties we are changing right next to each other in the code which is nice for our brains to keep track of everything.
@mixin mq($mq) {
@if $mq == mama-bear {
@media (max-width: 1250px) { @content; }
}
@if $mq == baby-bear {
@media (max-width: 800px) { @content; }
}
// etc
}
Thus the navigation changes end up like:
.main-nav {
font-size: 0;
a {
display: inline-block;
font-size: 1em;
width: (100% / 8);
@include mq(mama-bear) { width: (100% / 4); }
@include mq(baby-bear) { width: (100% / 2); }
}
}
At our phone-ish media query (baby-bear), we don’t show the navigation at all by default (it’s too big and overwhelming). Instead we show a link to reveal the navigation. This we did without any JavaScript at all, but instead by using the :target
selector in CSS and hiding/showing stuff as needed.
@include mq(baby-bear) {
.main-nav {
display: none;
}
#main-nav:target .main-nav {
display: block;
}
}
We structured the content into a grid. We used the Don’t Overthink It Grids principals. This grid is entirely based on percentages, so it’s inherently flexible. We also have the columns stop floating and become full width at the baby-bear media query.
.grid {
padding: $pad 0 $pad $pad; // $pad is a global spacing variable
@extend .group; // clearfix w/o needing a class
background: rgba(white, 0.2); // fun little rgba/sass trick
}
.col-2-3 {
width: 66.66%;
}
.col-1-3 {
width: 33.33%;
}
.col-1-2 {
width: 50%;
}
.col-1-4 {
width: 25%;
}
.col-1-8 {
width: 12.5%;
}
.col {
float: left;
padding-right: $pad;
@include mq(baby-bear) {
width: 100%;
}
}
This is a good start in making a “responsive” design.
In our main content area, we’re using some media: a Flickr photo and a YouTube video. In both cases, we used the copy-and-paste code provided by those services. In our flexy design, both the image and video have problems. At narrow widths they break out of the parent container. We fix image with some simple CSS:
img {
max-width: 100%;
height: auto;
}
That’s not as easy with video which comes in an iframe and thus has no inherent aspect ratio. We enlist FitVids.js to help us here.
$("article").fitVids();
We now have a good bit of JavaScript going on: jQuery, Modernizr, FitVids, as well as custom JS we’ve written to use these things. That’s four JavaScript files which is too many. All of the stuff we’ve written so far is likely “global” in that any page of the site should be running it. So, we make a global.js file and have CodeKit prepend all the libraries we’re using to it (i.e. // @codekit-prepend "jquery.js"
). Now we only load one JS file per page (global-ck.js
), which is good for performance.
This is similar to what we are already doing with SCSS – breaking files into small chunks and @importing them into the global.css. Ultimately our global.scss file is just a bunch of imports and a little bit of generic global code.
@import "compass/css3";
@import "bits";
@import "normalize";
@import "toolbox";
@import "grid";
@import "module";
@import "fonts";
@import "typography";
* {
@include box-sizing(border-box);
}
// Structure and basics
In our sidebar we want to have a map to our location. Google Maps provides code to embed interactive maps on websites easily. They work on mobile devices, but 1) they are iframes and thus slow and 2) they are a crappier experience than using a native app or maps.google.com.
This is a good candiate for conditional loading. Mobile device: load up a small graphic and link to the native app. Desktop: load the interactive iframe. We enlist Enquire.js to help us with this. Enquire.js runs JavaScript functions when media queries that we declare match or unmatch. So for us, we can Ajax in (via jQuery) the correct bit of content for the relevant media query.
enquire
.register("(min-width: 800px)", { // mama or bigger
match: function() {
$("#map").load("parts/papa-map.html");
}
})
.register("(max-width: 799px)", { // baby
match: function() {
$("#map").load("parts/baby-map.html");
}
})
.listen();
The texture of the site is from Subtle Patterns. Their downloads now come with 2x versions for retina displays. We utilize this, but only reference the 2x version inside a retina media query so it overrides the original. We confirm that it only downloads one or the other through the Resources tab of Chrome Dev Tools.
We used Open Sans from Google Fonts. Instead of linking up another CSS file as they suggest, we open up that CSS file and snag out the @font-face rules and put them in our own typography CSS files.
This article is hugely valuable in that it touches upon so many modern best practices all at once. I thought I’d knew about all of this yet still found a new gem: Enquire.js. I’ll be needing that on a project right now.
I do have one remark regarding responsive mixins: To me they’re quite messy. I’d definitely prefer having all ‘smartphone’ specific styles in a single location, rather than spreading them out everywhere. I think it’s easier to maintain and leads to smaller compiled CSS files. Could be just me though, I guess it’s a choice of style.
Google webfonts sniffs useragent so your pretty fonts will not be shown in ’not so modern’ browsers.
This reminds me a lot of the video you posted a few months back where you made basically the same thing with lots of colours.
Great non-tutorial explaining how to build a basic responsive/grid/SVG/web-font/flexible/etc. website.
Nice to see you’re using SVG for the logo image. I think SVG is really handy – you can edit it so much more easily than a raster image, it can be as large or as small as you want, and it takes up less file size too.
I’ve written an intro to SVG for beginners on my blog. Read it here:
An Introduction to SVG
Amazing to see you’ve adopted enquire into your responsive toolkit Chris, makes me feel like a proud dad! I’ve yet to get around to using it for switching content in/out as you have here, so really happy to see it working as expected.
Here’s a little tip: currently the map will not be displayed in IE <9 because of lack of media query support.
register
can accept a third parametershouldDegrade
, which causes the callbacks to always fire for incapable browsers (yet still conditionally fire for capable browsers).So you pass in
true
for your media queries targeting desktop browsers, and that would allow the map to be delivered to IE <9 :)Chris, I was an attendee at this workshop! I’ve been digesting my notes over the past few days and it’s really changing my workflow. I’ve become a big fan of Compass, amongst a few other gems you shared.
Thanks again!
That’s brilliant, I had no idea that @content blocks existed in Sass. I like that a lot!
I also gave the stripping out the font-face rules from Google fonts a try, but when testing I noticed that the fonts weren’t displaying IE8 at all. Like Alexey mentions, it looks like Google browser sniff before serving the CSS, and return different content depending on the browser. Switching back to using the standard link made the fonts work as expected again.
Might not be a problem depending on what you’re doing, but just something to be aware of.
Thanks : )
On SVG:
I made an attempt at a decent SVG fallback a few weeks ago, based partly on what you posted on smashing mag, but SVGs as background images on firefox (mobile and desktop are lousy) and one has to be careful to load only one asset but always have a fallback when there’s no JS.
So here is my take:
https://github.com/baamenabar/svgfall
It’s fast and library independent.
As I have been working with picturefill, I borrowed the syntax… and most of the code, I’m sure it could be done better. The actual code is very short and easy to adjut to whatever you need.
I hate to say this, but
font-size: 0;
is overruled by minimum font size this end. The menu bar folds!Opera 12.10 on Mac OS 10.8.2 FWIW.