RSS

compassのベンダープリフィックス制御

by tomita on 2012.12.3


歳晩の候、皆様におかれましてはますますのご繁栄の事とお喜び申し上げます。
NHN Japan ウェブサービス本部開発1室UITチーム(長い) 富田(@a_t)です。

CSS Preprocessor Advent Calendar 2012の3日目、css書くのに便利だからといって盲目的にcompass使ってないでちょっとは中でなにをしているか知っておいてもよかろうもん

ということで
タイトルのとおり、compassがベンダープリフィックスの制御をどのように行なっているかについて書きます。compassのソースをまだ一度も見たことがない人向けの内容です。
sassについてよくしらない、というかたは過去の記事をよむとわかるかもしれません。

CSS3関連のmixinはなにをしてるか

compassのCSS3関連のミックスインは、引数に値を渡してあげるだけで、しちめんどくさいベンダープリフィックスを自動で出力してくれる大変霊験あらたかな機能です。

使っているだけでは中で何をしているかはわかりませんが、compassは公式サイトからsourceの中身を見ることができます。そこからベンダープリフィックスの制御をどうしているか分かりそうです。

box-sizingを例で見てましょう。
http://compass-style.org/reference/compass/css3/box_sizing/
リンク先のview sorceをクリックすると、ソースを見ることができます。

@mixin box-sizing($bs) {
 $bs: unquote($bs);
 @include experimental(box-sizing, $bs, -moz, -webkit, not -o, not -ms, not -khtml, official); }

たった3行でした。ベンダープリフィックどころか普通のCSSすら書いてありません。

どうやら、unquote()で引数の値からクォートを外したあと、プロパティ名、プロパティの値(=引数の値)、ベンダープリフィックスの有無といった内容を、box-sizingからexperimentalというミックスインに渡しているようです。
では、experimentalというミックスインは何をしているものでしょうか。

experimentalはなにをしているか

先ほどの画面の、Importsという箇所に「Shared Utilities」というリンクがあります。ここにはその機能を使うために必要なimportファイルが記載されています。ここを見ればexperimentalのコードが見つかりそうです。
http://compass-style.org/reference/compass/css3/shared/

@mixin experimental($property, $value, $moz: $experimental-support-for-mozilla, $webkit: $experimental-support-for-webkit, $o: $experimental-support-for-opera, $ms: $experimental-support-for-microsoft, $khtml: $experimental-support-for-khtml, $official: true) {
 @if $webkit and $experimental-support-for-webkit {
  -webkit-#{$property}: $value; }
 @if $khtml and $experimental-support-for-khtml {
  -khtml-#{$property}: $value; }
 @if $moz and $experimental-support-for-mozilla {
  -moz-#{$property}: $value; }
 @if $ms and $experimental-support-for-microsoft {
  -ms-#{$property}: $value; }
 @if $o and $experimental-support-for-opera {
  -o-#{$property}: $value; }
 @if $official {
  #{$property}: $value; }
}

ありました。ズラズラと読みづらいですね。

まずは引数の部分から、改行して見やすくしてみます。

@mixin experimental(
 $property,
 $value,
 $moz: $experimental-support-for-mozilla,
 :
 ) {

experimentalの3番目の引数である$mozには、初期値として$experimental-support-for-mozillaが設定されいます。

しかしbox-sizingのミックスインからは、すでに3番目の引数として-mozが渡されているので、この初期値の指定は無視され、$mozには-mozが入ります。

@mixin box-sizing($bs) {
 $bs: unquote($bs);
 @include experimental(
  box-sizing,
  $bs,
  -moz, //← $mozに-mozが渡されている
  :
 );
}

experimentalに渡された$moz:-moz;は、プロパティの表示/非表示を指定している以下の箇所で使われます。

@if $moz and $experimental-support-for-mozilla {
  -moz-#{$property}: $value; }

ここでようやくCSSが出てきました。if文により、$mozと、$experimental-support-for-mozillaの両方がtrueの場合のみ、ベンダープリフィックス付きの値をCSSで出力されるようになっています。

まずはandの左側の$mozからtrue/falseを確認します。
if文では、条件式に文字列が指定されていた場合はtrueと判断されるため、-mozという文字列が入っている$mozはtrueになります。

次に$experimental-support-for-mozillaです。
$experimental-support-for-mozillaは変数ですが、これまでの流れで値を設定する部分は出てきませんでした。ということは、どこかで設定された値が読み込まれていることになります。

公式サイトのexperimentalの画面を見てみると、先程と同様Importsに「Browser Support」というリンクがあります。
http://compass-style.org/reference/compass/support/

また、この画面の右上にある、「Source on Github」というリンクから、githubに上がっているcompassの実際のソースを見ることも出来ます。
https://github.com/chriseppstein/compass/blob/stable/frameworks/compass/stylesheets/compass/_support.scss

これで$experimental-support-for-mozillaの設定値がわかります。
見てみると、

$experimental-support-for-mozilla : true !default;

となっていて、trueが宣言されていることがわかります。

以上により、@ifの条件式である$moz$experimental-support-for-mozillaはいずれもtrueであり、条件式全体もtrueであると判断され、

@if $moz and $experimental-support-for-mozilla {
  -moz-#{$property}: $value; }

がCSSに出力されることになります。

以上が、ベンダープリフィックス出力までの挙動になります。

プロパティが出力されないケース

mixin側の設定で非表示になってる

box-sizingミックスインの中で指定されている、experimentalの引数を見てみます。

@mixin box-sizing($bs) {
 $bs: unquote($bs);
 @include experimental(
  box-sizing,
  $bs,
  not -o, // ← $o
  not -ms,
  not -khtml,
  official);
 );
}

5番目の引数($oに渡す引数)のように、experimentalnot **が渡されるケースがあります。引数はそのまま次条件文に入るため、experimentalでは次のようになります。

@if $o and $experimental-support-for-opera {
-o-#{$property}: $value; }

@if not -o and $experimental-support-for-opera {
-o-#{$property}: $value; }

@if $o@if not -oとなり、notによって文字列-oの真偽値が反転してfalseとなります。結果条件式全体もfalseとなって、プロパティは出力されなくなります。

このケースはcompassのcss3関連のミックスインの定義に依るため、ユーザ側が意識することはありません。(compassが勝手にやってくれます)

逆に言うと、この部分の設定をどうしても変更したい場合は、変更した同名のmixinをcompassのimportより”あと”に定義して、compassのmixinを上書きする必要があります。詳しいやり方については後述します。

全体の設定で非表示になってる

プロパティが出力されないもう一つのケースは、$experimental-support変数がfalseの場合です。

デフォルトでは$experimental-support-for-khtmlはflaseで設定されています。そのため、

 @if $khtml and $experimental-support-for-khtml {
  -khtml-#{$property}: $value; }

このif文の条件式はflaseとなり、-khtmlがついたプロパティは出力されなくなります。

この$experimental-support系の変数の値は、次のように@import宣言前に宣言しておくことでcompass側で設定された変数よりも優先させることができます。

$experimental-support-for-opera:false;
@import "compass";

上記の例では、operaがflaseとなるため、プロジェクト内のすべてのCSS3系のミックスインで一律に-oがついたプロパティが出力されなくなります。

さて、では特定のプロパティに限定して出力を制御したい場合はどうすればよいでしょうか。
次節では対応策として考えられる3つの方法を見ていきます。

特定のプロパティに限定して、ベンダープリフィックスの出力を制御する

border-radiusbox-shadowといった、ブラウザサポートの範囲によってベンダープリフィックスが不要になるプロパティでは、そのプロパティでのみ特定のベンダープリフィックスを非表示にしたい、というケースが起こりえます。

その場合の対応策としては、以下の3つの方法が考えられます。

1.experimentalミックスインを使う
プロパティごとのミックスインは使わず、experimentalミックスインを使い、引数で出力するベンダープリフィックスを指定する方法です
2.同名のmixinを作って上書きする
compassのCSS3系のミックスインと同名のミックスインを作り、compassよりもあとで読み込んで優先的に実行させる方法です
3.compassのソースを書き換える
compassのソース自体を直接編集してしまう方法です

1.experimentalミックスインを使う

CSS3系のミックスインではなく、その中で使われているexperimentalミックスインを使うことで、include時にベンダープリフィックスの有無を制御することができます。

@include experimental(
box-sizing,    //プロパティ名
border-box,    //値
-moz,          //-mozの出力
-webkit,       //-webkitの出力
not -o,        //-oの出力
not -ms,       //-msの出力
not -khtml,    //-khtmlの出力
official       //ベンダープリフィックス無しの出力
);

-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;

出力させたくないベンダープリフィックスにnotをつけるだけです。

プロパティではなく、値にベンダープリフィックスを当てたい場合はexperimental-valueを使います。

@include experimental-value(
display,
flex,
-moz,
-webkit,
not -o,
not -ms,
not -khtml,
official
);

display: -webkit-flex;
display: -moz-flex;
display: flex;

ただし、compassのcss3系のミックスインには、単純にベンダープリフィックスをつけるだけではなく、ブラウザのバクfixも行なっている場合があります。その場合には注意が必要です。

例えばborder_radiusの説明を見てみると、

http://compass-style.org/reference/compass/css3/border_radius/

Note: webkit does not support shorthand syntax for several corners at once. So in the case where you pass several values only the first will be passed to webkit.

という注意書きがあります。要は古いsafariが/で区切ったショートハンドに対応していないため、

@include border-radius(2px 5px,3px 6px);

-webkit-border-radius: 2px 3px;
-moz-border-radius: 2px 5px / 3px 6px;
border-radius: 2px 5px / 3px 6px;

というように、-webkitの値のみcompass側で古いsafariでも表示できる形に変換しているのです。experimentalはベンダープリフィックスをつけるだけのmixinのため、当然このような特別な処理はされなくなります。

※このバグは古いバージョンのsafariのもので、今のsafariではすでに修正されています。バージョンの古いsafariへの対応如何ではこの処理自体不要ですが、しかしこの処理を省く方法はcompassには用意されていません。この処理を省く場合は、ベンダープリフィックスの制御と同様に、experimentalを使うか、同名のmixinで上書きする必要があります。

2.同名のmixinを作って上書きする

同名のmixinが複数存在した場合、あと勝ちになることを利用して、@import compassの後で同名のミックスインを作成して読み込ませることで、ベンダープリフィックスを制御します。

具体的には以下のとおりです。

@import "compass";

@mixin border-radius($radius: $default-border-radius) {
$radius: unquote($radius);
@include experimental(border-radius, $radius,not -moz, not -webkit, not -o, not -ms, not -khtml);
}

こうすることで、@include border-radiusを指定すると、上記の新しく書いたほうのミックスインが読み込まれるようになります。

もちろんこのままでは、不恰好なので上書きしたいmixinをまとめて以下のように読み込んでおくとよいでしょう。

@import "compass";
@import "compass_override";

3.compassのソースを書き換える

Libraryを直接いじることになるので、ちゃんと動くの、とか更新したらどうなるのとか、色々問題がありそうなのでやらないほうがいいと思われます。
/Library/Ruby/Gems/1.8/gems/compass-0.12.2/frameworks/compass/stylesheets/compass/css3
あたりに入っているsassを書き換えれば多分できると思われます。
自己責任でお願いします。windowsのパスは知りません

で、結局どれがいいのよ

1つ目のexperimentalを使う方法では、includeするたびに結構な量の引数を指定しなければならず面倒ですし、3つ目のcompassのソースを書き換える方法も敷居が高いため、一番おすすめする方法は2つ目のcompass側のmixinを上書きする方法となります。
とはいえいきなりすべてのCSS3系のミックスインを置き換えようとすると時間がかかりますので、よく使うものから徐々に移していき、compass_override.scssを充実させてゆくと良いでしょう。

以上がcompassのベンダープリフィックスの挙動と、制御方法についてです。
レッツエンジョイサスアンドコンパスライフ

[PR]
sass本共著しました
(今のところ多分)日本語で書かれた唯一のsass解説本(電子書籍)です
仕様のわりと細かいところまで解説してます。
compassの導入と使い方も解説してます。
なんとsass3.2の追加機能も対応済み。
涙で湖ができました。買うべかな!
https://gihyo.jp/dp/ebook/2012/978-4-7741-5123-6