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"); }