-
Notifications
You must be signed in to change notification settings - Fork 6
Recipe: concise CSS templates for sprites
While grunt-tight-sprite
is flexible enough to accommodate practically any CSS structure
(or your favorite CSS preprocessor), it makes sense to plan ahead how to use sprites,
especially for new projects unencumbered with legacy. Usually it makes sense to split
sprite-related CSS into two parts:
- static, common for all sprites.
- dynamic, specific for different images. This part is generated by
grunt-tight-sprite
.
Let's start with a plain vanilla CSS you can frequently find on Internet. It looks like that:
.sprite1 {
background: url(sprite.png) -100px -100px no-repeat;
width: 32px;
height: 32px;
/* other junk: position or float, padding, margin, font, and so on */
}
.sprite2 {
background: url(sprite.png) -200px -300px no-repeat;
width: 64px;
height: 64px;
/* other junk: position or float, padding, margin, font, and so on */
}
Now we use it in HTML:
<div class="sprite1">...</div>
<div class="sprite2">...</div>
We can generate a CSS like that using the default template (see options.template), or by specifying it explicitly:
// ...
var iconPath = "images/";
grunt.initConfig({
tight_sprite: {
my_sprite1: {
options: {
classPrefix: "",
hide: iconPath,
template: [
".<%= className %> {",
" background: url(<%= url %>) -<%= x %>px -<%= y %>px no-repeat;",
" width: <%= w %>px;",
" height: <%= h %>px;",
"}",
"\n"
].join("\n")
},
src: [iconPath + "*/*.{png,jpg,jpeg,gif}"],
dest: iconPath + "sprite.png"
}
}
});
// ...
But it is possible to do better than that.
Let's separate immutable parts common for all sprites from mutable parts, which are specific for every image, using a combo CSS above as a starting point.
Immutable (included statically, sprite.png
is generated by grunt-tight-sprite
):
.sprite {
background-image: url(sprite.png);
background-repeat: no-repeat;
/* other junk: position or float, padding, margin, font, and so on */
}
Mutable (generated by grunt-tight-sprite
):
.sprite_a {
background-position: -100px -100px;
width: 32px;
height: 32px;
}
.sprite_b {
background-position: -200px -300px;
width: 64px;
height: 64px;
}
As you can see we minimized junk replication.
Now we use it in HTML:
<div class="sprite sprite_a">...</div>
<div class="sprite sprite_b">...</div>
Essentially we moved from all-encompassing unique CSS classes, to a two-part declaration: this is a sprite, and we want this specific part of a sprite. The trade-off is obvious: our CSS is smaller, even with gzip
, yet our HTML became more wordy. In many cases it saves space, while keeping the overhead of looking up two CSS classes reasonably low.
We can generate a CSS like that using a custom template (see options.template):
// ...
var iconPath = "images/";
grunt.initConfig({
tight_sprite: {
my_sprite1: {
options: {
classPrefix: "sprite_", // actually this is the default
hide: iconPath,
template: [
".<%= className %> {",
" background-position: -<%= x %>px -<%= y %>px;",
" width: <%= w %>px;",
" height: <%= h %>px;",
"}",
"\n"
].join("\n")
},
src: [iconPath + "*/*.{png,jpg,jpeg,gif}"],
dest: iconPath + "sprite.png"
}
}
});
// ...
Sometimes we do need to customize on a per-image basis. In this case it is usually better to mix in an additional CSS class like so (borrowing from the previous example):
.title {
font-size: 24pt;
font-color: blue;
}
Used like this:
<div class="sprite sprite_a title">...</div>
<div class="sprite sprite_b">...</div>
One important case of such customization is dealing with groups of images of similar sizes. In many cases we deal with sprites of a few fixed sizes, for example, icons 16 by 16, 32 by 32, 64 by 64, and so on. It opens up new opportunities to save on dynamically generated CSS.
Immutable (included statically, sprite.png
is generated by grunt-tight-sprite
):
.sprite {
background-image: url(sprite.png);
background-repeat: no-repeat;
/* other junk: position or float, padding, margin, font, and so on */
}
.icon_32 {
width: 32px;
height: 32px;
}
.icon_64 {
width: 64px;
height: 64px;
}
Mutable (generated by grunt-tight-sprite
):
.sprite_a { background-position: -100px -100px; }
.sprite_b { background-position: -200px -300px; }
.sprite_c { background-position: -100px -200px; }
.sprite_d { background-position: -200px -100px; }
Now we use it in HTML:
<div class="sprite icon_32 sprite_a">...</div>
<div class="sprite icon_32 sprite_b">...</div>
<div class="sprite icon_64 sprite_a">...</div>
<div class="sprite icon_64 sprite_b">...</div>
This style of declaration consists of three parts: this is a sprite, this is a type of image we need, and we want this particular part of a sprite. This way we keep CSS even smaller, even with gzip
, with more wordy HTML. All in all we save even more.
The main drawback is that we should keep track of image sizes, and do not cut 32x32 image from a 16x16 original. In practice, the problem proved to be benign, because all mistakes are easily visible and can be corrected once occurred, and typically such assignment of CSS classes is done only once per HTML element, and left unchanged.
We can generate a CSS like that using a custom template (see options.template):
// ...
var iconPath = "images/";
grunt.initConfig({
tight_sprite: {
my_sprite1: {
options: {
hide: iconPath,
template: ".<%= className %> { background-position: -<%= x %>px -<%= y %>px; }\n"
},
src: [iconPath + "*/*.{png,jpg,jpeg,gif}"],
dest: iconPath + "sprite.png"
}
}
});
// ...
If for some reason we want to reduce a number of classes, we can go back to two classes by merging sprite
and icon_XX
together. Usually drawbacks of this approach (cutting-and-pasting sprite
several times) outweigh possible performance benefits.