Sassでlinear-gradient()のmixinをつくる その3
2013年1月も後半ですが、CSS Preprocessor Advent Calendar 2012の記事の続編です。
さて、数日前にWebKitが接頭辞なしのグラデーションをサポートしました。Chrome 26やSafari 7?になれば幸せになりそうです。
でも、GingerbreadのAndroidブラウザーなど、-webkit-gradient()
しかサポートしてない環境に対応しないといけないこともあるでしょう。というわけで、今回は linear-gradient()
から -webkit-gradient()
を生成するmixinを作ります。
Part 5 ― 構文の違いは制約と関数でなんとかする
-webkit-gradient()
は linear-gradient()
と構文が大きく違います。
-webkit-gradient( linear, start, end, colorstops ... )
第一引数っぽいものに linear
という、線形グラデーションを示すキーワードを指定します。そのあとに始点と終点、そしてカラーストップを続けます。
始点と終点ですが、定義済みのキーワードもしくはパーセンテージのみが使えます。linear-gradient()
のように角度を使うことはできません。
カラーストップですが、色と位置を並べるだけの linear-gradient()
と異なり、color-stop()
という関数表記が使われます。
color-stop( stop, color )
linear-gradient()
と色と位置の書き順が逆になります。stopには0から1までの数値、もしくはパーセンテージが指定できます。省略はできません。
まとめると、linear-gradient()
に比べて -webkit-gradient()
では次のことができません。
- 角度が使えない
- カラーストップの位置には数値もしくはパーセンテージのみ
- カラーストップの位置が省略できない
というわけで、mixinとしてふたつの構文を満足させるためには、以下の制約が必要です。
- グラデーションの方向に角度は使わない
- カラーストップはパーセンテージで指定し、必ず書く
これさえ守ってくれるなら、変換はそんな難しくはありません。面倒ですが。
ということで、こんな感じになりました。
@mixin lg-webkit($first, $rest...) { $direction: false; $wk_direction: false; $colorstops: false; // 始点/終点キーワードの変換 $wk_LT: 0 0; $wk_LB: 0 100%; $wk_RT: 100% 0; $wk_RB: 100% 100%; $wk_down: $wk_LT, $wk_LB; $wk_left: $wk_LT, $wk_RT; $wk_up: $wk_LB, $wk_LT; $wk_right: $wk_RT, $wk_LT; $wk_to_TL: $wk_RB, $wk_LT; $wk_to_TR: $wk_LB, $wk_LT; $wk_to_BR: $wk_LT, $wk_RB; $wk_to_BL: $wk_RT, $wk_LB; // 色だった場合 @if type-of(nth($first, 1)) == 'color' { $direction: $first; $wk_direction: $wk_down; $colorstops: $rest; // ISSUE } // 'to' からはじまるキーワードの場合 @else if type-of($first) == 'list' and nth($first, 1) == 'to' { $standard_keywords: to bottom, to left, to top, to right, to top left, to top right, to bottom right, to bottom left, to left top, to right top, to right bottom, to left bottom; $webkit_keywords: ($wk_down), ($wk_left), ($wk_up), ($wk_right), ($wk_to_TL), ($wk_to_TR), ($wk_to_BR), ($wk_to_BL), ($wk_to_TL), ($wk_to_TR), ($wk_to_BR), ($wk_to_BL); $idx: index( $standard_keywords, $first ); @if $idx { $direction: $first; $wk_direction: nth( $webkit_keywords, $idx ); $colorstops: $rest; } } // 角度かそれ以外か @else { $msg: '-webkit-gradient() に変換できないよ: #{$first}'; @warn $msg; /* #{$msg} */ } // いよいよ出力 @if ($colorstops) { $wk_colorstops: null; // いけてないけど…… @if type-of(nth($first, 1)) == 'color' { $wk_colorstops: convert_colorstop($first); } // カラーストップを変換してはくっつけ、を繰り返す @each $cs in $colorstops { $wk_colorstops: join($wk_colorstops, convert_colorstop($cs), comma); } background-image: -webkit-gradient(linear, $wk_direction, $wk_colorstops ); background-image: linear-gradient($direction, $colorstops); } } // color-stop() に変換する関数 @function convert_colorstop($colorstop) { @if unit(nth($colorstop, 2)) != '%' { @warn 'パーセンテージで指定しよう: #{$colorstop}'; } @return color-stop(nth($colorstop, 2), nth($colorstop, 1)); }
ふう。
キーワードのあたりが何やら騒がしいですが、マッピングの考えはパート2と同じです。準備が必要なだけです。むつかしくないです。
個々のカラーストップを color-stop()
へ変換するために convert_colorstop()
という関数を定義しました。@each
で回して都度変換し、出力用の変数にくっつけると。だいぶコスト高そうですが、map() 関数みたいなものがないので……
ちょっといけてないのが、最初の(最初の)引数が color 型だったときの処理でしょうか。$colorstop
を $first, $rest
という感じにできるとよいのですがそうもいきません。可変長引数の型は arglist
という、それ自体がひとつのリストになっています。なので、$first, $rest
とすると長さが2つのリストになっちゃいます。$rest
を @each
で回して…というのもできなくはない気がしますが、だるいのでここは個別に処理します。
あと、もしも角度が入ってしまった場合に、こういうことができます。
// もし角度だった場合 @else if type_of($first) == 'number' and unit($first) == 'deg' { // ちょっとは受け入れて、deg をキーワードに変換 @if index((-900, -540, -180, 180, 540, 900), $first) { $direction: null; $wk_direction: $wk_down; } @else if index((-810, -450, -90, 270, 630, 990), $first) { // $direction: to left; $wk_direction: $wk_left; } @else if index((-1080, -720, -360, 0, 360, 720, 1080), $first) { // $direction: to top; $wk_direction: $wk_upward; } @else if index((-990, -630, -270, 90, 450, 810), $first) { // $direction: to right; $wk_direction: $wk_right; } }
キーワードに変換してあげるやさしさ。リストで指定した範囲より広い角度はダメですが、まあだれが 1080deg
とか書くんだって話です。
使う側の制約は大きいですが、いちおう -webkit-gradient()
にも変換可能だとわかりました。