Go to English Blog

Criando sprites com Spriteful

Leia em 6 minutos

Faz tempo que gerar sprites deixou de ser uma tarefa trabalhosa e chata de se fazer. Existem diversos sites que permitem que você faça isso online, assim como algumas ferramentas integradas ao Rails, como é o caso do Compass. E hoje veremos uma alternativa que surgiu há algum tempo, mas que muita gente não conhece. Trata-se do Spriteful.

O Spriteful, projeto criado pelo Lucas Mazza, é uma ferramenta command-line escrita em Ruby que pode ser usada sem ter que adicionar uma dependência ao seu projeto Rails, por exemplo. Antes, era muito comum adicionarmos o Compass apenas por causa da geração de sprites, mas depois do surgimento do Bourbon, isso não fazia mais sentido (sim, eu acho o Bourbon infinitamente melhor que Compass).

Outra vantagem é que agora você pode gerar sprites facilmente mesmo sem estar em um projeto Rails. Isso porque o Spriteful é totalmente independente; basta instalar a ferramenta e começar a gerar seus sprites, com saída para CSS ou SCSS.

E as vantagens do Spriteful não param aí! Você tem otimização dos sprites gerados automaticamente, assim como suporte para sprites SVG (que também tem algumas otimizações).

Agora, vamos explorar um pouco a ferramenta!

Instalando o Spriteful

Para instalar o Spriteful, você precisará ter o Ruby instalado. Assumindo que você já tem, basta instalá-lo com o comando gem.

$ gem install spriteful
Fetching: spriteful-0.3.4.gem (100%)
Successfully installed spriteful-0.3.4
1 gem installed

Gerando sprites

Neste exemplo, vou usar os ícones de bandeiras do famfamfam. Ele contém 229 arquivos PNG, totalizando 128KB. Para gerar um único sprite, basta executar o comando spriteful.

$ spriteful flags
  create  flags.png
  create  flags.css

Viu como foi fácil? Este comando gerou o sprite e o arquivo CSS. Como o spriteful faz a optimização da imagem, temos um único arquivo final com apenas 71KB.

Agora, basta incluir o CSS na sua página e usar as classes .<nome do sprite>.<nome da imagem>, por exemplo .flags.br.

/*
 * This Stylesheet was generated by the 'spriteful' gem, with the following options:
 * 'spriteful flags --scss'.
 * Below there are several CSS classes to use the 'flags'
 * sprite on you HTML code, as in:
 *
 *  <div class='flags ad'>Your markup here.</div>
 */

.flags {
    background-image: url(flags.png);
    background-repeat: no-repeat;
}

.flags.ad {
  background-position: 0px 0px;
}

/* ... */

Você pode adicionar um <span class="flags br"></span>, e ter um CSS adicional como esse:

.flags {
  display: inline-block;
  width: 16px;
  height: 11px;
}

Se quiser, também pode gerar o arquivo SCSS, o que irá facilitar o uso do sprite em pseudo-elementos, por exemplo.

$ spriteful flags --format=scss
   identical  flags.png
      create  _flags.scss
// ============================================================================
// This Stylesheet was generated by the 'spriteful' gem, with the following options:
// 'spriteful flags --format=scss'.
// Below there are several [Placeholder Selectors]
// (http://sass-lang.com/documentation/file.SASS_REFERENCE.html#placeholder_selectors_)
// to use the 'flags' sprite on your SCSS code, as in:
//
// .my-thing {
//   @extend %flags-sprite-ad;
//   // Your styles here.
// }
//
// There also will be available a variable named '$flags-sprite-names'
// with all the images inside this sprite.

%flags-sprite {
  background-image: url(flags.png);
  background-repeat: no-repeat;
}

%flags-sprite-ad {
  @extend %flags-sprite;
  background-position: 0px 0px;
}

# ...

No caso do SCSS, basta fazer algo assim:

.flag-br:before {
  @extend %flags-sprite-br;

  content: '';
  display: inline-block;
  width: 16px;
  height: 11px;
}

O Spriteful possui diversas opções que permitem configurar o caminho de exportação das imagens e CSS.

$ spriteful -h
Usage:
  spriteful sources [options]

Options:
  -f, [--format=FORMAT]                  # Format to generate the sprite(s) stylesheet(s). Either "css" or "scss".
                                         # Default: css
  -s, [--stylesheets=STYLESHEETS_DIR]    # Directory to save the generated stylesheet(s), instead of copying them to the clipboard.
                                         # Default: /Users/fnando/Projects/samples/spriteful-samples
  -d, [--destination=DESTINATION_DIR]    # Destination directory to save the combined image(s).
                                         # Default: /Users/fnando/Projects/samples/spriteful-samples
  -r, [--root=ROOT_DIR]                  # Root folder from where your static files will be served.
  -t, [--template=TEMPLATE]              # Custom template file in ERB format to be used instead of the default.
      [--mixin], [--no-mixin]            # Choose to use the Mixin Directives instead of Placeholder Selectors.
      [--rails], [--no-rails]            # Follow default conventions for a Rails application with the Asset Pipeline.
      [--horizontal], [--no-horizontal]  # Change the sprite orientation to "horizontal".
      [--save], [--no-save]              # Save the supplied arguments to ".spritefulrc".
      [--spacing=N]                      # Add spacing between the images in the sprite.
  -v, [--version], [--no-version]
      [--optimize], [--no-optimize]      # Optimizes the combined PNG and inline SVG images.
                                         # Default: true

Runtime options:
  [--force]  # Always overwrite files that already exist.

Generates image sprites with corresponding stylesheets.

Se você quiser, pode salvar as opções geradas em um arquivo .spritefulrc. Basta usar o switch --save.

$ spriteful flags --format=scss --save
   identical  flags.png
   identical  _flags.scss
      create  .spritefulrc

Gerando sprites SVG

Como eu já disse, o Spriteful possui suporte para sprites SVG. Neste exemplo vou usar alguns ícones do projeto Tango. O sprite de SVG não irá juntar todos os SVGs em um único arquivo; em vez disso, um arquivo CSS com as imagens inline é gerado. Por isso, evite gerar sprites de arquivos SVG muito grandes.

Para gerar o sprite, basta usar o mesmo comando spriteful. O formato do arquivo é detectado automaticamente.

$ spriteful devices --format=scss
      create  devices.png
      create  _devices.scss

Algumas otimizações são realizadas no arquivo SVG, reduzindo o tamanho do arquivo antes de fazer o carregamento inline. O arquivo SCSS será parecido com isso:

// ============================================================================
// This Stylesheet was generated by the 'spriteful' gem, with the following options:
// 'spriteful devices --format=scss'.
// Below there are several [Placeholder Selectors]
// (http://sass-lang.com/documentation/file.SASS_REFERENCE.html#placeholder_selectors_)
// to use the 'devices' sprite on your SCSS code, as in:
//
// .my-thing {
//   @extend %devices-sprite-audio-card;
//   // Your styles here.
// }
//
// There also will be available a variable named '$devices-sprite-names'
// with all the images inside this sprite.

%devices-sprite {
  background-image: url(devices.png);
  background-repeat: no-repeat;
}

%devices-sprite-audio-card {
  @extend %devices-sprite;
  background-position: 0px 0px;

  .svg & {
    background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxuczpzdmc...');
    background-position: 0 0;
  }
}

Note que é feito fallback para a versão em PNG; caso o navegador não tenha suporte, uma versão alternativa será carregada. Para que você possa usar o SVG, é preciso ter o Modernizr disponível em sua página com a opção Misc > SVG disponível. O uso do sprite é igual ao que já fizemos antes.

.device-audio-card:before {
  @extend %devices-sprite-audio-card;

  content: '';
  display: inline-block;
  width: 48px;
  height: 48px;
}

Usando em um projeto Rails

O Spriteful também possui uma opção para quem trabalha com projetos Rails; essa opção permite que você exporte os sprites do diretório app/assets/images/sprites, salvando os arquivos de CSS em app/assets/stylesheets/sprites, tudo devidamente integrado com os helpers de Asset Pipeline. Para usá-la, basta utilizar a opção --rails.

Imagine que você tem os diretórios app/assets/images/sprites/icons e app/assets/images/sprites/flags, ao utilizar a opção --rails você terá dois sprites distintos; um para cada diretório.

$ spriteful --rails
   create  app/assets/images/sprites/flags.png
   create  app/assets/stylesheets/sprites/flags.css.erb
   create  app/assets/images/sprites/icons.png
   create  app/assets/stylesheets/sprites/icons.css.erb

Note que os arquivos são gerados com a extensão .erb; isso é necessário para que seja possível utilizar os helpers de asset pipeline mesmo quando você não estiver utilizando SCSS.

Usando um template personalizado

Às vezes você quer gerar uma estrutura diferente da sugerida pelo Spriteful; neste caso, sua melhor chance será utilizar um arquivo de template personalizado.

Como você viu, o Spriteful não permite que você use pseudo-elementos por padrão, por isso iremos criar um template personalizado com este objetivo.

Primeiro, copie o template original com o comando spriteful template --format=scss. Isso irár gerar um arquivo spriteful.scss contendo as marcações eRB. Agora, basta alterarmos o template para que ele exporte o arquivo com as marcações de pseudo-elemento.

// ============================================================================
// This Stylesheet was generated by the 'spriteful' gem, with the following options:
<%- if Spriteful.options -%>
// 'spriteful <%= cli_options.join(' ') %>'.
<%- end -%>
<%- if mixin? -%>
// Below there are several [Mixin Directives]
// (http://sass-lang.com/documentation/file.SASS_REFERENCE.html#mixins)
<%- else -%>
// Below there are several [Placeholder Selectors]
// (http://sass-lang.com/documentation/file.SASS_REFERENCE.html#placeholder_selectors_)
<%- end -%>
// to use the '<%= sprite.name %>' sprite on your SCSS code, as in:
//
// .my-thing {
//   <%= extension_strategy %><%= class_name_for(sprite) %>-sprite-<%= class_name_for(sprite.images.first) %>;
//   // Your styles here.
// }
//
// There also will be available a variable named '$<%= class_name_for(sprite) %>-sprite-names'
// with all the images inside this sprite.

<%= extension_prefix %><%= class_name_for(sprite) %>-sprite {
  <%- if rails? -%>
  background-image: image-url('<%= image_url(sprite) %>');
  <%- else -%>
  background-image: url(<%= image_url(sprite) %>);
  <%- end -%>
  background-repeat: no-repeat;
}

<%= extension_prefix %><%= class_name_for(sprite) %>-icon {
  content: '';
  display: inline-block;
}
<% sprite.each_image do |image| %>
<%= extension_prefix %><%= class_name_for(sprite) %>-sprite-<%= class_name_for(image) %> {
  <%= extension_strategy %><%= class_name_for(sprite) %>-sprite;
  background-position: <%= image.left %>px <%= image.top %>px;
  <%- if image.svg? %>
  .svg & {
    background-image: url(<%= data_uri(image) %>);
    background-position: 0 0;
  }
  <%- end -%>
}

.<%= class_name_for(sprite) %>-<%= class_name_for(image) %>:before {
  @extend <%= extension_prefix %><%= class_name_for(sprite) %>-sprite-<%= class_name_for(image) %>;
  @extend <%= extension_prefix %><%= class_name_for(sprite) %>-icon;
  width: <%= image.width %>px;
  height: <%= image.height %>px;
}
<% end %>

$<%= class_name_for(sprite) %>-sprite-names: <%= sprite.images.map { |i| class_name_for(i) }.join(' ') %>;

Para exportar o sprite, basta fornecer a opção --template.

$ spriteful flags --format=scss --template=spriteful.scss
  create  flags.png
  create  _flags.scss

O arquivo gerado será algo como isso:

%flags-sprite {
  background-image: url(flags.png);
  background-repeat: no-repeat;
}

%flags-icon {
  content: '';
  display: inline-block;
}

%flags-sprite-ad {
  @extend %flags-sprite;
  background-position: 0px 0px;
}

.flags-ad:before {
  @extend %flags-sprite-ad;
  @extend %flags-icon;
  width: 16px;
  height: 11px;
}

Perceba que além dos placeholders de SCSS também estamos gerando o seletor .flags-sprite-<nome da imagem>:before; isso permite que você use a classe .flags-ad sem ter que definir manualmente os pseudo-elementos. WOOT!