Join our HiFi mailing list to receive the latest news & updates

Automatic file minification on nginx

5 Comment(s) | Posted | by Eli Van Zoeren |

For our HiFi content management system we wanted to automatically combine and compress CSS and Javascript files. On complex sites, this minification can have a significant impact on page load times. You can read more details about the benefits and our template-side implementation in a previous post. Here I am going to discuss the technical details of how we set things up on our nginx server.

Getting Started

There is a very useful and stable Minify project on Google Code. Both CSS and Javascript minification libraries are included and they can easily be switched out if you prefer another library. The normal way to use Minify is to install it in your root directory and call its main file with a query string to specify the specific files that should be included: /min/?f=js/jquery.js,js/jquery.cycle.js,js/setup.js. However with this setup, even after caching the minified files server-side, the PHP interpreter is involved in delivering the file, which is relatively inefficient. Ideally, after a file is cached the first time nginx would serve the compressed file directly.

There is some sample code included in the Minify download that uses Apache url rewriting to take a more "normal"-looking url and point it to the Minified version. This was almost what we were looking for, except for two things: it requires a very specific url structure—with all JS and CSS files stored inside the Minify folder—and we are not using Apache. For HiFi, we decided on Nginx, as the configuration for running many sites off of the same code-base is much simpler. Nginx has a HttpRewriteModule, which is similar to Apache's mod_rewrite, so I figured there must be a way to replicate the Minify sample code in the new environment.

Aside: Template Tags

A quick preview of how HiFi handles stylesheets and scripts to give you a bit of context: We added two new tags to our templating language: {% css %} and {% js %}. These are designed to be used every time a script or stylesheet is included in a template, and they do everything needed to hook into the minification system. Each tag takes a comma-separated list of file names that will be included. For example, a standard js tag might be {% js 'jquery,jquery.cycle,setup' %}.

By default, these tags will output straight or tags for each file in the list. However, adding min as the last parameter of either tag will cause it to instead output a single tag containing a special url that will get the Minified version. This way, it is just a matter of adding and removing that single parameter to switch between development and production modes. You could even set up a global variable to change it for all the tags in a layout at once.

The url format for serving the minified file is scripts/jquery,jquery.cycle,setup_123456.mjs. There are three things to note here: The first is that we use the .mjs extension (or .mcss). That change is what tells Nginx to serve the Minified version. The second thing is the timestamp we are appending to the filename. Since we will be setting far-future expires headers (so visitors' browsers know to cache the files locally), we need a way to manually expire the files when we have changed them. Our template tag automatically resets the timestamp whenever the source files change, but you could also increment it manually. Finally, this script assumes that JS files will be in a folder called /scripts and CSS files will be in /styles. Eventually, it would be nice to be able to place files in any arbitrary location, but that makes things considerably more complicated.

How It’s Going to Work

For maximum speed, we are going to enable two different types of cacheing: of the minified files on the server and in the visitor's web browser. Whenever a particular combination of files is requested, nginx will check to see if there is already a cached copy. If there is—and there will be except for the first time after a source file is modified—the server will return that file directly, without involving the PHP interpreter at all. This operation is very fast, as it required almost no processing.

If nginx cannot find a cached copy of the file, it will pass the request off to our minification script. This code uses the Minify library to combine and compress the source files, returns the end result to the visitor's browser, and caches it for future use. It will actually cache two versions: one as plain-text and one that has been further compressed with gzip. Nginx uses content negotiation to determine which of the two should be returned to the browser.

What happens when you change a source file? Since we will be telling the visitor's browser to cache our asset files indefinitely, you will need to change the timestamp/version number on the file when you make updates. By doing that, you will invalidate both the server-side and the client-side caches and cause the files to be re-minified.

The Code

Nginx Configuration

Open up the nginx.conf file on your server. In the http block, add the following code:

# Logic for serving minified CSS & JS
location ~* \.(mjs|mcss)$ {
  set $domain      www.domain.com;        # Change this to your site's domain name
  set $root_fcgi   /var/sites/site_name;  # Change this to the public root folder of your site
  set $root_cache  $root_fcgi/cache;      # Change this to a folder in which to cache the minified files
  set $min_dir     /var/sites/min;        # Change this folder to wherever you put the Minify files
  
  include fastcgi_params;
  fastcgi_param SITE_ROOT $root_fcgi;
  fastcgi_param SCRIPT_FILENAME $min_dir/nginx-mininification.php;
  fastcgi_param PATH_INFO nginx-minification.php;
  fastcgi_param SERVER_NAME $domain;
  fastcgi_param CACHE_DIR $root_cache;
  
  root $root_cache;
  
  expires max;
	
  gzip_static on;   # You will need to have installed Nginx using the --with-http_gzip_static_module flag for this to work
  gzip_http_version 1.1;
  gzip_proxied expired no-cache no-store private auth;
  gzip_disable "MSIE [1-6]\.";
  gzip_vary on;
  
  # If there is not already a cached copy, create one
  if (!-f $request_filename) {
    root $root_fcgi;
    fastcgi_pass 127.0.0.1:9000;
  }
}

Make sure to set the four variables at the beginning to reflect the way your server is actually configured.

Minify and Cache

First, copy the Minify files to anywhere you like on your server. They do not need to be in a publicly-accessible location, since Nginx will be proxying requests to Minify behind the scenes. The Minify config.php file contains sensible defaults, but check it to be sure.

Create a new file inside the Minify directory, called nginx-minification.php. Paste the following code into it:

 $sources,
  'quiet' => true,
  'encodeMethod' => '',
  'lastModifiedTime' => 0
));
if (! $output['success']) {
  send404();
}

// Clear old cached files
if ($handle = @opendir($cachedir)) {
  while (false !== ($file = @readdir($handle))) {
    if ($file != '.' && $file != '..' && stristr($file, $filename)) {
      @unlink($cachedir . '/' . $file);
    }
  }
  @closedir($handle);
}

// Cache the output
error_reporting(0);
if (false === file_put_contents($cache, $output['content']) ||
  false === file_put_contents($cache.'.gz', gzencode($output['content'],9))) {
  echo "/* File writing failed. Your cache directory, {$cachedir}, must be writable by PHP. */\n";
  exit();
}

// And return it to the client
unset($output['headers']['Last-Modified'], $output['headers']['ETag']);
foreach ($output['headers'] as $name => $value) {
  header("{$name}: {$value}");
}
echo $output['content'];

Testing It Out

You should be ready to go. Try converting your script and style tags to use the new url format ("/scripts/jquery,setup_123.mjs" or "/styles/layout,typography,homepage_123.mcss"), making sure that the source files are in either the scripts or the styles folders. If it doesn't work, make sure all the paths in the Nginx configuration file are set correctly and that the cache folder exists. If you have suggestions or problems, please post them in the comments! I'm no expert on the ins and outs of nginx, so I am sure there are better ways to do certain things here.

User Experience and Technical timelines for loading a web page

0 Comment(s) | Posted | by Eli Van Zoeren |

We thought it would be interesting to look at the various things that happen as a web page loads, both technically and from the visitor's perspective. Although these processes happen in parallel and are connected, events on the two timelines won't necessarily match up exactly as I've show here. The actual loading speeds depend on many factors including page size, connection speed, web browser, and number of asset files. However, the order in which the steps happen will be as shown in most cases. If you want more detailed timing information for your particular site, I recommend starting with either the "Resources" tab in Webkit's Web Inspector or the "DOM" tab in Firebug for Firefox.

Future articles here on the HiFi blog will go into more detail about ways to tweak your website in order to optimize both the technical and user experience timelines.

Graphic of webpage loading process from technical and user perspectives.

References:

  1. Powers of 10: Time Scales in User Experience and Response Times: The Three Important Limits —Jakob Nielsen
  2. Reaction Time —Wikipedia
  3. Bing and Google Agree: Slow Pages Lose Users —Brady Forrest
  4. 5-Second Tests: Measuring Your Site's Content Pages —Christine Perfetti
  5. First Impressions - What Your Web Site Says About You (or your company) —Thanny Young

Why Minify?

4 Comment(s) | Posted | by Eli Van Zoeren |

HiFi has most of Yahoo’s best practices for high performance websites built in, right out of the box. In particular, our custom CSS and JavaScript handling system takes care of six items on the checklist with zero effort on the part of the front-end developer. Let’s take a look at these techniques, and how they improve the final page-load speed.

First, though, a preview of how HiFi handles stylesheets and scripts. We added two new tags to our templating language: {% css %} and {% js %}. These are designed to be used every time a script or stylesheet is included in a template, and they take care of all the details for you. Each tag is given a comma-separated list of file names that will be included. For example, a standard js tag might be {% js 'jquery,jquery.cycle,jquery.validate,setup' %}.

The Benefits

Choose over @import: Back when Netscape Navigator 4.0 was still in wide use, developers would often include their CSS files using , which would hide the stylesheet entirely from Netscape and its buggy CSS rendering. Fortunately Netscape is not an issue anymore, but certain other browsers have trouble with the @import statement. Using the @import rather than a standard element prevents the browser from starting to render the page until the stylesheet is completely downloaded, decreasing the apparent speed of the website. HiFi will always use the element for including stylesheets, so you don’t ever need to worry about this.

Add an Expires or a Cache-Control Header: Unless a file has changed since last time a visitor has seen it, there is no reason for their browser to download it again. Fortunately, the HTTP protocol includes an Expires header, which tells the browser “this file won’t change for X number of days, so don’t bother downloading it again until then”. HiFi automatically sets the expires header as far in the future as possible. So what happens when a developer actually has changed a stylesheet or script and you do want visitor’s browsers to download the new version? To work around that, the {% js %} and {% css %} tags add a timestamp to the end of the filename they write into the final HTML document. Some server magic redirects this new filename to the actual file, but since the timestamp changes every time the files themselves change, the visitor’s browser sees it as a new file and will ask the server for a fresh copy.

Gzip Components: Just as you can create a zip file to compress files on your desktop, a web server can compress files using a standard called gzip before sending them to the visitor’s browser. HiFi takes care of this by default, only gzipping files if the browser knows how to uncompress them.

Split Components Across Domains: Most browsers will not download more than two files at one time from any particular domain name. This is a holdover from a decade ago when servers and internet connections were both slower and it was an attempt to prevent one person from slowing everyone else down by retrieving a large number of files at once from a single server. Until the HTTP specification catches up with modern technology, we can trick browsers into getting more than two files at the same time by splitting the files across multiple subdomains. HiFi uses three subdomains for each site: The main "www" subdomain contains all the HTML files, the "ui" subdomain contains all the other files a front-end developer creates—stylesheets, javascript, and images—and the "files" subdomain contains the various images and other files that are uploaded to individual pages throughout the site. This way, five files can be downloaded in parallel (since, typically, only one HTML file is used for each page), potentially more than doubling the total speed of the pageload.

Minimize HTTP Requests and Minify JavaScript and CSS: Although spreading our files across three subdomains helps, it is still much faster to download one large file rather than several small ones. Most websites use a number of JavaScript files and many also have more than one stylesheet, so this is an obvious inefficiency to work on. Additionally, since both JS and CSS typically use a lot of whitespace for readability, even very basic minification (removing any unnecessary characters from the file) can result in massive reductions in file size. For example, the jQuery library goes from 155kb down to 24kb.

We use a customized implementation of the minify script to both combine and compress all the JS and CSS files used on each page. Since compressing these files makes them difficult to read, we needed a way to leave this feature off while developing a site and then turn it on in a single step when the site is ready to go live. Both of our asset tags take an optional “min” keyword, which does everything needed to combine and compress the asset files. For instance, if you had three CSS files that should be combined, you would use {% css 'reset,typography,layout' min %}. (The final compressed file will always keep the sources files in the same order you list them in, so cascading styles will work properly.)

Screenshot of minified CSS file

Taking the GetHiFi.com website for example, before minification the scripts and stylesheets came in at 63.36kb spread over seven HTTP requests (excluding jQuery, which we are hotlinking from Google's CDN). With the JavaScript and CSS files minified, their size is almost halved to 36.92kb and two requests.

A future post will go into more detail on the mechanics of our minification technique, but what really matter are the results.

Use HTML5 form attributes now with nmcFormHelper plugin for jQuery

12 Comment(s) | Posted | by Eli Van Zoeren | |

Inayaili de León's post last week on 24 ways reminded me about some of the new attributes that HTML5 will be adding to the <input> element. In addition to the flashy new input types (search, color, date, etc.), there are several more mundane, but just as useful, new attributes that can be used on the input types we already have. I created a small jQuery plugin that checks if these attributes are supported natively, and if not it adds support via JavaScript. See an example, or continue reading for the full explanation.

Placeholder

A common trick is to set the value of a text input to a short hint about the type of information that should be entered there. Then a couple lines of JavaScript are used to clear that value when the user clicks or tabs into the input. Although there are usability issues with this technique, its convenience is hard to resist. The HTML5 spec includes a new placeholder attribute that creates the same effect without the usability problems.

To Use:

<input type="text" name="name" id="name" placeholder="Enter your first and last names" />

Autofocus

When the primary purpose of a page is the form—a login page, for instance—it is helpful to start out with the focus on the first input, so the user can just start typing. You can use the new autofocus attribute on the first element to do that. (Be careful, though: setting autofocus on a non-essential form, like a comment form, can be very annoying.)

To Use:

<input type="text" name="name" id="name" autofocus="autofocus" />

Required

Setting the required attribute prevents the form from submitting unless the input has some value.

To Use:

<input type="text" name="name" id="name" required="required" title="This is required" />

Pattern

The pattern attribute lets you set a regular expression pattern that must be matched by the input value before the form can be submitted. This is a very powerful tool, allowing for complex validations. Our HiFi RexExp Tool will help you create regular expressions that test for anything from credit card numbers to urls. You should also read the HTML5 spec on the topic, as there are a few caveats. In particular, be aware that the pattern matches the entire value by default, and you should not wrap the expression in forward slashes.

To Use:

<!-- Only accept US zip codes -->
<input type="text" name="zip" id="zip" pattern="(^d{5}$)|(^d{5}-d{4}$)" title="US zip codes only" />

Using the Plugin

Some or all of these new attributes are supported by both Safari and Opera. But Firefox and Internet Explorer do not support them natively at this time. To be safe, the nmcFormHelper plugin checks for availability of each attribute (using a technique suggested by Mark Pilgrim) and, if it doesn't exist, uses JavaScript to emulate support.

At a minumum, you just need to include my script and place a single line in your $(document).ready() block: nmcFormHelper.init();. That will set up all of the new attributes, and you're ready to go.

Options

  • Placeholder styling: The placeholders are styled to match the native placeholder functionality in Safari. If you wish to set a different style, you can set nmcFormHelper.placeholder.styling = {'color':'red','font-style':'italic'}; or whatever look you choose.
  • Validation functions: The required and pattern attributes will prevent form submission and display an error if the value does not exist or does not match the pattern. By default, a new label will be added after each invalid input, containing the title text of the input. But you can write a function to do anything you want! Just override the functions nmcFormHelper.validation.showErrors and nmcFormHelper.validation.hideErrors (the latter function should undo the former). Both functions will be passed a jQuery object containing all the elements that are affected. Each element will also have one or both of the classes "requiredError" or "patternError" set.

Bonus!

Since Internet Explorer does not support CSS attribute selectors, it can be hard to style a text input, for instance, without screwing up your buttons, or vice-versa. So the plugin also adds a class to each of your inputs containing "input-" and the type of the input (i.e. "input-submit"). That way, you can style them individually.

See an example

Get the Code

Full, commented version

Minified version

Easy drop down menus with nmcDropDown

22 Comment(s) | Posted | by Eli Van Zoeren | |

nmcDropDown in use on ncbowd.comSince I released my nmcDropDown plugin for jQuery two weeks ago, several people have been asking for a simple example of how to use it. Althought the plugin takes care of all the behavior automatically, you still need to style and format the menu using CSS. In this post I will demonstrate two simple menu styles that use nmcDropDown.

The bare minimum

The examples below have styling and effects to make them look nice, but first I will show you the minimum you need to get your menus running. First off, you need a set of nested lists and link to create the structure of your menus:

<ul id="nav">
  <li><a href="#">Item One</a>
    <ul>
      <li><a href="#">Sub-menu Item 1</a></li>
      <li><a href="#">Sub-menu Item 2</a></li>
      <li><a href="#">Sub-menu Item 3</a></li>
    </ul>
  </li>
  <li><a href="#">Item Two</a>
    <ul>
      <li><a href="#">Sub-menu Item 1</a></li>
      <li><a href="#">Sub-menu Item 2</a></li>
      <li><a href="#">Sub-menu Item 3</a></li>
    </ul>
  </li>
  <li><a href="#">Item Three</a>
    <ul>
      <li><a href="#">Sub-menu Item 1</a></li>
      <li><a href="#">Sub-menu Item 2</a></li>
      <li><a href="#">Sub-menu Item 3</a></li>
    </ul>
  </li>
</ul>

Now you need a bit of CSS to put everything in its correct place. This menu isn't going to look nice, but it will give you a starting point from which you can match your site's overall aesthetic.

#nav { float: right; height: 30px; }
#nav li { float: left; position: relative; }
#nav li a { display: block; padding: 5px 10px; line-height: 20px; }
#nav li ul { display: none; position: absolute; top: 30px; left: 0; width: 120px; background: #fff; }
#nav li:hover ul { display: block; }
#nav li ul li { float: none; }
#nav li ul li a { display: inline-block; }
#nav li ul li a { display: block; }

The most important thing here is to set #nav li to position: relative; and #nav li ul to position: absolute; to the submenus are aligned with their parent item. In the interest of accessibility, we are also hiding the submenus in the CSS (#nav li ul { display: none; }) and showing them with their parent li is hovered over (#nav li:hover ul { display: block; }. That way, if JavaScript is disabled, the menus will still work in every browser but Internet Explorer 6. JavaScript is always required for drop-downs to work in IE6, unfortunately. Speaking of which, did you notice the oddity at the end where we declare the links as display: inline-block and then re-declare them at display: block? That is the only concession we need to make to Internet Explorer bugs—it removes the extra space IE6 inserts between list items, as discovered by Roger Johansson.

This CSS will create a horizontal menu with drop-downs, but by changing a few lines, you could just as easily make it a vertical menu with fly-outs.

Finally, the JavaScript to hook up the nmcDropDown script. Make sure you also paste the nmcDropDown script itself (and the HoverIntent plugin, if you wish to use it) into the JavaScript file.

$('#nav').nmcDropDown();

Makin' it Pretty

Screenshot of sample page

Now that we have a functioning menu, we can start styling it. I have created an sample page with two different examples using nmcDropDown.

The first—in the top-right of the page—is based on the simplified example above, just with additional CSS to style the menu bar and drop-downs. I also added an additional parameter to the call in JavaScript to make the menus slide down instead of fading in: $('#nav').nmcDropDown({show: {height: 'show', opacity: 'show'}});.

The second example is a little more interesting, as it's actually not a menu at all. In the right-hand sidebar, I am using nmcDropDown to create an informational panel with four heading that, which clicked on, each reveal a bit of text. To do this, I replaced the second level of <ul>s in my HTML with paragraphs. I then used the following CSS to line arrange everything vertically:

#sidebarNav { padding: 10px 0; background: #ccc; border: 1px solid #bbb; -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; }
#sidebarNav li { border-top: 0 solid #ccc; }
#sidebarNav li:hover, #sidebarNav li.open { background: #bbb; }
#sidebarNav li a { display: block; padding: 5px 10px; line-height: 20px; color: #444; font-weight: bold; text-decoration: none; }
#sidebarNav li p { display: none; padding: 5px 10px 10px; color: #444; border-top: 1px dashed #aaa; }
#sidebarNav li:hover p { display: block; }

Finally, in the JavaScript I told nmcDropDown to activate on click rather than hover and to look for my paragraph instead of the unordered-list it usually expects:

$('#sidebarNav').nmcDropDown({
  trigger: 'click',
  submenu_selector: 'p',
  show: {height: 'show'},
  hide: {height: 'hide'}
});

Please look at the sample page to try these out, and view source to examine the code in more detail. If you find another creative way to use nmcDropDown, please link to it in the comments.

nmcDropDown: A Drop-Down Menu Plugin for jQuery

34 Comment(s) | Posted | by Eli Van Zoeren | |

ncbowd

In my post on using template site to save time, I included a quick jQuery plugin for creating drop-down menus. Although that code has served me well I thought it was time to revisit it and make it more flexible and bullet-proof. This is the updated version that I will be including in my blank site template going forward

I found that I was still having to modify my plugin code to accomodate variations in interaction design. Since I sometimes wanted the menus to only appear whe n the y are clicked—rather than on hover—now that is an option. Also, when the top-level menu items are smaller (or when the sub-menus fly out from the side) the usability can be vastly increased by Brian Cherne's wonderful hoverIntent plugin. HoverIntent keeps the sub-menu from disappearing if your pointer leaves the edge for a split-second. This version will use hoverIntent automatically if you have it; otherwise it will revert to the standard hover event. I recommend copying the minified version of hoverIntent into the bottom of your main JavaScript file. It is quite small and including it in your main file will save an http request and speed up your page loading time.

Usage

You should create your navigation as a nested unordered list. Linking the top-level items is optional and will not change the working of the plugin. You should style your menu first with css. How you do that is up to you, but you should set the sub-menus to display: none (this is actually optional, but will prevent the sub-menus from flashing on page-load).

<ul id="nav">
    <li>About
        <ul>
            <li><a href="#">Profile</a></li>
            <li><a href="#">Board of Directors</a></li>
            <li><a href="#">Contact Info</a></li>
        </ul>
    </li>
    <li><a href="#">Products</a>
        <ul>
            <li><a href="#">Widgets</a></li>
            <li><a href="#">Doohickeys</a></li>
            <li><a href="#">Thing-a-ma-bobs</a></li>
        </ul>
    </li>
</ul>

The nmcDropDown plugin comes with sensible defaults, so you can just call it on the <ul> element that contains your navigation. The plugin will check each menu item to see if it has a sub-menu and set it up as a drop-down if it does.

$('#nav').nmcDropDown();

Configuration

If you want more control over the way your menu works, there are a number of configuration options available to you. Pass an object containing any that you want to change in as the first parameter of the nmcDropDown() call.

$('#nav').nmcDropDown({trigger: 'click'});

Available Options:

  • trigger: Event on which to show or hide the sub-menu, can be 'hover' or 'click'. ('hover')
  • active_class: Class to give open menu items, useful for styling. ('open')
  • submenu_selector: The element immediately below the top-level list-items containing the sub-menu. ('ul')
  • show: Effect(s) to use when showing the sub-menu. ({opacity: 'show'})
  • show_speed: Speed of the show transition. (300)
  • show_delay: Delay before the sub-menu is shown (requires HoverIntent). (50)
  • hide: Effect(s) to use when hiding the sub-menu. ({opacity: 'hide'})
  • hide_speed: Speed of the hide transition. (200)
  • hide_delay: Delay before the sub-menu is hidden (requires HoverIntent). (50)
  • fix_IE: This will attempt to fix IE 6 and 7's problems with z-index, where the sub-menu appears behind the body of the page. Set to false if it is interfering with your other styling. (true)

Example

We recently launched a website for North Carolina Business Opportunity and Workforce Development, a program that helps women, minorities, and small businesses secure NCDOT contracts. I created drop-down menus that use the nmcDropDown plugin with custom show and hide animations for an interesting effect. The show animation is {opacity: 'show', top: '-=10px'} (hide is exactly the reverse), which fades the menu in while sliding it up from below.

Update: I have also created and written up an example of how to use nmcDropDown, including HTML and CSS.

Download

Full, commented version (3.5kb)

Minified version (1.3kb)

Lazy Development: Using Templates to Save Time

6 Comment(s) | Posted | by Eli Van Zoeren | |

One of my high school teachers always used to tell me that laziness is the mother of invention. Nowhere is this more true than in web and software development. Coders, by nature, are a lazy bunch and are always looking for ways to save time and effort. The Don't Repeat Yourself principal is a prominent expression of this philosophy: the same information should never be repeated more than once in your code.

Even though all of the work we do for our clients is unique and individual, there are many elements that are repeated throughout every project. So it only made sense for me to create a template site folder with everything I need to get started on a new project. This saves me time, which saves our clients money. I am going to describe below what is in my default template. This will almost certainly not be what should be in your default template—that will depend on your personal workflow, CMS, and stylistic preferences—but it should give you some inspiration for things to consider including.

Overview

My template folder contains two sub-folders. Static, which I will discuss in the following sections, contains my default html, css, and javascript files. I code everything to a static templates before putting them on our CMS, since I find it faster and easier to catch problems at that point. I usually create the CMS templates directly on our server, but if you prefer to have a local copy of your CMS, you could include an empty installation of it in another sub-folder here. The other sub-folder I start with is mockups. In there I have the Photoshop template from the 960 Grid System. Although I don't use their css framework, I find having a pre-set grid is helpful whenever I start a new design. Since many of the designs I am coding come from our agency partners, more often than not I just delete that Photoshop template and copy the mockups we've been given into this folder.

HTML

In my static folder, there is one html file: interior.html. I have found that it is almost always better to code an interior page first, and then move on to the homepage. After all, there are many times more interior pages on the average site, and they are where visitors will spend most of their time (hopefully!). Once I have the interior page coded and browser-tested, I duplicate it to index.html and adapt it to the homepage. On more complicated sites there might be more than one interior page design, so I again start with the most generic of them and then duplicate and adapt.

I include plenty of dummy-text in my default iterior.html to give me something to style. Make sure you have a couple of sub-headings, an unordered list, a blockquote…anything that is likely to have individual styles. I also start with a variety of things I might need in the <head> section: potentially useful meta-date (including the viewport meta-tag for the iPhone), conditional comments for IE 6 and 7, a link to the RSS feed, and some default scripts.

My interior.html template

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">

<head>
    <title>Site Title</title>
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
    <meta name="DC.language" content="en" />
    <meta name="DC.creator" content="{author}" />
    <meta name="DC.publisher" content="New Media Campaigns" />
    <meta name="viewport" content="width={pixel width}" />

    <link rel="alternate" type="application/rss+xml" title="RSS feed" href="" />
    <link rel="shortcut icon" type="image/png" href="{$base_url}images/favicon.png" />

    <link href="css/all.css" rel="stylesheet" type="text/css" media="all" />
    <!--[if IE 7]><link href="css/ie7.css" rel="stylesheet" type="text/css" media="all" /><![endif]-->
    <!--[if IE 6]><link href="css/ie6.css" rel="stylesheet" type="text/css" media="all" /><![endif]-->

    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3/jquery.min.js" type="text/javascript"></script>
    <script src="js/site.js" type="text/javascript"></script>
    <!--[if IE 6]><script src="js/DD_belatedPNG.js" type="text/javascript"></script><script type="text/javascript">DD_belatedPNG.fix('');</script><![endif]-->
</head>

<body>
    <div id="wrapper">

        <div id="header">
            <h2 id="logo"><a href="{$base_url}"></a></h2>
            <a href="#content" id="skip">Skip to content...</a>

            <ul id="nav">
                <li><a href="{$base_url}"></a></li>
                <li><a href="{$base_url}"></a></li>
                <li><a href="{$base_url}"></a></li>
            </ul>
        </div>

        <div id="content">
            <h1>Page Title</h1>

            <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam eu metus velit. Sed felis sem, venenatis at ullamcorper a, ornare sit amet nunc. Nunc id massa turpis. In dolor dui, ultricies sit amet mattis vel, cursus non eros. Pellentesque ut mi arcu, ac scelerisque dolor. Phasellus neque nibh, tincidunt quis mattis vel, scelerisque et metus.</p>

            <p>Nullam fermentum pulvinar porta. In hac habitasse platea dictumst. Phasellus non nunc diam, in lacinia dolor. Cras nec eros in metus elementum rhoncus. Phasellus viverra malesuada est, eu pharetra sapien mollis at.</p>

            <h2>Nam accumsan dui at neque malesuada</h2>

            <p>Curabitur vitae neque a lacus malesuada porta. Sed non condimentum risus. Nam malesuada, erat quis facilisis consectetur, neque felis varius ante, vel egestas dui urna a diam.</p>

            <ul>
                <li>Aenean tincidunt, nibh vel sodales malesuada, nunc sapien consequat dui, eu auctor ante tellus eu purus.</li>
                <li>Sed ut leo metus, in interdum libero. Proin sagittis augue at justo vehicula commodo interdum libero ullamcorper.</li>
                <li>Vivamus quis felis at ligula sollicitudin adipiscing. Sed semper faucibus ligula, sit amet luctus mi interdum at. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.</li>
            </ul>

            <blockquote>Sed et tortor ante, vel mattis dui. Nam accumsan dui at neque malesuada id tincidunt quam faucibus. Maecenas tempus risus quis arcu suscipit eget ullamcorper diam scelerisque. Proin eleifend mauris quis magna pulvinar eget tempor massa fringilla. Aliquam erat volutpat. Praesent gravida luctus diam, eu scelerisque elit rutrum vel.</blockquote>

            <h3>Pellentesque convallis magna ut tortor mollis</h3>

            <p>Sed risus dolor, imperdiet nec pulvinar in, accumsan ac ipsum. Nullam adipiscing iaculis tempus. Nulla ornare neque non est ornare ac ornare ligula pretium. Donec lacinia condimentum ligula, in posuere arcu aliquam quis. Donec in nunc turpis. Proin nec leo in nulla ullamcorper laoreet. Cras ac justo elit. Duis a mauris purus. Sed eleifend urna non sapien porta ornare scelerisque massa facilisis. Duis mollis, elit et posuere imperdiet, elit sem tempor justo, non consectetur eros odio id urna.</p>
        </div>

        <div id="footer">
            <p id="copyright">Copyright &copy; 2009 by </p>
        </div>

    </div>
</body>
</html>

CSS

I begin my main all.css file with Eric Meyer's reset stylesheet, and you probably should too. I have it compressed onto one line to save space:

/* Eric Meyer's Reset styles */
html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td{margin: 0;padding: 0;border: 0;outline: 0;font-size: 100%;vertical-align: baseline;background: transparent;}body{line-height:1;}ol,ul{list-style:none;}blockquote,q{quotes:none;}:focus{outline:0;}ins{text-decoration:none;}del{text-decoration:line-through;}table{border-collapse:collapse;border-spacing:0;}

Next, I reset my base font-size using a percentage of the browser-default 16 pixels. From this point on, I use ems to adjust the size of particular elements as needed. I also set a default line-height here, which helps ensure a consistent baseline-grid. While I'm at it, I set a default font on both the body and a variety of form-elements, which tend to have trouble with inheritance.

body {
    font-size: 87.5%;      /* Reset base size to 14px */
    line-height: 1.429em;  /* Reset line-height to 20px */
    }

body, input, textarea, select, label {
    font-family: Helvetica, Arial, Geneva, sans-serif;
    }

Next, I have empty declarations for many of the common html elements. Having them all there to begin with keeps me from forgetting to style anything. You can construct your own list of the element use typically use, but one default declaration that I alway keep in there is for the img element. Internet Explorer 6 does a terrible job of resizing images unless you set img { -ms-interpolation-mode: bicubic; }.

Finally, I list IDs of everything that I have in my default html template—#header, #nav, #content, #footer, etc. By organizing them all in the template, I have a good starting point for styling the final site.

JavaScript

My default html template includes calls to two libraries that I use on almost all my projects. The first is the Google Code-hosted copy of jQuery. jQuery makes my life easier in lots of ways, and the Google-hosted version is faster to load (and more likely to be cached already) than a local copy. The second script I start with is DD_BelatedPNG, which fixes png alpha transparency in IE6. Although there are many png-fixing scripts out there, this is the least-buggy one I've found and one of the few that supports background-position and background-repeat. I keep a copy of BelatedPNG in the scripts folder of my template site.

Finally, I use a default site.js script with a few commonly-used jQuery mini-plugins. I don't use these on every site, but often enough that I don't want to re-write or hunt them down every time. If I don't need them for the project I'm working on I just delete them. The particular scripts I include in site.js changes from month to month, but here is what I've got in there right now:

$(document).ready(function() {

    // Open external links in a new window
    $('a[href^="http://"]').attr("target", "_blank");

    // Clear textboxes on focus
    $('.autoclear').autoClear();

    // Setup the nav drop-downs
    $('#nav').dropDown();

});


// Clears the default text when an input receives
// focus and reinstates it if it is left blank
(function($) {

    $.fn.autoClear = function() {	
        return this.each(function() {
            $(this).focus(function() {
                if( this.value == this.defaultValue ) {
                    this.value = "";
                }
            })
            .blur(function() {
                if( !this.value.length ) {
                    this.value = this.defaultValue;
                }
            });
        });
    };

})(jQuery);


// Sets up an elements's children as drop-down menus
(function($) {

	$.fn.dropDown = function(options) {

		// build main options before element iteration
		var opts = $.extend({}, $.fn.dropDown.defaults, options);

		// iterate each matched element
		return this.each(function() {
			menu = $(this);

			// Show the submenus on click
			menu.children('li:has(ul)').hover(
				function() {
					$(this)
						.addClass(opts.active_class)
						.children('ul').animate(opts.show, opts.show_speed);
				},
				function() {
					$(this)
						.removeClass(opts.active_class)
						.children('ul').animate(opts.hide, opts.hide_speed);
				}
			).children('ul').hide();
		});
	};

	// Default options
	$.fn.dropDown.defaults = {
		show: {opacity: 'show'}, 	// Effect to use when showing the sub-menu
		show_speed: 300,			// Speed of the show transition
		hide: {opacity: 'hide'}, 	// Effect to use when hiding the sub-menu
		hide_speed: 200,			// Speed of the hide transition
		active_class: 'open'		// Class to give open menu items
	};

})(jQuery);

Update: Since I wrote this post I have updated my Drop-Down Menu script to be more flexible and bullet-proof.

Conclusion

Don't just copy the code I've included here. You should look back through the last few websites you have created and pull out anything that you find yourself using frequently. When in doubt, include more template code rather than less. Remember that it is always easier to delete code you don't need for a particular project than to re-write code that isn't in your template site.

Websites like Wine: CSS Techniques to make a site Better With Age

4 Comment(s) | Posted | by Eli Van Zoeren | |

Some things in life get better with age: fine wine, cast-iron pans, Juliette Binoche. But websites don't usually fall into that category. I would provide examples from my portfolio, but fortunately they have all been lost. Trust me, don't try to find them. It doesn't need to be that way, though. I'm not going to tell you how to create designs that won't look dated five years from now, but with careful planning you can write code that will look even better in the future than it does today.

Do Your Research

Familiarize yourself with the W3C specs for HTML and CSS. Sure they may not be perfectly implemented (yes, I'm looking at you Internet Explorer) but it is a safe assumption that browsers will continue to support the specs more and more faithfully. In particular, you should be reading up on HTML5. Once you know how things should work you can start learning how the make them actually work. I will go through a few easy things you can add now that will work on one or more current browsers and will be supported by more browsers in the future. Note: For simplicity, I am listing compatibility with Safari, Firefox, and Opera. Generally if Safari supports something, Chrome will too.

HTML

The main thing you can do to future-proof your HTML is to learn HTML5 and start using it now. HTML5 includes a number of new structural elements that will make your code clearer and add semantic meaning. These include header, footer, section, nav, and article. You can find a complete list of differences between HTML 4.01 and HTML5 on the W3C site. However, since current browsers don't actually support HTML5 there are some tricks you need to know in order to trick them into displaying the new elements correctly. Adding display: block to the CSS declaration for the element will take care of the "good" browsers, including current versions of Safari, Firefox, and Chrome. For Internet Explorer we need to use a bit of JavaScript that creates (but doesn't actually place in the document) an element of the type we want to style. For some reason, this convinces IE to recognize that element as valid. For example, we could include the following inside a conditional comment—document.createElement('header');—and repeat it for each HTML5 element we want to be able to use. You can read more about this technique (and how to make the new elements work in Firefox 2) at HTML5 Doctor.

CSS

There are many exciting things happening with CSS right now. Webkit (Safari & Chrome) and Gecko (Firefox) are adding new properties from the CSS3 draft specification all the time. In many cases they namespace the properties in case the final spec is different than the draft they based their implementation on. For instance the border-radius property is represented by -webkit-border-radius in Safari and Chrome and -moz-border-radius in Firefox. For best forward-compatibility, you should list the available namespaced properties in your stylesheet and follow them with the actual property according to the current draft. So you might have a style declaration like: .box { -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; }. Redundant, yes, but this way as new versions of other browsers are released they will begin to see the effect as well. Here are a few of the most useful properties that you can start using now:

Shadows

CSS3 allows shadows to be generated behind both text and block-level elements. The text-shadow property is actually part of CSS2 and is widely supported now (Safari 3+, Firefox 3.1+, Opera 9.5+). You don't need the "-webkit" and "-moz" prefixes on the one: text-shadow alone will work in all supported browsers. I demonstrated some creative uses of css text-shadow in a previous blog post.

Box shadow uses identical syntax, but applies the shadow to the outline of the element. It is less widely supported (Safari 3+ and Firefox 3.5+) and requires the browser-specific prefixes. We used this in the pop-up author information boxes at the top of our blog posts; an ideal case in that the shadow adds some nice visual flair but isn't necessary for the design to work.

.item {
    text-shadow: 1px 1px 2px #222;
    -webkit-box-shadow: 3px 4px 8px #222;
    -moz-box-shadow: 3px 4px 8px #222;
    box-shadow: 3px 4px 8px #222;
    }

Border-Radius

We also used this one in the author-info popups. Border-radius adds the rounded corners that are so beloved by the Web 2.0 crowd. Safari 3.1+ and Firefox 3+ support border-radius, each with their prefixed versions.

.item {
    -webkit-border-radius: 5px;
    -moz-border-radius: 5px;
    border-radius: 5px;
    }

Background-Size

This one isn't being used much, partly because it won't degrade as well in non-supporting browsers and partly because it is currently only supported by Safari/Chrome and Opera. However the Firefox team recently announced that it will be included in version 3.6. You can use background-size to stretch or shrink a background image, either to a particular pixel size, or as a percentage of the element's dimensions (not the image's dimensions). You can also choose whether the image will be scaled proportionally or not.

.item {
    background-image: url(image.jpg);
    -webkit-background-size: 25% auto;
    -moz-background-size: 25% auto;
    -o-background-size: 25% auto;
    background-size: 25% auto;
    }

Note that we are also using the property with a "-o" prefix for Opera. If you decide to scale your background make sure it looks fine without the scaling, since Firefox won't support it until 2010 and IE support make take years.

Transitions

I showed how to use CSS transitions to make a cross-browser drop-down menu in my last blog post: Nicer Navigation with CSS Transitions.

Font-Face

There has been a lot of activity just recently on the subject of using non-web-safe fonts on the internet. Some people see hosted services like TypeKit and Kernest as the way forward with web typography. Others are waiting for the browser makers and type foundries to work out their differences and come up with a unified standard for font embedding. In the mean time, there are a few fonts out there with licenses that support direct embedding with the @font-face property. Using a special block in your stylesheet, you can load the font file and make it available in your font-family declarations.

@font-face {
    font-family: "Font Name";
    src: url(/fonts/font-file.otf) format('opentype');
    }
.item {
    font-family: "Font Name", Helvetica, Arial, sans-serif;
    }

As you would expect, this doesn't work in all browsers. Chrome has font linking disabled by default, but you can use the --enable-remote-fonts command-line switch to turn them on. Opera won't support @font-face until version 10. And Internet Explorer requires that the font files be in a format, EOT, that no other browser supports. Fortunately, Jon Tan has a nice write up on using conditional comments to make IE play nice with the other browsers.

More @font-face resources:

Final Note

As you can see, there are plenty of things to look forward to in front-end coding, and no reason not to start using them now. But since these are not yet finalized, always be sure to test in as many browsers as possible. And if you have a favorite upcoming element or property that we can start using now, please share it in the comments!

Nicer Navigation with CSS Transitions

21 Comment(s) | Posted | by Eli Van Zoeren | |

Newer versions of Apple's Safari web browser (and Google Chrome, which is also based on Webkit) support a vendor-specific implementation of the CSS3 Transition property. CSS Transitions are a very powerful effect that can eliminate the use of JavaScript for many common effects.

Quick Overview

There are three properties that make up the transition: -webkit-transition-property, -webkit-transition-duration, and -webkit-transition-timing-function. Additionally, there is a shorthand property that combines the three: -webkit-transition.

-webkit-transition-property: This specifies which properties of the element will be animated. If it is set to all every available property will be animated, otherwise you can choose to animate one or more individual properties and leave the rest as they are.

-webkit-transition-duration: The amount of time from the beginning of the transition to the end. This is specified in seconds: 1s, 0.5s, etc.

-webkit-transition-timing-function: There are a number of timing functions—mathematical models for how the transition takes place—that you can choose from. These include ease, linear, ease-in, ease-out, and ease-in-out as well as the ability to create your own with cubic-bezier. A detailed explanation of these modes is beyond this article, please experiment with them or see the article on The Art of Web for more detail.

-webkit-transition combines all three into a handy shortcut: -webkit-transition: all 1s ease;

A Simple Example

a { background: #fff; color: #aa0000; padding: 3px; -webkit-transition: all 1s linear; }
a:hover { background: #220077; color: #fff; }

This is a link

When you hover over the link above you will see the text fade to white and the background fade to purple. (One of the great things about using Transitions in this way is that they degrade gracefully. If you are using a browser other than Safari you will see a normal hover effect with no transition.)

Navigation

We can use CSS Transitions to create a very nice navigation menu without JavaScript (well, maybe a little for Internet Explorer). We will start off by creating the menu using the standard CSS properties, to be sure it will degrade nicely in browsers other than Safari.

HTML

<ul id="trans-nav">
    <li><a href="#">Home</a></li>
    <li><a href="#">About</a></li>
    <li><a href="#">Products</a>
        <ul>
            <li><a href="#">Widgets</a></li>
            <li><a href="#">Thingamabobs</a></li>
            <li><a href="#">Doohickies</a></li>
        </ul>
    </li>
    <li><a href="#">Contact</a></li>
</ul>

CSS:

#trans-nav { list-style-type: none; height: 40px; padding: 0; margin: 0; }
#trans-nav { list-style-type: none; height: 40px; padding: 0; margin: 0; }
#trans-nav li { float: left; position: relative; padding: 0; line-height: 40px; background: #5a8078 url(nav-bg.png) repeat-x 0 0; }
#trans-nav li:hover { background-position: 0 -40px; }
#trans-nav li a { display: block; padding: 0 15px; color: #fff; text-decoration: none; }
#trans-nav li a:hover { color: #a3f1d7; }
#trans-nav li ul { opacity: 0; position: absolute; left: 0; width: 8em; background: #63867f; list-style-type: none; padding: 0; margin: 0; }
#trans-nav li:hover ul { opacity: 1; }
#trans-nav li ul li { float: none; position: static; height: 0; line-height: 0; background: none; }
#trans-nav li:hover ul li { height: 30px; line-height: 30px; }
#trans-nav li ul li a { background: #63867f; }
#trans-nav li ul li a:hover { background: #5a8078; }

Notice we have a nice drop-down menu under "Products" and hover state on the top level menu items.

Enhance It!

With just four lines of CSS and without changing anything in other browsers, we can now make the menu a little nicer in Safari.

#trans-nav li { -webkit-transition: all 0.2s; }
#trans-nav li a { -webkit-transition: all 0.5s; }
#trans-nav li ul { -webkit-transition: all 1s; }
#trans-nav li ul li { -webkit-transition: height 0.5s; }

You might have noticed a few oddities in our original CSS. For instance, instead of hiding the submenu by settings display:none we set the height of all the sub-menu items to 0. That was in preparations for our slide-down transition. Since you can't apply a transition to display we need to find a workaround. We are also animating the hover color on the links and the background image position to get the nice fade between the normal and "pressed" states.

If you want to learn more about the details of CSS transition, see the W3's specification. There is a lot of room for creativity with this, so please share any unique transitions you come up with!

UPDATE!

Through a combination of extra css styles and JavaScript, you can make the menus work well in Internet Explorer, too! First, for all versions, you will need to hide and show the sub-menus using display: none rather than by setting the height of the menu items to zero, which leaves an ugly block of the menu's background color.

#trans-nav li ul { display: none; }
#trans-nav li:hover ul, #trans-nav li.over ul {display: block; }
#trans-nav li ul li { height: 30px; line-height: 30px; }

(Obviously, this should be in a separate stylesheet that is called with conditional comments, so it only affects IE.)

That will fix things for IE 7 and 8. Since IE 6 doesn't support the :hover pseudo-element, we need a bit of JavaScript (shamelessly stolen from the Suckerfish Dropdowns method) to fake it.

startList = function() {
    if (document.all && document.getElementById) {
        var navRoot = document.getElementById("trans-nav");
        for (i=0; i<navRoot.childNodes.length; i++) {
            var node = navRoot.childNodes[i];
            if (node.nodeName=="LI") {
                node.onmouseover=function() {
                    this.className+=" over";
                }
                node.onmouseout=function() {
                    this.className=this.className.replace(" over", "");
                }
            }
        }
    }
}
window.onload=startList;

And there you have it! The above demos don't work, in IE, since TinyMCE keeps messing with my code, but you can see the final version.

Controlling typographic weight and contrast with text-shadows

0 Comment(s) | Posted | by Eli Van Zoeren | |

Face it, we don't have a lot of control over typography on the web. We have about a half-dozen fonts to pick from, we don't know what size or exactly what color the type will be, and the exact same type will render very differently on a Mac than a PC. But the increasing browser support of the CSS text-shadow property is giving us more fine-grained control of text weight and contrast.

Text shadow has been supported in Safari since version 1.1 (as well as other Webkit-based browsers like Google Chrome) and in Opera since version 9.5. With the release of Firefox 3.5 last week, all the major browsers aside from Internet Explorer support shadows. Fortunately, since we will only be making subtle adjustments to our typography, it isn't a big deal even if the viewer is using a browser that doesn't support shadows. They will simply see the default look.

Typographic Weight

Normally, there are two font weights on the web: normal and bold (in theory there are a range of weights from 100 to 900, but there is little practical support for that). However, we can fake an intermediate text weight by setting the text-shadow color to the text color, and setting the blur to zero.

Normal text / Bold text / Text with text-shadow: 0 0 0 #000;

We can also reverse the trick and make the text a bit lighter with nearly-transparent shadow with 1 pixel of blur behind the text. I first saw a similar trick on Wilson Miner's site, although he has since removed it. The version using alpha transparency was developed by Komodo Media. Unfortunately, this technique is a little less predictable in browsers other than Safari, particularly in the current version of Chrome which actually darkens the text.

This has no text-shadow / This has text-shadow: 0 0 1px rgba(0,0,0,.01);

Local Contrast Enhancement

There is a trick commonly used in Photoshop to make photos "pop" with more contrast, but without modifying the overall tonal range of the photo. You can read more about local contrast enhancement elsewhere, but the essence of it is increasing the contrast of every edge in the photo—making the darker side a bit darker and the lighter side a bit lighter. We can use text-shadow to do the same thing with low contrast text.

Note: I am not advocating low-contrast text on your website! Make sure the text is readable even before you add any shadows.

So for instance if we have some brown text on a tan background, we would need to add a lighter tan "glow" around the text. That will increase the contrast immediately around the text itself, without changing the rest of the background.

No contrast adjustment / With text-shadow: 0 0 5px #f1e2cd;

You can see that this method lightens the text-weight slightly (just as in the previous example) but it also makes the text look sharper. The trick here is to pick a shadow color that sets off the text without looking artificial. In particular, this can be useful when you are overlaying text on a background image: the slight glow will make the text easier to read.

With all of these techniques the trick is getting the right combination of shadow color and blur radius: keep experimenting until you get it looking the way you want. And make sure to browser test! We are using cutting-edge technology here, so it will look a little different from browser to browser.

Finally, there are undoubtedly many other interesting effects to be had by manipulating the text-shadow property. If you find others, please share them in the comments.