歳晩の候、皆様におかれましてはますますのご繁栄の事とお喜び申し上げます。
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に渡す引数)のように、experimental
にnot **
が渡されるケースがあります。引数はそのまま次条件文に入るため、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-radius
やbox-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