2014 年 4 月 23 日

Stylusを使って、iOS retina対応のCSSスプライトを作る

spritesmithでCSSスプライト画像を合成し、CSSも出力することができますが、iOS retina対応にはなりません。
各画像は@2xで合成し、CSSの各座標情報は1/2としたいのですが、そのようなオプションはないようです。

そこで、spritesmithの出力形式にstylusを指定し、このstylusファイルを読み込んで、1/2された各画像のCSSを出力するstylusのfunctionを書くことにしました。

環境

  • node.js v0.10.26
  • grunt-cli v0.1.13
  • grunt v0.4.4
  • grunt-contrib-stylus 0.14.0
  • grunt-spritesmith 1.24.0

gruntからspritesmithを実行

Gruntfileに以下のようなタスクを記述します。

    sprite: {
      icons: {
        src: '<%= dir.src %>/assets/sprites/*.png',
        destImg: '<%= dir.src %>/assets/img/sprite.png',
        destCSS: '<%= dir.src %>/assets/css/sprite.styl',
        algorithm: 'left-right',
        cssFormat: 'stylus',
        cssOpts: {
          function: false
        }
      }
    },

これを実行すると、作成されたsprite.png上の座標情報がstylusの変数として出力されます。以下のものが、スプライトの一つの画像の情報です。実際は全ての画像の変数が出力されます。cssOptsのfunctionをtrueにしておくと、必要なファンクションも出力します。今回はfalseとしますが、サンプルとして下記に掲載します。

$icn-home_x = 222px;
$icn-home_y = 0px;
$icn-home_offset_x = -222px;
$icn-home_offset_y = 0px;
$icn-home_width = 44px;
$icn-home_height = 44px;
$icn-home_total_width = 916px;
$icn-home_total_height = 86px;
$icn-home_image = '../img/fw-sprite.png';
$icn-home = 222px 0px -222px 0px 44px 44px 916px 86px '../img/sprite.png';

spriteWidth($sprite) {
  width: $sprite[4];
}

spriteHeight($sprite) {
  height: $sprite[5];
}

spritePosition($sprite) {
  background-position: $sprite[2] $sprite[3];
}

spriteImage($sprite) {
  background-image: url($sprite[8]);
}

sprite($sprite) {
  spriteImage($sprite)
  spritePosition($sprite)
  spriteWidth($sprite)
  spriteHeight($sprite)
}

出力CSS

上記の情報を元に、以下のようなCSSを出力することを考えます。
positionやwidth,heightを1/2にしています。出力されるファンクションでは1/2する機能がないので、修正が必要ですが、全て自分で書くのも簡単です。

.icon-home {
  background: url("../img/sprite.png") no-repeat -111px 0px;
  width: 22px;
  height: 22px;
  -webkit-background-size: 458px 43px;
  background-size: 458px 43px;
}

stylusのファンクション

独自のsprite()関数に、上記の変数である$icn_homeを渡して、CSSを出力します。
retina()関数は、座標や大きさを1/2にする関数です。値に’px’が付加されているせいか、直接1/2することができませんでしたので、別の関数にしました。operate(‘/’, a, b)としてbuildin functionを呼び出してもよいです。

/*
 * 各スプライトの位置と大きさを読み込む。
 */
@import 'sprite.styl'

/*
 * 座標、大きさを1/2する。
 */
retina($x)
  $x / 2

/*
 * スプライト画像の座標、大きさから、CSSを出力する。
 */
sprite($args)
  background url($args[8]) no-repeat retina($args[2]) retina($args[3])
  width retina($args[4])
  height retina($args[5])
  background-size retina($args[6]) retina($args[7])

/*
 * .icn_homeクラスを出力する。sprite()をmixinで呼び出す。
 */
.icn_home
  sprinte($icn_home)

これで、目的のCSSを得ることができました。stylusは構文がシンプルかつ自由で、非常に導入しやすいCSSプリプロセッサです。LessやSass以外の選択肢としても、最適です。