読者です 読者をやめる 読者になる 読者になる

Sass&CompassでPath風のメニューを作ってみた

2012/12/31 フォロー記事書きました: 1年前のSass/Compassを振り返ってみた

みんな気になってしょうがないPathのアレを、Sass, Compass, CSS3 Animationsのお勉強をかねて作ってみました。Chrome, Safari, Firefox, IE10で動きます。

CSSでPath風メニュー」っていうのは国内外ですでにたくさんあるので、気になったところなどSass&Compass寄りでいくつか書きます。

SCSSの記述量はCSSの約5分の1

今回作ったもので、SCSSで約300行(後述の自作ライブラリ含む)、コンパイル後はCSSで約1550行になりました。
大きいのは、繰り返しで@forループできたところと、ベンダープレフィックスですね。border-radiusやanimation, box-shadowなどを多用する場合はこのぐらいに成ると思います。Sass形式で書くとブレースがない分、まだ若干減りますね。

transition-propertyでtransformを指定する方法

前述のとおり、Compassの最大の利点はベンダープレフィックスをよろしく付けてくれるmixinが用意されてることです。SCSSをこう書くと、

/* SCSS */
@include border-radius(50%);

コンパイル後にこうなります。

/* CSS */
-moz-border-radius: 50%; 
-webkit-border-radius: 50%; 
-o-border-radius: 50%; 
-ms-border-radius: 50%; 
-khtml-border-radius: 50%; 
border-radius: 50%; 

ところが、transition-propertyのように値に対象のプロパティ名を指定するケースでは、値の方にベンダープレフィックスをつけてくれません。これが、

/* SCSS */
@include transition-property(transform);

こうなってしまいます。

/* CSS */
-moz-transition-property: transform;
-webkit-transition-property: transform;
-o-transition-property: transform;
transition-property: transform;

本当はこうなってほしいのです。

/* CSS */
-moz-transition-property: -moz-transform;
-webkit-transition-property: -webkit-transform;
-o-transition-property: -o-transform;
transition-property: transform;

なので、プロパティ名と値の両方にプレフィックスを付けるmixinを作ってtransition-propertyに使ってみました。

transitionとtransformでベンダープレフィックスサポートが異なる場合もあるので、実はあんまりいけてないmixinですけど。。

Compassのベンダープレフィックスサポートはわりとテキトー?

IE10からCSS3 Transitionに対応していますが、上の出力例で分かるようにCompassはtransition-propertyに-msを出力しません。IE10でもTransitionしたいのに!一方、CSS3 Flexible Layout関連には-msが出力されます。Issuesの議論を見ると、入れるかどうかの判断基準は割とゆるそうです。

座標計算をするにはCompassがほぼ必須

Pathのメニューは円形なので、座標計算のためにはsin(), cos(), piなどが必要になります。そういうのがSassには用意されてるものだと思ったのですが、Sassの組み込み数値計算関数は四則演算と丸め関数ぐらいしかありません。

そこでCompassのリファレンスを見てみるとsin, cos含めていくつかのヘルパー関数が用意されていました。

Compassって、-moz-transitionみたいなベンダープレフィックスの便利帳みたいな印象だったのですが、こういう組み込み関数も充実してるんですね。もうSassはCompassとワンセットだと思った方が良さそうです。

@keyframesが鬼門すぎる

CSS3 AnimationsをSass&Compassでうまいことクロスブラウザ対応するのは非常に苦労しました。結論としては以下の2つでなんとかなりました。

  • Sass-3.2.0(alpha版)から導入される@contentを使う
  • SCSS形式をあきらめて一部をSass形式にする (Partial Sass)
やりたいこと

CSS3 Animationsの基本構文は以下のようになります。

/* CSS */
@-webkit-keyframes anim-1 {
  0% {
    width: 0px;
  }
  100% {
    width: 100px;
  }
}
.hoge {
  -webkit-animation-name: anim-1;
  -webkit-animation-duration: 1s;
}

やりたいことは2点

  1. @keyframesのベンダープレフィックスを自動生成したい
  2. 複数要素に対してを繰り返しで@keyframesを生成したい(メニューの周りのちっちゃい丸用)
@keyframesのベンダープレフィックスを自動生成したい

これはmixinとかいろいろ試したのですが、根本的に@keyframesがCSSとしては例外的にネストしている構文なせいで、コンパイルエラーになるか、コンパイル後におかしなCSSになってしまってだめでした。
で、SassのIssuesを漁っていると、@contentというのが次期バージョンから追加されることが分かりました。これは、mixinを呼び出した側のブロックの中身を呼び出されたmixin側が@contentで参照できるもの。まさに@keyframesや@media用に作られたっぽいです。こんな感じ。

/* SCSS */
@mixin keyframes {
  @-moz-keyframes anim-1 {
    @content
  }
  @-webkit-keyframes anim-1 {
    @content
  }
  @-ms-keyframes anim-1 {
    @content
  }
  @keyframes anim-1 {
    @content
  }
}
/* 使い方。この中身が全ての@contentに代入される。 */
@include keyframes {
  0% {
    width: 0px;
  }
  100% {
    width: 100px;
  }
}

開発版のSassのGemはこんな感じでインストールできます。(rvmを使うと複数のGemのバージョンを管理できるのでオススメです)

$ gem install sass -v 3.2.0.alpha.35
複数要素に対してを繰り返しで@keyframesを生成したい

ほんとはこんな感じで複数の要素に異なるkeyframesを割り当てたいのです。

/* SCSS */
@mixin keyframes($name) {
  @-moz-keyframes #{$name} {  /* ここでコンパイルエラー! */
    @content
  }
  ...
  @keyframes #{$name} {
    @content
  }
}
/* 3つの要素に繰り返しkeyframesを生成して設定 */
@for $i from 1 through 3 {
  @include keyframes(anim-#{$i}) {
    0% {
      width: 0px;
    }
    100% {
      width: 10px * $i;
    }
  }
  li:nth-of-type(#{$i}) {
    @include animation-name: anim-#{$i};
  }
}

ところが、keyframesの$nameを設定するところでコンパイルエラーになってしまいます。

Line 1: Invalid CSS after "($name) ": expected expression (e.g. 1px, bold), was "{"

https://github.com/nex3/sass/issues/207 に書いてあるように'#{$name}'のように引用符で囲むとコンパイルは通りますが、CSSとしては引用符で囲むのはInvalidなので、Firefoxでは動きません。。

諦めてsedで置換しようと思ったのですがw、どうもSCSSのパーサーが怪しい気がしたので、Sass形式で試してみるとうまく動きました!

/* Sass */
@mixin keyframes($name)
  @-moz-keyframes #{$name}
    @content
  @-webkit-keyframes #{$name}
    @content
  @-ms-keyframes #{$name}
    @content
  @keyframes #{$name}
    @content

Sassにはアンダースコアで始まるSassファイルを@importで読めるPartialsという機能がありますが、これはSCSS形式でもSass形式でも区別なく読めます。読み出し側と呼ばれる側の形式が異なっても問題なし。なので、普段はSCSS形式で書いていて、keyframesのmixinだけ_keyframes.sassとしてSassファイルで保存すれば、SCSSファイルから、

/* SCSS */
@import "keyframes";

として呼び出せます。Partial Sassとでも言いましょうか。素晴らしい!


CompassではAnimations関連のmixinが定義されていない(このkeyframes問題のせいかな?)ので、まるっと定義しました。ほとんどTransitionsのコピペですが。

まとめ

以前は、HTMLで構造を、CSSで見た目を、JavaScriptで振る舞いを、と言われていましたが、CSSで「動きの見た目」まで定義した方が、JSにはロジックだけ書けてすっきりするし、CSSを適切に書ければ自然とProgressive Enhancementになるので良いかなと思います。ただし、素のCSSは「動き」を記述するにはあまりに貧弱なので、Sass的なものが必須になってきます。


SassもCompassもまだ発展途上でいろいろ問題がありますが、現在進行形で発展しているのでおもしろいです。ガリガリ書き始めると、Sass/SCSSは@includeなど記述の冗長性が気になりますが、デザイナーさんと協業しているところは必要経費かもしれません。


個人的には、より自然で簡易な記述が出来るStylusが気になってます。Rubyで書かれたSassよりもJSのStylusの方が個人的に読みやすいのでw