home / blog

CSS spriting using RMagick

I wanted to give CSS spriting a go – i.e. one large image montage and then using background-position to ‘see-through’ to the correct image.

The best off the shelf solution looks like SmartSprites but this requires you annotate your CSS with configuration…

So I wondered if I could knock something cheap and cheerful using RMagick – ImageMagick for Ruby.

Convention over configuration.

For my approach to work I made the following assumptions.

  • File name same as class-name, e.g. next.png and div.next
  • All images in CSS are class-names, not IDs.
  • Image groups in directories, e.g. symbols/cloud.png, symbols/rain.png are classes symbols-cloud and symbols-rain respectively.
  • All files in img/ at top level or in group directory at top level

Finding the images

Do a directory trawl of img/ looking for .png, exclude sprite.png itself.

images = Dir.glob("img/**/*.png").sort.select {|it| !(it.match("favicon|sprite"))}

Combining the images

I’m sure there are loads of complicated optimal layout algorithms, but I just went for one long row.

# image that is much larger, we'll crop later
cat = Image.new(4000, 1000) do 
  self.background_color= "transparent"
end
cat.alpha(Magick::ActivateAlphaChannel)
max_height = 0
x = 0
images.each do |f|
  img = Magick::Image::read(f)[0]
  img.alpha(Magick::ActivateAlphaChannel)
  cat.composite!(img, x, 0, Magick::OverCompositeOp)
  x = x + img.columns
  max_height = [max_height, height].max
end
cat.crop!(0, 0, x, max_height)
list = ImageList.new
list.push cat
list.write("png:img/sprite.png")

Creating the CSS

I added the following to the loop to build up the CSS as the image was built – quick and dirty

css_output = ''
css_classes = Array.new
images.each do |f|
  ...
  css_output = css_output + ".#{css(f)} {\n"
  css_output = css_output + "  background-position: -#{x}px 0;\n"

  css_output = css_output + "}\n"

  css_classes.push("." + css(f))
  x = x + width
  max_height = [max_height, height].max
end
css_output = css_output + "#{css_classes.join(',')} {\n background-image: url(\"/img/sprite.png\");\n}\n"

File.write('sprite.css', css_output)

Example CSS output showing just a few images…

Note this only the spriting dynamic part. I had existing handcrafted CSS for background-attachment, width, height etc. This is then imported into my main.css using @import and then inlined/optimized later in the build process.

.error-cloud {
  background-position: -0px 0;
}
.next {
  background-position: -80px 0;
}
.previous {
  background-position: -124px 0;
}
.provider-accuweather {
  background-position: -168px 0;
}
.provider-bbc {
  background-position: -250px 0;
}
.error-cloud,.next,.previous,.provider-accuweather,.provider-bbc {
 background-image: url("/img/sprite.png");
}

This entry was posted in geek and tagged , , . Bookmark the permalink.

Leave a Reply

Your email address will not be published.