Use Webkit and Imagemagick to Create Cross-browser Buttons and Other Swag

So you’ve seen the nice BonBon buttons and Super Awesome Buttons and you really want to use them in your web design. Only, as we all know, most people out there use Internet Explorer, which makes them render like crap.

For this tutorial, I’ll show how you can render the well known super awesome button into a sprite that can be used in most current browsers and look just as good as they do in webkit. You can use the technique for a lot of things, such as boxes, borders, and so on.

Before you start, you need to install webkit2png and imagemagick.

Now, let’s start out with a basic awesome button and save it as /tmp/awesome.html. I like the blue one:

<style>
a {
  background: #222 url(http://www.zurb.com/images/alert-overlay.png) repeat-x;
  display: inline-block;
  padding: 5px 10px 6px;
  color: #fff;
  text-decoration: none;
  -moz-border-radius: 5px;
  -webkit-border-radius: 5px;
  -moz-box-shadow: 0 1px 3px rgba(0,0,0,0.5);
  -webkit-box-shadow: 0 1px 3px rgba(0,0,0,0.5);
  text-shadow: 0 -1px 1px rgba(0,0,0,0.25);
  border-bottom: 1px solid rgba(0,0,0,0.25);
  cursor: pointer;
  font-size: 13px;
  font-weight: bold;
  line-height: 1;
  background-color: #ff5c00;
}
</style>
<a href="#">&nbsp;</a>

It will render as this:

Let’s just get some more width on this thing, as we’re going to cut it up later:

a {
  ...
  width: 240px;
}

That’s better:

Now, to render this into an image we could of course just take a screenshot. But that wouldn’t preserve the delicate alpha channels in the shadows. So let’s break out webkit2png:

webkit2png --transparent --output=./awesome.png file:///tmp/awesome.html

Note that you need to specify the absolute path to the input file as the second argument, using the file:// protocol.

This should give you a screen capture in awesome.png of 800×600 pixels. Let’s trim that down a bit, shall we? You need imagemagick installed for this:

convert -trim +repage awesome.png awesome.png

Et voila—the image is now 260×26 pixels. But we also need an image for the hover effect. So let’s just adjust the stylesheet a bit. This should do:

a {
  ...
  background-color: #f90;
}

Save it under a new name—/tmp/awesome-hover.html. Now repeat the process from above:

webkit2png --transparent --output=./awesome-hover.png file:///tmp/awesome-hover.html
convert -trim +repage awesome-hover.png awesome-hover.png

Next we glue the two pieces together to create our sprite. Imagemagick comes handy again:

montage awesome.png awesome-hover.png -background transparent -tile 1x2 -geometry +0+0 combined.png

So now we have the two states rendered into a single sprite. Alright, but we still need to create the css to use it. Let’s create a new document in awesome-sprite.html:

<style>
body { font-family: sans-serif; }
a {
  background: url(combined.png) no-repeat;
  display: inline-block;
  padding: 6px 10px 6px;
  color: #fff;
  text-decoration: none;
  text-shadow: 0 -1px 1px rgba(0,0,0,0.25);
  cursor: pointer;
  font-size: 13px;
  font-weight: bold;
  line-height: 1;
  width: 240px;
}
a:hover {
  background-position: 0 -26px;
}
</style>
<a href="#">Awesome</a>

Note that I adjusted the padding to compensate for the border-bottom. That way the combined height will stay the same.

But wait. That’s nice and all, but this sprite is locked to a fixed width of 260px. What if we wanted to make it fluid? We can use the sliding doors technique for this:

<style>
body { font-family: sans-serif; }
a {
  background: url(combined.png) no-repeat;
  background-position: right 0;
  display: inline-block;
  padding-right: 10px;
}
a span {
  background: url(combined.png) no-repeat;
  background-position: left 0;
  display: inline-block;
  padding-top: 6px;
  padding-bottom: 6px;
  padding-left: 10px;
  color: #fff;
  text-decoration: none;
  text-shadow: 0 -1px 1px rgba(0,0,0,0.25);
  cursor: pointer;
  font-size: 13px;
  font-weight: bold;
  line-height: 1;
}
a:hover { background-position: right -26px; }
a:hover span { background-position: left -26px; }
</style>
<a href="#"><span>Awesome</span></a>

I moved most of the styling into the inner span and left some padding to make room for the ends of the sprite. It’s close enough, but there’s still a problem:

Look at the sides of this thing!

Because our sprite is transparent, we can’t simply overlay the images like this. We need to do some surgery to the sprites. First let’s slice off the ends into a temporary image:

convert sprite.png -gravity west -crop 250x52+0+0 +repage left.png
convert sprite.png -gravity east -crop 10x52+0+0 -background transparent -extent 250x52 right.png

This cuts the sprite into a left side and a right side, where the continuous background is together with the left part.

And finally, let’s combine them back into one sprite:

montage left.png right.png -background transparent -tile 1x2 -geometry +0+0 sprite_final.png

We end up with a sprite that looks like this:

Then simply adjust the offsets on those backgrounds:

<style>
body { font-family: sans-serif; }
a {
  background: url(sprite_final.png) no-repeat;
  background-position: right 0;
  display: inline-block;
  padding-right: 10px;
}
a span {
  background: url(sprite_final.png) no-repeat;
  background-position: left 0;
  display: inline-block;
  padding-top: 6px;
  padding-bottom: 6px;
  padding-left: 10px;
  color: #fff;
  text-decoration: none;
  text-shadow: 0 -1px 1px rgba(0,0,0,0.25);
  cursor: pointer;
  font-size: 13px;
  font-weight: bold;
  line-height: 1;
}
a:hover { background-position: right -26px; }
a:hover span { background-position: left -26px; }
</style>
<a href="#"><span>Awesome</span></a>

And there we are. Awesome indeed.

You can repeat this formula by stuffing it in a shell script and you have your own little button-factory. In fact, being Christmas and all, I did the work for you. Just save the snippet as webkit_sprite and make it executable (chmod +x webkit_sprite), and you can invoke it like this:

PADDING=10 HTML_NORMAL=normal.html HTML_HOVER=hover.html ./webkit_sprite

Win an Annual Membership to Learnable,

SitePoint's Learning Platform

  • Andy

    Awesome! Thanks a bunch man.

  • http://www.tyssendesign.com.au Tyssen

    Just use http://css3pie.com/ and you can save yourself a lot of trouble and have cleaner markup too.

    • rickydazla

      Woah! Eat that!

  • Mark-K

    Wow CSS3 PIE is awesome, thanks for that link!

  • Troels Knak-Nielsen

    Hadn’t seen CSS3 PIE – Thanks for that. I’d say that the approach is a bit different, with different pros and cons. By rendering your buttons as sprites, not only do you ensure that it works in all browsers (not just IE is weird, you know), but in my experience behaviors has an unfortunate tendency to crash IE.

  • rudiedirkx

    What happens with buttons have have multiple line text?

  • frderek

    “Only, as we all know, most people out there use Internet Explorer, which makes them render like crap.”

    Yawn. Why bother pandering to the stupid? Let them eat crap until M$ produce a half-decent standards-compliant browser why the F*** should I make the effort?

  • Shay

    Your examples dont seem to work in IE8.

    • Troels Knak-Nielsen

      There was a typo in the final css example, that I have fixed now. Does this help?

  • climboid

    Nice solution but images for buttons just cause of the rounded corners?
    Screw IE… if they can’t handle border-radius they are not worthy of having rounded corners =)

  • Al

    Twenty seven lines for one button. Doesn’t seem worth the trouble.

    • Kam

      You can make it shorter by putting all the CSS attributes on one line :p

  • DSavage

    Very nice article! I really like the technique, will have to use this more often as I’m constantly forced to support IE6-8. Speaking of which, @frderek: because not only is it sometimes the requirement, as in my case where we have corporate customers which refuse to upgrade due to costs, but why would you want to scare off potential customers on your website because it looks crappy to them? I wouldn’t argue supporting anything pre-IE6, but IE6-8 holds such a vast market share its close to negligence not to support it.

    @Troels: any chance there’s a good cross browser technique that’s reliable for creating text shadows? I came up with a method I had to use on a website recently which was spotty at best. It worked, but lots of specific browser fixes…wondering if there’s a similar technique that can be used for that?

    Anyway, great article!