Skip to content
Eugene Lazutkin edited this page Oct 9, 2015 · 13 revisions

Generally all image spriting tools can be divided into two broad categories:

  • Converters of existing projects.
  • Supporters of a development process.

grunt-tight-sprite is in the second category.

Converters

Converters typically deal with existing assets (images, CSS, HTML), and can parse files, detect used images, sprite them, and edit existing CSS and HTML to use sprites.

There are numerous technical problems with this approach:

  • Parsing is hard, and consequently error-prone. Many tools don't parse HTML, opting for manual removal of images, and replacing them with CSS backgrounds before any conversion.
  • Some tools have problem with parsing CSS properly, and require either a proper formatting, or a manual mark-up with pseudo-comments in order to extract needed information.
  • Editing of existing CSS and HTML is hard, so many tools opt for producing new files, which should be manually integrated into the application.
  • Incorporating new images, and deleting old ones begins usually with manual editing of a CSS file.
  • Separating images to different sprites is complicated. It is not recommended to mix images of different types in a single sprite (e.g., photos vs. sharp graphics) due to different compression requirements (e.g., JPEG vs. PNG). Common solutions are separating images manually into different CSS files and processing them differently, or using a special mark-up with pseudo-comments.

"Process" supporters

These tools (and grunt-tight-sprite) should be used routinely during the whole life-cycle of a project.

Common workflow:

  1. Artist produces images. Typically they have self-evident unique names. Their names, and their folders may already encode some pertinent information. For example, they can be grouped in folders by their size (e.g., icons 32 by 32), or by their functional purpose (e.g., corporate logos). The names and folders are used by an artist and a programmer to refer to images unambiguously.
  2. Programmer places images into their proper location in a project folder, and run a tool, which processes images, and produces a corresponding CSS file with automatically generated unique CSS class names for every image. Programmer uses those class names to style web pages.
  3. Artists modifies existing images, produces new ones, or removes old ones, and the cycle repeats.

Discussion

Obviously a "converter" is geared towards working on legacy projects. From my experience a conversion is far from free and requires extensive modifications to the project rendering this advantage practically moot.

On one project I spent a lot of time converting it to use sprites. This is the process listed here for posterity:

  1. Find and replace all <img src="images/image.jpg"> elements in HTML files and templates with <div class="sprite_image"></div>. This step produced a lot of new CSS classes like this:

    .sprite_image {
       background-image: url(../images/image.jpg);
       background-position: -100px -200px;
       background-repeat: no-repeat;
       width: 32px;
       height: 32px;
       display: inline-block;
    }
  2. Decide what images should go into a sprite, and which shouldn't for technical or business reasons. For example, some big images, dynamically generated images, or frequently modified images should be skipped. They should go into separate CSS files.

  3. Finally I am done, and can run the tool. The result was horrible, even worse than it was before especially for mobile browsers.

  4. Postmortem: the resulting sprite was too big on two accounts:

    1. It was a bigger file size-wise than all images before combined.
    2. The resulting image was so big pixel-wise, it crashed an image previewer.

    The first one was due to different compression needs of different images (e.g., photos vs. graphics), while the second was due to a sub-par packing, which produced a lot of unused space.

  5. In order to combat the first problem, I had to separate images into two broad categories:

    • better compressed with JPEG.
    • better compressed with PNG.

    That helped tremendously with the transmitted file size. I was fully prepared to produce several sprites with different JPEG quality settings for different image asset classes, but I was lucky that one JPEG sprite was good enough. Increasing number of sprites can defeat their purpose to reduce a number of required network connections, so it is not a decision to take lightly.

  6. In order to combat the second problem I had to "help" the algorithm manually, and sorted all images into two piles: small ones, and big ones. Having more or less homogeneous images in each pile increased a number of sprites, but greatly reduced the waste.

After more profiling, and analysis, I ended up with three sprites: a JPEG one, a PNG one with big chunks, a PNG one with small chunks. Given that they replaced hundreds of individual images, the result was tremendously positive. But during the process I touched all HTML files in the project, and heavily edited all CSS files. In order to achieve what I wanted (3 sprites) I had to rearrange CSS, add pseudo-comments, so the tool could assign images to corresponding sprites, and reformat CSS, which was not understood by tool (e.g., background should be split to background-image, background-position, background-repeat, and so on).

"Process" tools require at least the same amount of work for existing projects, but greatly reduce friction, when programmers count on them, and incorporate them as a part of their process. This is where grunt comes into the picture. The ideal situation is to start using grunt-tight-sprite from the beginning.

Usually the biggest obstacle for such tools is to use automatically generated CSS class names persistently, which means that image files should be named properly, e.g., 'chevron-logo.png' instead of 'lg4155.png'. With the "converter" tool all CSS class names are assigned manually, and can look nice. But it makes communicating with teammates about images hard, because every time, we need to look up both CSS class and file names. Clear names should be enforced at the source, and used consistently through the project. If you agree with that, you are ready to try "process" tools.

Workflow

How to implement the "process" workflow described above with grunt-tight-sprite? One possible scenario:

  • Let's imagine the result first:
    • When we need to place an image, instead of <img> we will use <div> like that:

      <div class="sprite sprite_logo"></div>
    • It will work assuming we have a following CSS in place:

      .sprite {
        background-image:  url(../images/sprite.png);
        background-repeat: no-repeat;
        display:           inline-block;
      }
      
      .sprite_logo {
        background-position: -120px -168px;
        width:  64px;
        height: 64px;
      }
    • We separated static and dynamic CSS properties into two different CSS classes. Now we have to generate .sprite_logo, and other CSS classes for all our images.

    • Now imagine that our artists already put all graphics into pictures/ sub-folder, and all photos into photos/ sub-folder. We can use this information to produce two different sprites. (Obviously we can split files differently, e.g., using a file extension as a hint, or sometimes this information is encoded into a name as a prefix/suffix of some sort -- we can do grunt facilities to select proper files with wildcards.)

  • Now it is time to write some code:
    • Let's add CSS rules for both sprites in an appropriate CSS file:

      .sprite-p {
        background-image:  url(../images/sprite-p.png);
        background-repeat: no-repeat;
        display:           inline-block;
      }
      
      .sprite-j {
        background-image:  url(../images/sprite-j.jpg);
        background-repeat: no-repeat;
        display:           inline-block;
      }
    • Let's write a template for our images, and save it as sprite.tmpl next to our gruntfile:

      .<%= className => {
        background-position: -<%= x %>px -<%= y %>px;
        width:  <%= w %>px;
        height: <%= h %>px;
      }
    • Now it is time to write a Gruntfile.js:

      "use strict";
      
      var imagePath = "assets/images/";
      
      module.exports = function(grunt) {
        grunt.initConfig({
          tight_sprite: {
            sprite_p: {
              options: {
                cssDest: "assets/css/sprite-p.css",
                templateFile: "sprite.tmpl",
                hide: imagePath + "pictures/"
              },
              src: [imagePath + "pictures/**/*.{png,jpg,jpeg,gif}"],
              dest: imagePath + "sprite-p.png"
            },
            sprite_j: {
              options: {
                cssDest: "assets/css/sprite-j.css",
                templateFile: "sprite.tmpl",
                hide: imagePath + "photos/",
                jpeg: { quality: 85 }
              },
              src: [imagePath + "photos/**/*.{png,jpg,jpeg,gif}"],
              dest: imagePath + "sprite-j.jpg"
            }
          }
        });
      
        grunt.loadNpmTasks("grunt-tight-sprite");
        grunt.loadTasks("tasks");
      
        grunt.registerTask("sprite", "tight_sprite");
      };

      Obviously real project will have more tasks defined.

  • Now whenever we modified our images we just run grunt sprite, and everything will be updated automatically provided that we were consistent with our use of CSS classes, and sprite-p.css and sprite-j.css are properly included in HTML files.
  • Profit! :-)

Do not forget to check common recipes listed on the main Wiki page. If you stuck, or have some questions, please check FAQ, which is constantly updated to cover more and more ground.