Retina sprites with Sass and Compass

Menu
  1. 1. What is needed sprites image
  2. 2. Mixin for a Compass
  3. 3. Mixin for Gulp

What is needed sprites image

As web developers have become more concerned about browser performance a technique called “image spriting” has emerged that is designed to reduce the number of requests made to the server. As it turns out, fewer requests made the server (when there is no significant difference in the combined size of the files delivered) can make a big difference in how fast a page appears to download.

Image spriting works by combining a bunch of images (called “sprites”) into one large image (or “sprite sheet”) to significantly reduce the number of requests made to the server. Then, with clever use of background-position only part of the sprite sheet is revealed each time an image is needed.

Mixin for a Compass

(it is assumed that the already installed Ruby, compass and Sass)

Folder structure for compass of project

1
2
3
4
5
6
7
8
9
10
tools/
|--config.rb
htdocs/
|--f/
|--sass/
|--i/
icons/
|--icon-bar.png
icons2x/
|--icon-bar.png

Configuration for ruby compilation

1
2
3
4
5
6
7
8
9
10
http_path = "../htdocs"
css_dir = http_path+"/f/css"
sass_dir = http_path+"/f/sass"
images_dir = http_path+"/f/i"
Encoding.default_external = "utf-8"
output_style = :nested
relative_assets = true
line_comments = false
preferred_syntax = :scss

When doing configure ruby on your project, you can run the compass watcher to keep your CSS files up to date as changes are made.
For windows:

1
2
cd /path/to/project/tools/
compass watch

Variables for sass mixin:

1
2
3
4
//sprite
$spacing-icons: 2px !default;
$icons: sprite-map("icons/*.png", $layout: smart, $sort-by: 'name', $spacing: $spacing-icons) !default;
$icons2x: sprite-map("icons2x/*.png", $layout: smart, $sort-by: 'name', $spacing: $spacing-icons*2) !default;

Retina mixin for compass

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@mixin sprites($image) {
background-image: sprite-url($icons);
background-position: sprite-position($icons, $image);
background-repeat: no-repeat;
height: image-height(sprite-file($icons, $image));
width: image-width(sprite-file($icons, $image));
@media (min--moz-device-pixel-ratio: 1.3),
(-o-min-device-pixel-ratio: 2.6/2),
(-webkit-min-device-pixel-ratio: 1.3),
(min-device-pixel-ratio: 1.3),
(min-resolution: 1.3dppx) {
background-image: sprite-url($icons2x);
background-size: ceil(image-width(sprite-path($icons2x)) / 2) auto;
background-position: round(nth(sprite-position($icons2x, $image), 1) / 2) round(nth(sprite-position($icons2x, $image), 2) / 2);
height: image-height(sprite-file($icons2x, $image)) / 2;
width: image-width(sprite-file($icons2x, $image)) / 2;
}
}

Mixins can be used to generate custom classes for all of your sprites automatically. The name of the mixin is based on the name of the image where the sprite sheet source images are located. In our example:

1
2
3
4
5
6
7
.icon-bar {
&:before {
display: inline-block;
content: "";
@include sprites(icon-bar);
}
}

Will output the following CSS:

1
2
3
4
5
6
7
8
9
10
11
.icon-bar {
&:before {
dosplay: inline-block;
content: "";
background-image: url('../i/icons-s8df5cbe487.png');
background-position: -7px -18px;
background-repeat: no-repeat;
width: 5px;
height: 8px;
}
}

Notice that Compass has built the “icons-s8df5cbe487” image for us automatically. This is our sprite sheet. The name of the file is the name of our sprite sheet (in this case “icons”) plus a funny series of letters and numbers called a “hash.” The hash will change whenever you update the sprite sheet so that cached CSS will know to use the updated image.

Mixin for Gulp

Folder structure for gulp of project

1
2
3
4
5
6
source/
|--f/
|--css/
|--sprites/
icon-bar.png
icon-bar@2x.png

Configuration for Gulp compilation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
var gulp = require('gulp'),
sass = require('gulp-sass'),
source = require('vinyl-source-stream'),
spritesmith = require('gulp.spritesmith'),
...
paths = {
source: 'source',
scss: 'source/f/css',
img: 'source/f/i',
sprites: 'source/f/sprites'
};
gulp.task('sprite', function generateSpritesheets () {
var spriteData = gulp.src(paths.sprites+'/*.png')
.pipe(spritesmith({
retinaSrcFilter: paths.sprites+'/*@2x.png',
imgName: 'sprites.png',
retinaImgName: 'sprites@2x.png',
cssName: '_sprites.scss',
cssFormat: 'scss',
padding: 10,
imgPath: '../i/sprites.png',
retinaimgPath: '../i/sprites@2x.png',
algorithm: 'binary-tree',
cssVarMap: function (sprite) {
sprite.name = 'sprite-' + sprite.name;
}
}));
spriteData.img.pipe(gulp.dest(dest.img));
spriteData.css.pipe(gulp.dest(paths.scss));
});

Retina mixin for Gulp (_mixin.scss)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
@mixin sprite-width($sprite) {
width: nth($sprite, 5);
}
@mixin sprite-height($sprite) {
height: nth($sprite, 6);
}
@mixin sprite-position($sprite, $extra-offset-x: 0, $extra-offset-y: 0){
$sprite-offset-x: nth($sprite, 3) + $extra-offset-x;
$sprite-offset-y: nth($sprite, 4) + $extra-offset-y;
background-position: $sprite-offset-x $sprite-offset-y;
}
@mixin sprite-image($sprite) {
$sprite-image: nth($sprite, 9);
background-image: url(#{$sprite-image});
}
@mixin sprite($sprite) {
@include sprite-image($sprite);
@include sprite-position($sprite);
@include sprite-width($sprite);
@include sprite-height($sprite);
}
//Retina
@mixin sprite-2x($sprite1x, $sprite2x){
@include sprite($sprite1x);
@media (min--moz-device-pixel-ratio: 1.3),
(-o-min-device-pixel-ratio: 2.6/2),
(-webkit-min-device-pixel-ratio: 1.3),
(min-device-pixel-ratio: 1.3),
(min-resolution: 1.3dppx) {
@include sprite-image($sprite2x);
background-size: nth($sprite1x, 7) nth($sprite1x, 8);
@include sprite-position($sprite1x);
@include sprite-height($sprite1x);
@include sprite-width($sprite1x);
}
}
@mixin sprite-2x-image($sprite1x, $sprite2x){
@include sprite-image($sprite1x);
@media (min--moz-device-pixel-ratio: 1.3),
(-o-min-device-pixel-ratio: 2.6/2),
(-webkit-min-device-pixel-ratio: 1.3),
(min-device-pixel-ratio: 1.3),
(min-resolution: 1.3dppx) {
@include sprite-image($sprite2x);
background-size: nth($sprite1x, 7) nth($sprite1x, 8);
}
}

Sprites generated at plugin spritesmith, so you need to include the generated styles for images in the main styles. Example scss sprite:

1
2
3
4
5
6
7
8
9
@import "_sprites";
@import "_mixin";
...
.icon-bar {
&:before {
@include sprite-2x($sprite-icon-bar, $sprite-icon-bar);
}
}

Generate sprite with gulp:

1
gulp sprite

Will output the following CSS:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.icon-bar:before {
width: 5px;
height: 8px;
background-image: url(../i/sprites.png);
background-position: -7px -18px;
}
@media (min--moz-device-pixel-ratio: 1.3), (-webkit-min-device-pixel-ratio: 1.3), (min-device-pixel-ratio: 1.3), (min-resolution: 1.3dppx) {
.icon-bar:before {
width: 5px;
height: 8px;
background-image: url(../i/sprites.png);
background-position: -7px -18px;
background-size: 269px 233px;
}
}