2013-01-23

In my last post, Designing a responsive, Retina-friendly site, I covered my design process and thoughts behind redesigning this site. I did not cover any aspects of actual development or any Jekyll specifics. This post will mainly cover coding up responsive design and the third and final post will cover retina media queries, responsive images and more.

Note: The final part to this series is now published: Developing a responsive, Retina-friendly site (Part 2).

Jekyll + Rack on Heroku

I last redesigned my blog in 2010 when I migrated from WordPress to Jekyll. I eventually forked jekyll to support a separate photos post type outside of the main site.posts. I then wrapped it in Rack::Rewrite with Rack::TryStatic so I could host it on Heroku and 301 some old permalinks. I won't cover the details of that too much, but I recall reading this post by Matt Manning when I made the switch.

Most of the configuration is in the config.ru file. I loathe URLs that end in .html so my jekyll fork is based on this gist for Apache-inspired "multiviews" support — basically it writes links without the file extension and then I get Rack to do the same.

To ensure deflater is properly compressing markup run this and you should see Content-Encoding: gzip returned:

grunt watches over

When I started developing the new site I wanted to automate some of my workflow. Things like Coffeescript, JavaScript and Sass compilation to production-ready assets whenever any of the source files changed.

I took a look at the grunt build tool to help me with these issues. If you use jekyll, you probably have a Rakefile1 where you have specified several tasks to aid in create new posts and so on. In layman's terms, grunt is very similar but based on node.

Installation is an npm command away: npm install -g grunt

I setup the main grunt.js file in my project directory root to do a few things:

Monitor all files in my style directory and compile screen.scss if any of them changed, like imported scss files.

Watch and compile the Coffeescript file app.coffee into JS and put it in the js directory.

Watch all specified js files in the _jslibs directory and minify them along with the compiled coffee file, app.js, into a single file.

Gzip then upload assets to Cloudfront as necessary.

I installed grunt-coffee and grunt-compass plugins to be able to work with Coffeescript and Compass for Sass. And then grunt-s3 to upload some assets to my S3 Cloudfront bucket. Finally, I installed grunt-smushit to be able to optimize images from the command line (or you can use ImageOptim if you like).

In the root of the directory I created a simple package.json file. I really only use it to add a banner to the top of my js file builds but it also keeps track of dependencies so you can easily re-setup grunt on a new machine with npm install.

Then I created the grunt.js gruntfile2.

Grunt has several built-in tasks, such as min. It accepts a directory or a bunch of specific JavaScript files and a single destination. One of those source files is from a compiled Coffeescript file, so it's important I only run min after the coffee task. To do that, I registered a js task that runs coffee first, then min.

I've also registered a default task (runs when grunt is called by itself) to call the compass task to compile Sass and then run the js task.

Setting up watch is the last and most important step. I configured it to run the coffee task anytime my coffee file changes, run compass anytime any file in the style directory changes, et cetera. I'm only working with a few files so it's instant.

That's it. I just run grunt watch and get back to work.

This was a very basic overview of how I use grunt. It can do a lot more so it's worth exploring for other uses. I don't update the CSS or javascript on my site often so digestification 2 of the compiled assets wasn't important for this, but it's something I want to look into.

Currently I manually run that last task (uploadjs) to build the js and upload it to S3. I'll have to spend some time reading the grunt-s3 source but at first glance it looks like it didn't support upload subtasks, so I couldn't abstract out only css uploading, search-related js uploading, and so on. It just uploads all specified files at the same time right now.

Matt Hodan's Jekyll Asset Pipeline is an alternative to using grunt entirely.

Search

I decided to ditch Google CSE and try out Swiftype, a Y Combinator search startup that has been dubbed the Stripe for site search. I have to agree, it's pretty slick. The best thing is that Swiftype lets me control the search results. I can find popular searches and pin certain results to the top.

There are a few install methods for Swiftype but I chose their self-hosted jQuery plugin. I ended up modifying it to provide pagination controls on the top and bottom of the results, add a no-results-found state and some extra markup to help me style it.

The plugin operates by listening to hash changes that include search params. I may end up refactoring it to remove that. Ideally I don't want to have to load an additional jQuery plugin to watch for hash changes and would like to forgo jQuery in favor of the smaller zepto.

Here's what the completed search interaction looks like, thanks to Photoshop CS6's new Timeline feature that helps me create annoying gifs:

A snippet of the header markup with the search bar:

I only load the Swiftype libraries when the user clicks on the search icon. No need to load all that extra JS for everyone when only a few people will end up searching. Below is the coffeescript that hooks up all of the interactions, downloads the swiftype libraries concatenated and uploaded by grunt, and runs it.

Take it for a spin and try searching above!

Getting Responsive

I'm sure you already know what RWD design is, but to dive a bit deeper responsive design is usually defined as multiple fluid grid layouts while adaptive design is multiple fixed breakpoints / fixed width layouts. However, most of the time you see a mixture of both: fixed width for larger layouts and fluid layouts for smaller viewports.

That's what I do here. Content is 37.62136em wide (multiply that with 16px browser default and the 103% font-size I have on content = 620px) until the smaller viewports when it expands to 100% width.

Right about here I would start talking about how I adapted my site to be responsive on mobile. Except I didn't. I began thinking mobile first and as such it was designed with only a few elements that would need to change between viewports. There was really very little to plan for; I coded it up instead of designing responsive pixel perfects first.

Only a few elements elements needed fiddling:

Header: Show some extra subtitle text and increase font-size for larger viewports in addition to moving the avatar to the left size. Also, showing navigation button text for larger screens ("About" next to about icon, etc)

Footer: Increase font-size considerably on smaller viewports to make links easier to tap. Apple HIG suggests at least 44pt x 44pt for tappable UI elements4.

Content: Overall, making buttons larger and full-width where necessary. Adjusting font-size.

For larger sites, you'll usually hear people buzzing about content choreography — adjusting layouts and moving elements around as content reflows with smaller viewports — and responsive navigation patterns, things like collapsing larger menus into the header. You can also get a good idea for how others work with layouts by scrolling through screenshots mediaqueri.es

Responsive Setup

To get started we need to tell the browser to set the viewport width by using the device's native width in CSS pixels (different than device pixels, CSS pixels take into account ppi). We also need to disable the browser's default zoom level, called initial-scale. Setting maximum-scale to 1 ensures this zoom remains consistent when the device orientation changes. This is necessary as most smartphones set the viewport width to around 1,000 pixels wide, thus bypassing any media queries you have for smaller screens.

Apple created this viewport tag to be placed in the head:

EDIT: While setting maximum-scale to 1 fixes the iOS orientation change bug it also limits users from manually zooming into your site. I have since removed , maximum-scale=1 from the viewport line above and updated my website to use this accelerometer-based javascript solution: iOS-Orientationchange-Fix. It's very tiny when minified and works like so:

How it works: This fix works by listening to the device's accelerometer to predict when an orientation change is about to occur. When it deems an orientation change imminent, the script disables user zooming, allowing the orientation change to occur properly, with zooming disabled. The script restores zoom again once the device is either oriented close to upright, or after its orientation has changed. This way, user zooming is never disabled while the page is in use.

I'm also taking a suggestion from normalize.css to set text-size-adjust: 100% to prevent iOS from changing text size after orientation changes and without having to set user-scalable=0 on the viewport5. Just be sure to never, ever set text-size-adjust to none — it has the nasty effect of messing up accessibility by preventing visitors from using browser zoom to increase text size.

Then we want to make sure less capable browsers like Internet Explorer 8 can make use of media queries. There are a myriad of ways to polyfill or gracefully degrade media queries on such browsers. I've decided to go with css3-mediaqueries.js as it supports em units.

Similarly to how you conditionally load JavaScript like html5shiv in your head, we load css3-mediaqueries.js in the same way:

WTF are Media Queries!?

Media queries are the lifeblood of any responsive website. They are used to conditionally6 load CSS for a media type (like screen, tv, print) based on at least one media feature expression. Well to be technically correct, the browser loads all of them regardless of whether they will be used. More on how to fix that later.

You're probably already familiar with them being used for screen width and resolution, but it can also be used to respond to other device characteristics like orientation, monochrome, pointer (presence and accuracy of a pointing device; i.e. use larger buttons/input fields for devices with inaccurate pointing devices to combat Fitts's law), hover and luminosity (soon).

For example, the media query below targets devices with viewport widths of at least 481px, which is pretty much the portrait width of most tablets and above. That's where the extra 1px comes into play so it doesn't overlap with another media query you may have of say 480px and below, or between 320px to 480px. This can all be a bit confusing at first.

Breakpoints 101

Those "ranges" of widths which you want to target differently are called breakpoints. But how do you know where to set those viewport ranges? Do you just set them up for a few devices?

This has been a rather large topic of discussion in the web development community. Typically you would make breakpoints for the iPhone and iPad and have your design conform to those viewports. The current trend is the opposite—your content and design should determine your breakpoints.

I started with the "regular" breakpoints. You know the ones: 320px (iPhone portrait), 480px (iPhone landscape), 768px (iPad portrait), 1024px (iPad portrait), and a desktop one for anything larger.

However, the max width of my primary content (I like to keep the measure7 under 75 characters) was awkwardly right in between 480px and 768px. It looked odd to display a condensed mobile header for browsers wide enough to show the full version. I changed the 768px breakpoint to 640px as a result.

As web developers like to say...

Start with the small screen first, then expand until it looks like shit. Time to insert a breakpoint!

Working with Media Queries

Generally speaking, with media queries you'll have a big ugly section on the bottom of your CSS where you set your breakpoints and manually define styles you want to override. If only someone slapped me when I first did this in 2010. This was wrong for a few reasons.

For one, in an ideal world you should not be overriding styles, only augmenting them. One code smell is if you see yourself "canceling" out or undoing styles you defined elsewhere. I must admit it's something I always forget to do when I start a new project and want to get to "it works!" as fast as possible. Bookmark this article and read it sometime: Code smells in CSS. Then spread the gospel with me.

Any CSS that unsets styles (apart from in a reset) should start ringing alarm bells right away.

What I really wanted to point out is the placement of all your media queries. Placing them at the bottom of your CSS breaks when you end up changing the original value and forget to change the appropriate media query style if necessary. That's where Sass saves the day.

Thanks to Sass 3.28@media bubbling and @content blocks, you can write ridiculously easy to use media query mixins. Yes, you can nest @media directives just like regular selectors and they will bubble up to the top level! Toss in a pinch of @content and that means you can write mixins like the ones below, as written by Anthony Short.

Now all of your media queries can be nested in the same place as the CSS, instead of at the end of the file or in another one entirely.

Skim through this tiny example of how I use these mixins for my header scss:

#masthead {
margin: 0 auto;
width: $main_content_width;

@include tablet-portrait-and-below {
text-align: center;
width: 100%;
}

// circle avatar
.stammy {
display: block;
width: 133px;
height: 133px;
background: url('http://turbo.paulstamatiou.com/assets/stammy.png') no-repeat 0 0;
// HiDPI media query mixin, I describe it a bit later
@include image-2x('http://turbo.paulstamatiou.com/assets/stammy@2x.png', 133px, 133px);
@extend %back-n-forth-anim;
&:active { background-position: 0 1px; }

@include tablet-portrait-and-up { float: left; }
@include tablet-portrait-and-below { margin: 0 auto; }
}

hgroup {
margin: 1em auto 0.6em;

@include mobile-landscape-and-below {
margin-bottom: 0;
.twttr { display: none; }
}
@include tablet-portrait-and-up {
float: right;
margin-top: 1.95em;
}

h1 {
font-size: 1.95em;

// using exact font-size #s here to get h1 and h2s to line up perfectly on both ends
// yeah this is probably a code smell.. but it works :)
@include tablet-portrait-and-up { font-size: 2.42718446601942em; } // equal to 40px @ 16px baseline * 103% font-size I use in main content areas
@include mobile-landscape-and-up { font-size: 2.25em; }
@include mobile-landscape-and-below {
font-size: 2.1em;
span {</spa

Show more