Skip to content

Instantly share code, notes, and snippets.

@eprothro
Last active December 27, 2015 10:09
Show Gist options
  • Save eprothro/7309610 to your computer and use it in GitHub Desktop.
Save eprothro/7309610 to your computer and use it in GitHub Desktop.
Sass sprite generation with compass for retina and non-retina images (example code for a Rails 3 application).

Overview

The below Sass+Compass/Ruby can be used to:

  • Generate retina and non-retina sprite maps
  • Allow a single style class to provide retina and non-retina support, per image
  • Automatically generate those sprite style classes

Example markup for a retina supported menu button:

%a.icon-hamburger

To do this, we need:

  • 1x and 2x Sprite images
  • Sass to generate the sprite maps and styles
  • A custom sass function to help dynamically set the background-width of the 2x styles

1x and 2x Sprite images

Place sprite images in .../icon and .../icon2x directories.

  • 2x image widths must be of an even number of pixels
  • Every 2x image must have a corresponding 1x image (with the same name) and vice-versa

Sass, using compass to generate the sprite maps and styles

// Using Compass spriting, see http://compass-style.org/help/tutorials/spriting/
// Compass is generating the sprite map and styles from images in /app/assets/images/web/mobile/icon[2x]/*.png.
// 
// NOTE: 2x icons must be an even number of pixels wide to avoid clipping/bleed.
// NOTE: Adding an icon will cause the sprites to be regenerated.

// Reduce sprite-map waste for variable-width icons
$icon-layout: horizontal
$icon2x-layout: horizontal

// Include dimensions for span background-image support
$icon-sprite-dimensions: true

// Ensure no overlap between sprites in the map
$icon-spacing: 6px
$icon2x-spacing: 12px

// Create 1x and 2x sprite map images
@import "web/mobile/icon/*.png"
@import "web/mobile/icon2x/*.png"

// Use 1x sprite for non-high-dpi screens
@include all-icon-sprites

// Override the sprite image for high-dpi screens
// and adjust background-size so 1x positions will work correctly
// for all 1x sprite style classes
@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)
  #{$icon-sprite-base-class}
    background-image: $icon2x-sprites
    background-size: image-width(asset_path((sprite-url($icon-sprites)))) image-height(asset_path((sprite-url($icon-sprites))))

Custom Sass function

# /lib/assets/sass_functions.rb

module Sass::Script::Functions
  # return asset path given a url
  def asset_path(url)
    assert_type url, :String
    
    begin
      relative_path = url.value.match(/url\(.*\/assets\/(.*)\)/)[1]
    rescue StandardError
      raise "Didn't know how to find relative asset match for #{url.value}"
    end

    file = "#{Compass.configuration.images_path}/#{relative_path}"
    unless File.exist?(file)
      # for asset precompilation
      file = "#{Rails.root}/public/assets/#{relative_path}"
    end

    Sass::Script::String.new(file)
  end
  declare :asset_path, :args => [:url]
end

Rails with asset precompilation

We must require the library file explicitly

# /config/initializers/assets/sass_functions.rb

if defined?(Sass)
  require File.expand_path("./lib/assets/sass_functions.rb")
end

We also must require initializers explicitly during asset initialization (e.g. precompilation)

# config/applciation.rb

module AssetsInitializers
  class Railtie < Rails::Railtie
    initializer "assets_initializers.initialize_rails", group: :assets do |app|
      Dir["#{Rails.root}/config/initializers/assets/*.rb"].each{ |file| require file }
    end
  end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment