homeASCIIcasts

268: SASSの基礎 

(view original Railscast)

Other translations: En It Es

Other formats:

Written by Naomi Fujimoto

もうすぐリリースされるRails 3.1の大きな変更点のひとつは、CSSを生成する方法としてデフォルトで提供されるSASSのサポートです。SASSはアプリケーションのスタイルシートの管理を簡素化するために、CSSに標準では含まれないネストや変数、その他多くの機能を提供します。今回のエピソードでは、RailsアプリケーションのCSSをSASSに変換しながら、その利点を実際に見ていきます。

作業の対象となるサイトは、以下のようなものです。

ToDoアプリケーション

スタイルはすべて単純なCSSで書かれていますが、これを外観はまったく変えないままSASSに変換していきます。変更を加えるスタイルシートは次のとおりです。

/app/assets/stylesheets/layout.css

body {
  margin: 0;
  padding: 0;
  background-color: #FFF;
  font-family: verdana;
  font-size: 14px;
}

#header {
  background-color: #03507B;
  color: #FFF;
  padding: 4px 100px;
  border-bottom: solid 5px #00395A;
}

#header h1 {
  font-size: 30px;
}

a {
  color: #03507B;
  text-decoration: none;
}

a:hover {
  text-decoration: underline;
}

.new_project {
  background-color: #03507B;
  color: #FFF;
  padding: 5px 12px;
  margin: 10px 0;
  font-size: 16px;
  border-radius: 5px;
  -moz-border-radius: 5px;
  -webkit-border-radius: 5px;
}
#container { 
  margin: 0 100px; 
}

.project {
  border: solid 1px #777;
  margin: 20px 0;
  padding: 7px 12px;
  border-radius: 10px;
  -moz-border-radius: 10px;
  -webkit-border-radius: 10px;
}

.project h2 { 
  margin: 0; 
}

複雑な部分はないので、このコードをSASSに変換するのは簡単でしょう。まず最初にファイル名に拡張子.scssを追加します。通常のCSSコードは、拡張子が.scssの場合はSASSに完全に対応しているので、ページを再読み込みすると表示はまったく同じになります。

これでこのサイトはSASSを使っていることになりました。これで作業は終わりでしょうか?もちろん違います。しかしCSSが完全にサポートされているということは、使いたいSASSの機能を部分的に取り込むことができるということであり、大きな利点です。すでにいくつかのCSSファイルが設定されたサイトを持っている場合、それらのファイル名を変更して、好きな時に好きなSASSの機能を導入し始めることができます。これから紹介する機能を見たら、すぐに使いたくなることでしょう。

ネスト

最初に使うSASSの機能はネストです。次のCSSを見てください。

/app/assets/stylesheets/layout.css.sass

#header {
  background-color: #03507B;
  color: #FFF;
  padding: 4px 100px;
  border-bottom: solid 5px #00395A;
}

#header h1 { 
  font-size: 30px; 
}

このコードは、headerというidを持つ要素と、そのheader要素内のすべてのh1要素のスタイルを定義します。SASSでは、次のように#headerのスタイルの中にh1のスタイルをネストできます。

/app/assets/stylesheets/layout.css.sass

#header {
  background-color: #03507B;
  color: #FFF;
  padding: 4px 100px;
  border-bottom: solid 5px #00395A;

	h1 { 
		font-size: 30px; 
	}
}

つまりネストされた要素は接頭辞(prefix)を明示しなくてもいいので、作成者に対してネストされた要素をなるべくまとめて記述するように促します。この機能のせいで、調子に乗ってSASSのコードを長くて複雑なネストにしてしまいがちですが、ネストはシンプルに保つ方がいいでしょう。そうすることで、何が何にネストされているかを一目で見ることができます。しかしこれは絶対守らなくてはいけないルールではないので、自身のスタイルシートで試してみて自分に合うネストのレベルを見つけるのがいいでしょう。

またスタイルシートで自己参照をネストすることもできます。今回のCSSのコードには、aのセレクタとa:hoverのセレクタがあります。

/app/assets/stylesheets/layout.css.sass

a {
  color: #03507B;
  text-decoration: none;
}

a:hover { 
  text-decoration: underline;
}

これらについても同じようにネストできるのですが、a:hoverは厳密にはネストされた要素ではないので、親要素を意味する&を付ける必要があります。

/app/assets/stylesheets/layout.css.sass

a {
  color: #03507B;
  text-decoration: none;

  &:hover { 
   text-decoration: underline;
  }
}

&記号を付けなくてはいけないのは、スタイルを適用しようとする対象が、厳密に言うとネストされた要素ではなく、要素の属性だからです。

変数

SASSのもう一つの便利な機能は、変数を定義できることです。今回のCSSファイル内ではいくつかの場所で同じ色を使用しています。その色をサイト全体で変更したい場合、CSSファイルでは各場所に変更を加える必要がありました。

SASSでは変数を定義できるので、これを簡単に行うことができます。SASSの変数名は$記号で始まるので、色の変数を次のように定義します。

$main-color: #03507B;

ファイル内のこの色への参照を変数に置き換えます。

/app/assets/stylesheets/layout.css.sass

$main-color: #03507B;

#header {
  background-color: $main-color;
  color: #FFF;
  padding: 4px 100px;
  border-bottom: solid 5px #00395A;

	h1 { 
		font-size: 30px; 
	}
}

a {
  color: $main-color;
  text-decoration: none;

  &:hover { 
   text-decoration: underline;
  }
}

再度ページを読み込み直すと、表示は同じままなので今のところ問題はないようです。色を変更するときは一ヶ所を修正するだけでよく、スタイルシートの一行を変えるだけで青色を緑色に変えることができます。

/app/assets/stylesheets/layout.css.sass

$main-color: #1E7B12;

ここでページを再度読み込むと、この色を使ったスタイルの部分が変わりました。背景のヘッダ、リンク、「新規プロジェクト」ボタンがすべて緑色に代わったのですが、行った作業はスタイルシートの一ヶ所を修正しただけです。

青色が緑色に変わった

ヘッダの下の線がまだ暗い青色ですが、ヘッダの色を暗くして影のような効果にしようと思います。SASSでは、関数を使うことで相対色を定義できます。全関数のリストをSASSのサイトで見ることができますが、そこに含まれるdarken関数がまさにこの目的で使うことができます。この関数に色とパーセントを渡すと、その色をその割合の分暗くした色が返されます。

ヘッダの下の線の色は#header宣言で定義されます。

/app/assets/stylesheets/layout.css.sass

#header {
  background-color: $main-color;
  color: #FFF;
  padding: 4px 100px;
  border-bottom: solid 5px #00395A;

	h1 { 
		font-size: 30px; 
	}
}

枠は背景色を10%暗くした色なので、「暗い青色」の指定をdarkenの呼び出しに置き換えます。

/app/assets/stylesheets/layout.css.sass

#header {
  background-color: $main-color;
  color: #FFF;
  padding: 4px 100px;
  border-bottom: solid 5px darken($main-color, 10%);

	h1 { 
		font-size: 30px; 
	}
}

ページを再度読み込むと、青色の枠は濃い緑色に変わります。

枠の色も変更された

Mix-in

Mix-inは、SASSで重複を減らすためのもうひとつの機能です。今回のスタイルシートにはborder radiusを持つ要素が2つあります。Border radiusの定義は、すべてのブラウザでradiusが正しく動作することを保証するためにベンダ依存のCSS宣言をしているため、不必要に複雑になっています。

border-radius: 5px;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;

このコードを取り出してmix-inにまとめることができれば、border radiusの宣言はずっと簡単になります。mix-inを定義するときは、@mixinキーワードと共に、他の場所で使う時のための名前を指定します。

/app/assets/stylesheets/layout.css.sass

@mixin rounded-corners {
  border-radius: 5px;
  -moz-border-radius: 5px;
  -webkit-border-radius: 5px;
}

これで、@includeを呼び出すことでどこにでもborder radiusを適用することができます。

/app/assets/stylesheets/layout.css.sass

.new_project {
  background-color: $main-color;
  color: #FFF;
  padding: 5px 12px;
  margin: 10px 0;
  font-size: 16px;
  @include rounded-corners;
}

このスタイルシートには、もうひとつborder-radiusの宣言がありますが、radiusに別の値が設定されています。幸いにもmix-inには引数を渡すことができるので、角丸用のmix-inを次のように修正します。

/app/assets/stylesheets/layout.css.sass

@mixin rounded-corners($radius) {
  border-radius: $radius;
  -moz-border-radius: $radius;
  -webkit-border-radius: $radius;
}

これで、要素にborder radiusを付けたいときに好きな値を渡すことができるようになりました。

/app/assets/stylesheets/layout.css.sass

.project {
  border: solid 1px #777;
  margin: 20px 0;
  padding: 7px 12px;
  @include rounded-corners(10px);
}

次のようにすれば引数のデフォルト値を指定することも可能です。(ただし、今回のスタイルシートではこの方法はとりません。)

/app/assets/stylesheets/layout.css.sass

@mixin rounded-corners($radius: 5px) {
  border-radius: $radius;
  -moz-border-radius: $radius;
  -webkit-border-radius: $radius;
}

SASSを複数ファイルに分割する

たとえSASSを使ったとしても、スタイルシートは長くて扱いにくくなりがちです。そこでスタイルを整理して複数ファイルに分割する方法を見てみましょう。アプリケーションには、Projectsのscaffoldを生成したときに作られたprojects.css.scssファイルがすでに存在しています。このファイルには初期状態ではコメント以外は何もありません。

SASS関連のプロジェクトをすべてprojects.css.scssファイルに移動してみましょう。これによって何が起きるでしょうか?

/app/assets/stylesheets/projects.css.scss

.new_project {
  background-color: $main-color;
  color: #FFF;
  padding: 5px 12px;
  margin: 10px 0;
  font-size: 16px;
  @include rounded-corners(5px);
}

.project {
  border: solid 1px #777;
  margin: 20px 0;
  padding: 7px 12px;
  @include rounded-corners(10px);
}

.project h2 { 
  margin: 0; 
}

ページを再読み込みすると、スタイルが適用されていません。

スタイルがなくなった

SASSエラーが発生しているかどうかを確認するには、ログファイルを見るか、ブラウザでスタイルシートを参照します。

スタイルシートを直接見てSASSエラーを確認する

SASSコードに文法エラーがあるため、スタイルシートの生成が停止しています。今回は、未定義変数に関するエラーが原因です。

Undefined variable: "$main-color".

どうやらメインのスタイルシートで定義した変数は、複数のSASSファイル間では共有されていないようです。

このエラーの原因は、Rails 3.1におけるSprocketsの動作のしくみにあります。変数はSASSファイル間で共有されないのですが、その理由はapplication.cssファイルを見ればわかります。

/app/assets/stylesheets/application.css

/*
 * This is a manifest file that'll automatically include all the stylesheets available in this directory
 * and any sub-directories. You're free to add application-wide styles to this file and they'll appear at
 * the top of the compiled file, but it's generally better to create a new file per style scope.
 *= require_self
 *= require_tree . 
*/

require_tree .の行は、Sprocketsに対してstylesheetsディレクトリ内のすべてのファイルをincludeするよう指示しています。問題は、SASSファイルは結合される前に別々にCSSファイルにコンパイルされるため、変数がファイル間で共有されないのです。これを解決するためには、require_tree .を取り除いてその代わりにSASSに各スタイルシートをインポートさせます。このアプローチの利点は、ファイルが読み込まれる順番を定義できることです。require_treeを使用する場合、ファイルを読み込む順番を決めることはできません。それでは、まずapplication.cssファイルの名前をapplication.css.scssに変更します。そしてrequire_treeの行を削除し、代わりにスタイルシートをインポートします。

/app/assets/stylesheets/application.css.scss

/*
 * This is a manifest file that'll automatically include all the stylesheets available in this directory
 * and any sub-directories. You're free to add application-wide styles to this file and they'll appear at
 * the top of the compiled file, but it's generally better to create a new file per style scope.
 *= require_self
*/
@import "layout.css.scss";
@import "projects.css.scss";

これによって変数やmix-inなどをスタイルシート間で共有することができます。スタイルシートをリロードすると読み込みは成功し、レイアウトスタイルシートで定義した変数をprojectsスタイルシートで参照することができます。

SCSS vs SASS

言語の名前がSASSなのに、スタイルシートの拡張子が.scssなのはどうしてかと不思議に思っている方もいるでしょう。その理由は、SASSが2つの文法をサポートしているからです。SCSSはSASS 3で新たに導入されましたが、古い文法も引き続き利用可能で今後もサポートされる予定です。古い文法を使うには、スタイルシートのファイル拡張子を.sassにします。SASSの文法はSCSSに似ていますが、波カッコや行末のセミコロンは使用せず、代わりに空白文字でブロックレベルを定義します。SCSSの文法は、CSSの上位セットなので、既存のスタイルシートをSASSに移行させる場合は扱いやすいでしょう。どちらを選択するかはあなた次第です。

条件付きスタイルシート

最後に簡単なチップを紹介します。現在のコントローラに応じてincludeするCSSファイルを変えるにはどうすればいいでしょうか? スタイルシートの名称がprojects.css.scssであれば、ProjectsControllerのアクションだけにincludeされると考えられそうですが、実際はそうではありません。すべてのCSSはひとつのスタイルシートにコンパイルされて、アプリケーションの全ページに適用されます。すべてが正しく設定されていれば通常は大丈夫なのですが、時には問題が発生する場合があります。

ひとつの解決方法は、レイアウトファイルのbodyの開始タグを修正して、id属性を追加して値として現在のコントローラ名を持つ方法です。

/app/views/layouts/application.html.erb

<body id="<%= params[:controller].parameterize %>_controller">

コントローラ名に対してparameterizeを呼び出して、名前から不正な文字を除きました。これによって次のようにSASSファイルで対象とするコントローラごとにユニークなidを持つことができます。

/app/assets/stylesheets/projects.css.scss

#projects_controller
{
  .new_project {
    background-color: $main-color;
    color: #FFF;
    padding: 5px 12px;
    margin: 10px 0;
    font-size: 16px;
    @include rounded-corners(5px);
  }

  .project {
    border: solid 1px #777;
    margin: 20px 0;
    padding: 7px 12px;
    @include rounded-corners(10px);
  }

  .project h2 { 
    margin: 0; 
  }
}

このスタイルシートで定義されたスタイルはProjectsControllerのアクションだけに適用されるようになりました。つまりコンロトーラ間で似た名前のidclassが衝突する可能性が低くなるということです。

SASSについての今回のエピソードは以上です。SASSでできることをこのエピソードですべてカバーできなかったので、追加の情報についてはSASSのウェブサイトのドキュメントを参照してください。