Higher-level style sheets
Yesterday I used three things for the first time: Sass, Compass, and Ruby. To summarize:
- I ♥ Sass
- I ♥ Compass
- I ♥ Ruby
One's own site is a great place to play with new (or in this case, not so new) web technologies. I decided to get stuck in and manually convert the 1200 line style sheet from CSS to something a bit more awesome. This post documents the most interesting portion of that transformation, which involved this site's archives styles.
Original CSS
ol.archives { margin: 0 0 0 -21px; list-style: none; } ol.archives li h2 { margin: 1.75em 0 0 21px; padding: 0; font: bold 1em/1.75 "Lucida Grande", ..., sans-serif; color: #93a1a1; } ol.archives li ol { margin: 0; list-style: none; } ol.archives time { display: block; float: left; margin: 0.167em 0.5em 0 0; width: 16px; height: 16px; background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUg AAALAAAACICAYAAABHnmGeAAADxUlEQVR42u3d0W3bQBCEYTFISS4hLThFOiU4rT itnF9iwBBM2yR3j3u87wcE6EXjIz1YDimOuLTWbsCo/LALwMAAAwMMDAYGGBhgYI CBwcAAAwMMDDAwGBhgYICBwcAAAwMMDDAwGHgSHh4e2pb39HL1dtFam+r18vTYnm +39ny7tdbapvcvT4+NXqze0dfUE/jvsmx6Ty9XT4TAdPyccaN/7fwtjH9/ftNL0D OBYQLjdlv+57WIXyta7rLfUc0MvTeN9++rrI+Bd16RWYJOOt7/AyM0o/Wy9l9vRA iIEPj6kFpNL3uNIsRFIkmGVsVIclbEESEgQiD/kFr1xO3s9TFw0mE/OgNmZsoI7b N+KF2EgAgxGtFfadI7Mbp4RgZGRoQAAwMMDFQzsI4YveE6cTpi9C7TidMRo6cTB3 xCyhcZOmL0tuiZwDCBsxmhI1ZR776nF6G7phmpFbUfS03g92ePlbTuNSvpffT5o7 r3n43Wit6PIgREiOgYUfEmohF6bNXpvc1dDbyWk6oQub4RavDZ+1GEAKpGiKsf/j K3daT9mD2Nuxl4tk5XVqU+q7MXdSWi97mNCAER4h4dMXrd4opOHEZGhAADAwwMVD Owjhg9nTh6OnE6cfRG0JOBMR06cfRO1zOBYQJnEd2Z+kjvyPPOeqyP3sAZOLoz1e OusSNrzNbL2H+V9EQIiBA9IkVlzegbsnUBL2TgGZ93pgsoQgDXjRCzRZzK67tkrT 66M7WmEXEmHb0+2ytCAH0nsI4YvW7xRycOIyNCgIEBBgaqGVhHjJ5OHD2dOJ04ei PoycCYDp04eqfrmcAwgTNY60cd6U19pRmptXV9PfX29ACrb285A392c/Pem6jXNF troVpr666gl/n/OFNPhIAIkRkhqmmuae39G7309mp+V29vhDiqV87AWYvPeoBKxN 9Yq5nvNd6IekcioggBESLrkB91UhJ5ePrO+vZe2YiYQj0q/hnr69WN63YVIioCfP bZrbo94kOG3l7t6tsrQkCEiEBHjF4vdOIwNCIEGBhgYKCagXXEauldEp04nbORXz pxE+mJEEAxdOIm0DOBgZkm8Bsjd84q6H1Hc/ZvUk/pxGV0xPbcGlhd76t9uEx+Ai dCwATecvjL1MvsiJ21vVmaDLzzEJipF3GIzow40TEMIgRM4G2HwMzHtkZU1492xK Ijialb6CpERMQYtcNWpQMoQgAzTGAdsVp6V0YnDiIEwMAAA4OBAQYGGBhgYDAwwM AAAwMMDAYGGBhgYDAwwMAAAwMMDAYGRuEVbcTUMFk81KcAAAAASUVORK5CYII=); background-repeat: no-repeat; line-height: 10; overflow: hidden; } ol.archives time[datetime*="01T"] { background-position: -60px -20px; } ol.archives time[datetime*="02T"] { background-position: -80px -20px; } ol.archives time[datetime*="03T"] { background-position: -100px -20px; } ol.archives time[datetime*="04T"] { background-position: -120px -20px; } ol.archives time[datetime*="05T"] { background-position: -140px -20px; } ol.archives time[datetime*="06T"] { background-position: -20px -40px; } ol.archives time[datetime*="07T"] { background-position: -40px -40px; } ol.archives time[datetime*="08T"] { background-position: -60px -40px; } ol.archives time[datetime*="09T"] { background-position: -80px -40px; } ol.archives time[datetime*="10T"] { background-position: -100px -40px; } ol.archives time[datetime*="11T"] { background-position: -120px -40px; } ol.archives time[datetime*="12T"] { background-position: -140px -40px; } ol.archives time[datetime*="13T"] { background-position: -20px -60px; } ol.archives time[datetime*="14T"] { background-position: -40px -60px; } ol.archives time[datetime*="15T"] { background-position: -60px -60px; } ol.archives time[datetime*="16T"] { background-position: -80px -60px; } ol.archives time[datetime*="17T"] { background-position: -100px -60px; } ol.archives time[datetime*="18T"] { background-position: -120px -60px; } ol.archives time[datetime*="19T"] { background-position: -140px -60px; } ol.archives time[datetime*="20T"] { background-position: -20px -80px; } ol.archives time[datetime*="21T"] { background-position: -40px -80px; } ol.archives time[datetime*="22T"] { background-position: -60px -80px; } ol.archives time[datetime*="23T"] { background-position: -80px -80px; } ol.archives time[datetime*="24T"] { background-position: -100px -80px; } ol.archives time[datetime*="25T"] { background-position: -120px -80px; } ol.archives time[datetime*="26T"] { background-position: -140px -80px; } ol.archives time[datetime*="27T"] { background-position: -20px -100px; } ol.archives time[datetime*="28T"] { background-position: -40px -100px; } ol.archives time[datetime*="29T"] { background-position: -60px -100px; } ol.archives time[datetime*="30T"] { background-position: -80px -100px; } ol.archives time[datetime*="31T"] { background-position: -100px -100px; }
That's a lot of text, most of which relates to the calendar sprite. To start, though…
Significant whitespace and nesting
As in Python and CoffeeScript, whitespace is significant in Sass. As a result, squiggly brackets are not required to delimit blocks, and semicolons are not required to separate one rule from the next.
Sass allows selectors to be nested. The main advantage of this approach is that selectors needn't include their "context" (ancestors). It also means that a style sheet's structure resembles that of the corresponding markup.
ol.archives margin: 0 0 0 -21px list-style: none li h2 margin: 1.75em 0 0 21px padding: 0 font: bold 1em/1.75 "Lucida Grande", ..., sans-serif color: #93a1a1 ol margin: 0 list-style: none time display: block float: left margin: 0.167em 0.5em 0 0 width: 16px height: 16px background: url(data:image/png;base64,...) no-repeat line-height: 10 overflow: hidden
Compass's inline-image function
For an image that's design rather than content, it's better to link to it
from a style sheet than to include it as an img. Better still, it can be
Base64 encoded and embedded in the style sheet as a data URI, saving an HTTP
request.
Until yesterday, I'd always done this by hand. It's a bit of a pain, but I'm pretty familiar with the routine:
- Export image file as PNG
- Smush.it
- Save smushed image
- Drop smushed image into Hashify (e.g. calendar.png)
- Copy data URI from Hashify and paste it into style sheet
Having to perform these steps each time the source image is changed is a real
nuisance. Compass offers an extremely elegant solution: a Sass function named
inline-image. When compiled, inline-image("calendar.png") becomes a
data URI – the Base64-encoded representation of calendar.png. If calendar.png
is changed, compass compile is all that's required to update the data URI.
ol.archives ... time ... background: inline-image("calendar.png") no-repeat ... &[datetime*="01T"] background-position: -60px -20px &[datetime*="02T"] background-position: -80px -20px &[datetime*="03T"] background-position: -100px -20px ... &[datetime*="31T"] background-position: -100px -100px
Generating repetitive CSS programmatically
Having a placeholder for the data URI is great; 31 background-position
declarations not so much. Compass provides helpers for generating and working
with sprites, but in this case all that's required is to generate background
positions for an existing sprite. The first step is to add a loop:
@for $date from 1 through 31 // do stuff
The loop variable, $date, can be interpolated to generate the selectors:
@for $date from 1 through 31 &[datetime*="#{$date}T"]
Almost. This gives datetime*="1T", datetime*="2T", etc. rather than their
zero-padded equivalents. Adding a leading zero when required is not difficult:
@for $date from 1 through 31 @if $date < 10 $date: "0#{$date}" &[datetime*="#{$date}T"]
Finally, variables can be used to calculate the background-position of each
element:
$offset: -20px $x: 3 $y: 1 @for $date from 1 through 31 @if $date < 10 $date: "0#{$date}" &[datetime*="#{$date}T"] background-position: $x * $offset $y * $offset @if $x == 7 $x: 1 $y: $y + 1 @else $x: $x + 1
These 13 lines of relatively straightforward logic produce the same output as the 62 lines they replace.
The right level of abstraction
In A Dark Age of Objective-C and in The Bridges of Siracusa County, John Siracusa made the claim that software developers have an insatiable desire for ever higher-level programming languages and frameworks. In and of itself, though, abstraction is not a virtue. Most abstractions fail for one reason or another: they aren't sufficiently flexible, or they are so generic that they provide very little utility, or they don't fit well with people's mental models.
Good abstractions are wonderful. jQuery, for example, makes it possible to act on collections of elements as one would individual elements. Before jQuery, binding an event handler to several elements required more than a superficial understanding of JavaScript (advanced knowledge if it were necessary to close over variables). Good abstractions allow us to write succinct, self-documenting code.
I'm hopeful that Sass and its kin will do for CSS what jQuery has done for the DOM (and what CoffeeScript is doing for JavaScript). My first taste of higher-level CSS tasted very good indeed.