morishitterのCSSの書き方(2016年夏)
今、自分がどうやってCSSを書いているのかについてまとめる。
CSSを書く前にすること
持論だが、「デザインの意図を正確に理解した上で書かれたCSSは破綻しない」と思っている。 しかし、自分ひとりでサービスを作るときような、デザインの決定権を持つ人とUI実装者が同じである場合を除いて、デザインの意図を正確に伝え、理解することは難しい。
僕が1番時間を使うのがこの工程だ。
今の仕事ではデザイナーがSketchファイルを作成し、エンジニアがそれを元に実装する。 Sketchファイルを開き、アートボードをひたすら眺めデザインの矛盾がないかを確認し、「なぜこのようなデザインなのか」を質問しまくる。
ここで良い質問と提案をするためにも、エンジニア側に最低限のデザインに対する知識が必要だと思う。 最近読んだ本だと、「みんなではじめるデザイン批評―目的達成のためのコラボレーション&コミュニケーション改善ガイド」が良かった。 かなり現場寄りな内容で、コミュニケーションの取り方について書いてある。
みんなではじめるデザイン批評―目的達成のためのコラボレーション&コミュニケーション改善ガイド
- 作者: アーロン・イリザリー,アダム・コナー,長谷川恭久,安藤貴子
- 出版社/メーカー: ビー・エヌ・エヌ新社
- 発売日: 2016/05/24
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (1件) を見る
ビルドフロー
僕はCSSのプリプロセス、最適化といった一連のフローでPostCSSのエコシステムを使っている。
プリプロセスとしては、
- postcss-import(
@import
を使ったファイルのインライン展開) - postcss-custom-properties(Custom Propertiesの記法でグローバル変数を定義できる)
- postcss-apply(Custom Sets of Propertiesを展開する)
を利用している。 できるだけ将来CSSの仕様に入るようなものだけを使うようにしている。
ポストプロセスとしては、
- autoprefixer(ベンダープリフィックスの自動付与)
- postcss-flexbugs-fixes(flexboxのバグの中で、CSSの変換のみで対応可能なものを直す)
- csso(minifyツール, PostCSSのプラグインとしての実装もある)
などを使っている。
他にリンターとしてstylelint、フォーマッターとしてstylefmtを使っている。 また、postcss-style-guideを使ってスタイルガイドの生成をビルドフローの中に入れている。
設計手法
次に設計手法、どういう考えでルールセットを分けているかについて。 CSS設計の本質は、セレクタの命名規則ではなく、ルールセットの分割粒度だと思っている。
僕はスタイルを6つのレイヤーに分けて考えている。
ディレクトリの分け方はこんな感じ。
app.css
は全てのレイヤーのCSSファイルを@import
しているだけ。
$ tree src/css -L 1 src/css ├── app.css ├── base ├── components ├── pages ├── patterns ├── settings └── tools
Settingsレイヤー
Settingsレイヤーは、カラーパレットやフォント等、アプリケーション全体で共有する値をCustom Propertiesやプリプロセッサーの変数を用いて定義しておく。
:root { --black: #333; --darkGray: #777; --gray: #aaa; --lightGray: #ddd; --white: #fff; --red: #ff4136; --green: #2ecc40; --blue: #0074d9; }
Toolsレイヤー
CSSには複数のプロパティを組み合わせることで、デザイン的な意味を持つものがある。
代表的なものはテキストを切り取り、末尾に3点リーダーを付けるようなものだ。
Toolsレイヤーでは、@apply
ルールやプリプロセッサーのMixinを使って抽象化し、名前を付ける。
:root { --truncate: { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }; }
このレイヤーは現状プリプロセッサーの利用が必須になるので別になくてもいいと思う。
Baseレイヤー
Baseレイヤーにはnormalize.css等を使った、ブラウザ間のデフォルトスタイルシートの差異をなくしたりリセットしたりするコードを記述する。
また、font-family
やbox-sizing
、ベースとなるfont-size
の指定など、アプリケーション全体の設定もここに記述する。
Patternsレイヤー
Patternsレイヤーは後述するComponentsよりも抽象度の高いレイヤーで、Components間にあるデザインのパターンについて書く。 有名な例だとmedia objectがこれに当たる。
コンポーネントの例として頻繁に「ボタン」が挙げられているのを目にする。 僕は、フォローボタンや送信ボタン、キャンセルボタンと呼ぶものはComponentsで、「ボタン」はPatternsとして定義している。
.button { display: flex; align-items: center; justify-content: center; cursor: pointer; ... } .button:hover { ... } .button:focus { ... }
「ボタンをボタンたらしめる」ためのスタイルをPatternsレイヤーに書き、より具体的なものをComponentsに書くといったイメージ。 また、アニメーションもこのレイヤーに含めている。
Componentsレイヤー
Componentsレイヤーには、実際にユーザーが見て、触れるものを定義する。 Componentsはコンポーネント内のレイアウトについての記述は持つが、コンポーネントと同士の位置関係については考慮しない。
Componentsの命名規則としてはよくBEM (MindBEMding)を利用している。 また、スタイルガイドジェネレーターを使ってスタイルガイドを作るときは、Componentsレイヤーのファイルに生成のために必要な情報(タイトルとかマークアップとか)を書く。
そのコンポーネントがパターンを持つ場合は、class
属性には複数の値を指定することになる。
<button class="button FollowButton"> Follow </button>
Pagesレイヤー
最後にPagesレイヤーについて。 よくデザイナーの人が作っている、Sketchの固定幅のアートボード上に作りこまれている見た目はここ。 (僕たちはこれを見て再利用しやすく、拡張しやすいCSSを書く必要がある。)
Pagesレイヤーでは、最終的にデバイスのディスプレイに表示されることを定義するためのレイヤーで、Componentsを並べ、レイアウトする。 全てのページは、コンポーネントの組み合わせで作っていく。 ざっと、ページ間で再利用するものがコンポーネント、コンポーネント間で再利用するものがパターンというイメージ。
ECSSIST
僕はこの独自の設計思想にECSSIST (Extensible CSS Idealistic Structure Theory)と名前を付けた(「イグジスト」って読ませる)。 別に名前はどうでもいいけど、名前を付けることで理解した気になってもらえやすくなる節がある。
ルールセットの分割は見えているものをより細かくなるように分けるのではなく、スタイルの抽象度で分けるのがいいと思っている。 だから、Atomic DesignでいうAtomsもMoleculesもOrganismsも、目に見えてユーザーが触れるものなのでECSSISTでは全てComponentsになる。
ぶっちゃけHarry RobertsのITCSSに似ていて、ECSSISTもITCSSを自分なりにアレンジしていく中で生まれた。 ITCSSとの違いは、Pagesレイヤーの追加と、汎用クラスを作らないところだ。 「デザインの意図を理解していれば汎用クラスはいらない」という理想論のもとECSSISTは生まれた。
実際に使ってどうなの?
ECSSISTは名前の通り、理想論だ。 デザイナーとエンジニアで別れて仕事をしている以上、そのデザインの意図を完全に理解できることはない。 (またデザインに意図が存在しない場合もある。)
結果、僕は今のプロジェクトではutilities
というディレクトリを切って、そこにいくつかの汎用クラスを定義している。
また、論理的な理由はないが「どうしてもここは譲れない」というデザインもある。
このようなものに対してどうCSSを書くか考え、creative.css
というファイルを作って対応している。
これも同じくHarry Robertsの言うshame.cssと同じようなものだ。
あと、いきなりPatternsを作っていくのは難しい。 見た目だけで再利用を計ると、それがデザインの意図とあわない場合、破綻の原因となる。 僕はまずComponentsをどんどん書いていって、共通化できるものをPatternsにしていくという流れでやってる。
CSSの設計手法なんていろいろあるけど、自分自身の考えやチームの開発体制、デザインプロセスに合わせて調整していくものだと思う。
よくある質問
その他、よく質問されることについての回答。
PostCSSのメリットは何?
- 小さいからコントリビュートしやすい
- 必要な機能を選んで使える
- プラグインを書いて独自のカスタマイズができる
僕はPostCSSを昔から追いかけていて、何かあっても対応できるからってのが大きい。 自分は既存のプリプロセッサーに不満を持っていて、独自のものを作りたいという衝動でPostCSSに目を付けたので、強い意志がない場合はプリプロセッサーはSassとかを使ってもいいと思う。
CSS Modulesについてどう思う?
僕は使わない。
ReactコンポーネントとCSSのコンポーネントの粒度は同じにならないと思っていて、CSSのコンポーネントの方がJSのコンポーネントよりも多くなる(細かくなる)。 JSとCSSのコンポーネントが1対1に対応しないので、CSS ModulesやRadiumなどのCSS in JS系ツールのアプローチは合わないと思っている。
しかしこれは、僕が比較的CSSの設計手法について知見や意見があるからだ。 「CSSはよくわかってなくて、雑な命名でもなんとかやっていきたい」って人には良いと思う。 似たようなことを以前も書いた。
あと、CSS Modulesはセレクタ名を適当な一意な文字列に置き換えてそれをHTMLのclass属性値と対応させるってことをやっているんだけど、それがそもそも筋が悪い。
できるだけ標準化されたものを使いたくて、各ブラウザでのShadow DOMの実装が待たれる。
ちなみに、@scope
ルールは消えた。
どうすれば壊れないCSSが書けるの?
まず基本として、CSSのカスケーディングという機能について正確に理解する必要がある。 詳細度の計算方法と、値が継承されるプロパティについて理解できると良い。
また、ここまでで「デザインの意図を正確に理解した上で書かれたCSSは破綻しない」と述べた。 あまりに変更が繰り返されるデザインにはそもそもそこには意図がなく、行き当たりばったりでデザインされていることが多い。 それでは良いCSSの設計ができるはずもない。 壊れない、良いCSS設計には、良いデザインプロセスが必要になる。
この良いデザインプロセスに変えるというのが難しくて、個々人のモノ作りに対するリテラシーによるものが大きいので、組織レベルで変えるにはどうすればいいんだろうと思ってる。 良いモノ作りがしたい。
最近どうなの?
近況です。
いつものアレです。
CSS Modules 所感
CSS Modulesという、CSSの新しい設計概念・指針のようなものがある。 CSS Modulesチームの1人であるGlen Maddern氏が書いた「CSS Modules - Welcome to the Future」という記事の翻訳がバズっていたので、僕がCSS Modulesについて思ったことをまとてみる。「CSS Modulesとは何か」ということは、上記の記事に書かれているのでここではあまり触れない。
CSS Modulesとコンポーネント設計
CSSのルールセットは全てがグローバル定義であり、CSS(Cascading Style Sheets)というスタイルシート言語の最大の特徴である"カスケーディング"という機能により、CSSファイルを見ただけでそのスタイルの影響範囲を理解することは難しい。 CSS Modulesは、CSSのルールセットの影響範囲を、Webアプリケーションのいわゆるコンポーネント単位で閉じようという考えで作られた。
ReactやAngular等の主要JSフレームワークは、コンポーネント設計を強要する。 またBEMやSMACSSといったCSSの設計手法も、コンポーネント単位でのスタイリングを意識させる。 このように、Webアプリケーションのコンポーネント設計は、今やかなり当然のことになっているように思う。
CSS Modulesチームの人々はReactユーザーであり、CSS ModulesもReactを使うことを前提に開発されているように思う。 CSS Modulesの簡単な例を示すと、以下のCSSファイルで定義されたルールセットの影響範囲を以下のReact Componentを定義したHTML(JSX)の中で閉じることができる。
/* Logo.css */ .logo { background: url('./logo.svg'); background-size: 200px 200px; width: 200px; height: 200px; }
import styles from './Logo.css'; import React, { Component } from 'react'; export default class Logo extends Component { render() { return <div className={styles.logo} />; } };
グローバルなルールセットの定義の仕方や、コンポーネント間でのスタイルの再利用(ローカルスコープのルールセットの再利用)も可能である。 CSS ModulesのGitHubのREADMEや、デモのリポジトリにサンプルコードがあるのでそれらを見てみるといい。
CSS Modulesの仕組み
CSS ModulesがどのようにしてCSSにローカルスコープを与えているのかについて。 CSS Modulesの実装は内部でcss-loaderというwebpack loaderを使っており、言語拡張としていくつかPostCSSのプラグインを作っている。(css-modulesifyというBrowserifyでCSS Modulesを使うためのプラグインも作られてはいるが、現在はhighly experimentalということになっている)
css-loaderは、
:local(.localClass) { color: green; } :local(.subClass) { color: red; } :global(.globalClass) { color: blue; }
このように書いたCSSを
._23_aKvs-b8bW2Vg3fwHozO { color: green; } ._13LGdX8RMStbBE9w-t0gZ1 { color: red; } .globalClass { color: blue; }
このように変換する。
セレクタ名をデフォルトではBase64でエンコードし、以下のようにexports
することで、JavaScriptからCSSを参照できる。
webpack.config.js
にcss-loader?localIdentName=[name]__[local]___[hash:base64:5]
のように書くことで、変換後のセレクタ名を指定することもできる。
exports.locals = { className: "_23_aKvs-b8bW2Vg3fwHozO", subClass: "_13LGdX8RMStbBE9w-t0gZ1" }
:local()
に書かれたセレクタがエンコードされることで他のルールセットのセレクタ名と衝突しなくなり、結果的にローカルスコープを得たようになる。
:global()
に書かれたセレクタはそのまま展開され、通常のCSSのルールセットと同じ振る舞いをする。
しかし、CSS Modulesでは全てのルールセットはデフォルトでローカル定義となる。
CSS Modulesでは、普通に書かれたセレクタをcss-loader
で変換できるように:local()
の中に突っ込む処理をしていて、これをpostcss-modules-local-by-defaultというPostCSSのプラグインが担当している。
他にも、CSS Modulesの
element { composes: large from "./typography.css"; }
このようなcomposes
キーワードによるコンポーネント間でのスタイルの再利用のための構文は、postcss-module-scopeとpostcss-modules-extract-importsで実現されている。
これら3つのプラグインを使ったCSS ModulesのPostCSSのプラグインパック(css-modules-loader-core)もある。
css-loader
とpostcss-modules-local-by-default
を使うと、上記のLogo.css
を以下のように変換できる。
.Logo__logo___3xrQQ { background: url(data:image/svg+xml;base64,... background-size: 200px 200px; width: 200px; height: 200px; }
module.exports = { logo: "Logo__logo___3xrQQ" }
CSSからJSに互換性を保ったまま情報を渡すことができる上記のフォーマットを、ICSS(Interoperable CSS)と呼んでいる。
CSS in JS系ツール 所感
ここまで長々とCSS Modulesのコンセプトや動作する仕組みについて述べてきたが、本当に伝えたいのはここから。
まず、CSS in JS系のツールについて。 今のCSS in JSの勢いに火がついたのは、Facebookの@Vjeux氏の「React: CSS in JS」という発表からだと感じている。
この発表で@Vjeux氏はCSSの問題点をいくつか挙げ、これらをReactのInline Stylesを使って解決できると述べた。 そして、この発表を受けていくつかのCSS in JS系ツールが作られている。
インラインに(HTMLのstyle属性に)CSSを展開すると、全てのルールセット(ルールセットではないが)の詳細度を一定に保つことができ、セレクタも存在しないのでカスケーディングの一部を禁止することもできる。
しかし、僕はインラインにCSSを展開する系のツールは クソ 微妙だと感じている。
理由はこんなところだ。
- プロパティー宣言しか書けない(擬似クラスもメディアクエリーも使えない)
- ネットワークはWebアプリのパフォーマンスのボトルネックとなるので、CSSファイルとしてキャッシュしたい
- valueが継承されるプロパティーのカスケーディングは結局止められない
CSSerとしての本能が受けつけない
あと、JSコード内にJSオブジェクトとしてスタイルを記述するものもあるが、シンタックスハイライトが適切でなかったりするのでそれも嫌だ。
CSS Modules 所感
その点、CSS Modulesは他のCSS in JS系ツールよりも良いものだと思う。(CSS ModulesをCSS in JS系ツールに含めるかは微妙なところだが。) 擬似クラスとメディアクエリーも使えるし、普通にCSSを書いている感覚でコンポーネントごとにローカルスコープを持つことができる。
しかし、CSS Modulesの一般的というか、ユーザーとしての感想を述べると、BEM等のCSSの設計手法になじんでいる人にとっては必要ないと感じた。
CSS ModulesはReact Componentの分割粒度でCSSを書くことを強制する力があって、かつそこにはスコープがあるのでセレクタの命名をそこまで意識しないで適当につけても破綻することがなくなる。
import button from './Button.css'; import React, { Component } from 'react'; export default class Button extends Component { render() { return <button className={button.is-success} />; } };
CSS Modulesを使うとButton.css
をJSでimport button from './Button.css';
のようにimportして、className={button.is-success}
のようにその中のスタイルを参照できる。
しかしこれは、プリフィックスがJSコード側に付いただけで、BEMで.Button--success
のような名前を付けているのと変わらない。
composes
についても、結局はHTMLのclass属性に複数のICSSのセレクタ名が書かれる(マルチクラス)だけだ。
また機能の異なるコンポーネントから、(たまたま表現したい見た目が同じだが)デザインの意図の異なるスタイルをcomposes
すると、コンポーネント化が途端に崩れ、雑にCSSを書いているのと同じになる。
CSS Modulesは現状Reactとwebpackの使用が前提にあるし、CSSのコンポーネント設計が身についている人(がいるチーム)にとってはわざわざ導入すべきものではないように感じた。
また、CSS Modulesのcomposes
を使いこなせる人は、CSSプリプロセッサーのSassで言う@mixin
と@extend
も使いこなせると思う。
CSS Modulesは、「Reactを使って正しくコンポーネントにわけて設計でき、CSSの設計手法やカスケーディングやセレクタ詳細度の仕様をきちんと理解できていないけど、なんとなく破綻しにくいCSSを書きたい」人に向いていると思った。
あわせて読みたい
- CSS Modules - Welcome to the Future
- Interoperable CSS
- The End of Global CSS
- Against CSS in JS
- The Debate Around “Do We Even Need CSS Anymore?”
その他雑感
CSS ModulesチームはCSSの問題点をよく理解していて、本質的な問題解決に挑戦しているように見えた。 僕も「作れるから作ってみた」ではなく、このような考えでプロダクトを作っていきたいと思う。
PostCSSとは何か
PostCSSというnode.js製のツールがある。 PostCSSのGitHubでのStar数は4000を超え、海外のブログではPostCSSについての記事をよく目にするようになった。しかしまだ日本では盛り上がりを感じていないので、日本語のPostCSSの記事を書くことにした。
PostCSS
PostCSSとは、JavaScriptで書いたプラグインでCSSを変換するためのツールだ。 PostCSS自体は、CSSパーサーとそのASTを操作するためのAPIのみを提供していて、ユーザーはPostCSSのプラグインを書くことでCSSを変換することができる。 僕もPostCSSを使って、以前ブログにも書いたAtCSSというCSSプリプロセッサーや、postcss-style-guideというスタイルガイドをMarkdownから自動生成するためのプラグインなどを書いたことがある。
PostCSS製のツールとして、ベンダープレフィックスを自動で付与するAutoprefixerや、現在策定段階でブラウザが未実装のCSSの記法を、今のブラウザが解釈できるようにトランスパイルするcssnextが有名だ。 また、プラグインの一覧はREADMEか、PostCSS.partsというサイトで確認できる。
PostCSSの使い方
PostCSSを使う方法はいくつかあるが、ここではgulpを使ったコード例を示す。
gulp.task('postcss', function () { var postcss = require('gulp-postcss'); return gulp.src('src/**/*.css') .pipe( postcss([ require('postcss-mixins'), require('postcss-nested'), require('postcss-simple-vars'), require('cssnext'), require('cssnano'), ]) ) .pipe( gulp.dest('build/') ); });
上記のgulpタスクでは、
の5つのプラグインを使用している。
postcss-nested
は既存のプリプロセッサーにあるセレクタのネスト表記を可能にするプラグインで、postcss-simple-vars
とpostcss-mixins
もそれぞれ変数とミックスインを使用するためのプラグインだ。それらをcssnext
と使用し、結果をcssnano
でminifyしている。
結果として、以下のようなコードをコンパイルすることができるようになる。
@define-mixin icon $network, $color: blue { .icon.is-$(network) { color: $color; @mixin-content; &:hover { color: white; background: $color; } } } @mixin icon twitter { background: url(twt.png); } @mixin icon youtube, red { background: url(youtube.png); }
PostCSSプラグインの作り方
既存のPostCSSのプラグインを組み合わせて使用することもできるが、自分でプラグインを書いて独自のCSSの変換を定義することもできる。
以下はPostCSSのプラグインを書くときのテンプレートだ。
PostCSSのプラグインを書くときは、postcss.plugin()
関数をexportする。第一引数にプラグインの名前、第二引数にコールバックとして行いたい変換の処理を記述する。
var postcss = require('postcss'); module.exports = postcss.plugin('PLUGIN_NAME', function (opts) { opts = opts || {}; // Work with options here return function (css) { // Transform CSS AST here }; });
PostCSSのプラグインを書くためのYeomanジェネレーターもあるので、それを使ってもいい。 またガイドラインも用意されていて、プラグイン開発者はそれに従わないといけない。
また、PostCSSではASTを操作するための便利なAPIが用意されている。
どのような操作かはプロパティ名やメソッド名で察してほしいが、node.parent
、node.clone()
、root.eachRule()
、root.eachDecl()
、 rule.selector
などがある。
詳しい説明や他のAPIはAPIドキュメントがあるので、そちらを見てほしい。
最後に
It is time admit my mistakes. “Postprocessor” term was bad. PostCSS team stoped to use it.
https://t.co/vs2AiXGoJy
— PostCSS (@PostCSS) 2015, 7月 28
以前、このブログでもPostCSSはCSSポストプロセッサーをビルドするためのツールだと書いたが、PostCSSは「ポストプロセッサー」という言葉を使わないようにするらしい。少し前からREADMEからはpostprocessorという単語は消えていたが、package.json
のkeywords
からも削除された。
PostCSSのGitHubのissueやGitterでの会話で、PostCSSという名前を変えるべきだという意見もあったので近々名前が変わるかもしれない。
PostCSSの登場やReact.jsの流行により、CSSerにもまた変革が求められているのかもしれない。 しかし解決したい問題は変わってはいないと思うので、問題の本質を知る必要がある。 そのためにも、今自分が使っている技術やツールがどういうものなのかと問いなおすことが重要だ。
その他参考サイト
AtCSS: Annotation based CSS Processor
AtCSSという、プリプロセスに必要なメタデータをCSSファイルのコメントにアノテーションとして記述し、変換するツールを作った。 開発自体は約3ヶ月前から行っていたが、先日v1.0をリリースしたのでブログで紹介してみる。 ちなみに読み方は「アットシーエスエス」です。
Annotations based CSS Processing
以下がAtCSSのコードの例である。
CSSのコメント内に@
で始まるメタデータを仕込み、それを元に変換する。
.base-1 { /* * @base * @constant */ font-size: 12px; padding: 8px 16px; } .base-2 { /* * @base */ color: red; } /* @start constant */ .class { /* * @use .base-1 */ background-color: green; } /* @end constant */ .too-long-and-ugly-selector-name { /* * @use .base-2 */ margin: 10px; }
ここで使っているアノテーションを説明する。
まず@base
というのは、AtCSSで継承元のルールセットであることを示すためのもので、後に説明する@use
を使って継承する。
@constant
は、そのルールセットの同一セレクタによる上書きを禁止するもので、この場合だとどこかで.base-1
のセレクタ名のルールセットを作るとエラーとなる。
これは以前作ったYACPにもある機能だ。
@base
で継承元のルールセットを定義するときに一緒に使うと便利っぽい。
@start constant
と@end constant
は、この間にかかれたコード内(仮にconstantブロックと呼ぼう)でのカスケーディングを禁止するものだ。
上記のコードの場合、.class
がそれにあたる。
このconstantブロック内で、.class {}
や.nested .class {}
、.children > .class {}
と言ったルールセットを定義し、.class
が上書き(カスケーディング)するとエラーとなる。
最後に@use
だが、これは@base
で定義した継承元のルールセットのプロパティ宣言を継承(再利用)するためのものだ。
この例では、.class
が.base-1
を、.too-long-and-ugly-selector-name
が.base-2
を@use
している。
しかし、AtCSSの継承はSass等の@extend
とは少し違う。
以下は、上記のコードを変換した結果だ。
.class { /* * @base * @constant */ font-size: 12px; padding: 8px 16px; } .class { /* * @use .base-1 */ background-color: green; } .too-long-and-ugly-selector-name { /* * @use .base-2 */ margin: 10px; color: red; }
まず、.base-1
を@use
した.class
だが、これはSassの@extend
と同様に継承先のルールセットのセレクタ名を継承元のルールセットのセレクタに付与するかたちでプロパティを継承していることがわかる。
次に.base-2
を@use
した.too-long-and-ugly-selector-name
だが、こちらは継承元である.base-2
内のプロパティ宣言が継承先の.too-long-and-ugly-selector-name
内に展開されている。
このように、AtCSSでは他のルールセットを継承した際の振る舞いが2通りあり、変換後のファイルサイズが小さい方の選択を自動でおこなっている。 具体的には、
- 継承先のセレクタ名の文字数が継承元の全プロパティ宣言の文字数よりも多い
- メディアクエリ内で
@use
している
この2つの状況では後者のプロパティ宣言を展開する継承を選択肢、それ以外は前者の@extend
と同じ振る舞いの継承となっている。
後者のような継承を、既存のプリプロセッサーでも@mixin
を利用することで可能だが、インターフェースを統一したい、開発者が意識せずパフォーマンスの良い選択をさせたい、といった思いから実装した。
なぜアノテーションなのか
わざわざコメントにアノテーションを記述させるかたちにしたのには理由がある。それは、開発時の変換前のコードもブラウザが解釈できるようにしたかったからだ。
また、少し抽象的な話になるが、HTMLとCSSにおいての命名の際、HTML側からは構造やそのコンテンツの性質に対して名前を付け、CSS側からは見た目やデザインの意図に対して名前を付けるのが良いと思っている。 見た目に対して名前を付けている例として、Utility Casses(汎用クラス)と呼ばれるものがある。 簡単に説明すると、以下のような、単一プロパティで定義されたルールセットのことだ。
.font-sm { font-size: 10px; } .bg-blue { background-color: #0089ff; } .mgr-10 { margin-right: 10px; }
汎用クラスを使用することで、素早いデザインの微修正が可能となり、インブラウザデザインや開発時のスピードアップを見込める。 しかし、多用するとWebサイトのメンテナンス性が低下する。
この問題を解決するための手段として、Sassの@extend
を使って汎用クラスをまとめて意味のある名前にするのが良いと思っている。
%font-sm { font-size: 10px; } %bg-blue { background-color: #0089ff; } %mgr-10 { margin-right: 10px; } .class { @extend %font-sm; @extend %bg-blue; @extend %mgr-10; }
しかしこのコードには、placeholder(%
)セレクタや@extend
といった、CSSを拡張した、ブラウザが解釈できない構文が含まれている。
そのため、開発時に%font-sm
をHTMLのclass属性値にすることはできないし、表示確認のごとにコンパイルする必要がある。
このような理由から、全ての変換のためのメタデータをコメントに記述することで、開発時と本番時で同じCSSで異なる名前を付けるのはどうか、という考えでAtCSSは作られた。
参考サイト
汎用クラスを使ったWebデザイン
CSSにおいて、以下のような単一プロパティで定義されたルールセットをUtility Classes、日本語で汎用クラスとかヘルパークラスだとか呼ばれる。
.text-center { text-align: center; } .font-sm { font-size: 10px; } .bg-blue { background-color: #0089ff; } .mgr-10 { margin-right: 10px; }
汎用クラスを利用してスタイルを当てていくと、HTMLのclass属性値を変更するだけで素早くデザインの変更が可能だ。 Twitter Bootstrap等のCSSフレームワークにも汎用クラスはいくつか定義されていたり、BASSCSSという汎用クラスを大量に定義してあるフレームワーク(BASSCSSではlow-level CSS toolkitと呼んでいる)も存在する。
また、スタートアップや少人数のチームでは、デザイナー(最終的なデザインの決定権を持つ人)がいないことも少なくないと思う。 エンジニアだけで何かサービスを作ることも多いだろう。 僕自身このような環境で開発をしてきて、デザインについてはHTMLとCSSを書きながら、インブラウザデザインをして、他の人と相談しながら決めていく。 このような場合では、WYSWYG的にデザインできる汎用クラスの利用は必須なんじゃないかとも思っている。
しかし、汎用クラスを使った(使いっぱなしにした)Webデザインは、メンテナンス性を著しく低下させると考えている。 というのも、汎用クラスの名前にはそのようなデザインにした意図が含まれないからだ。
例えば、上記の.mgr-10
には、「右側マージンを10pxとる」以上の意味がない。
これでは後から違う人がメンテナンスする際になぜこのスタイルが当てられているのかがわからない。
でも汎用クラスはとても便利だし、僕もこれからも使っていくだろう。
複数人で開発するときは、汎用クラスをプリプロセッサーの@extend
とかでまとめて、意味のある名前にするのがいいと思っている。
また1人で管理するような個人サイトでは、ガンガン使っていいと思う。
Webサイトは、 情報システムであってアートではない(もちろんアートとして美しいサイトもある)。 マークアップはモックアップではないし、CSSは構造化文書の見た目を制御するスタイルシート言語だ。 だからWebデザインは「作ったら終わり」ではなく、保守の対象であり、リファクタリングの対象だと思う。
See also:
OOCSSとEDJO、もしくはHTMLとCSSにおける命名
OOCSSの欠点とEvery Declaration Just Onceのもたらすもの
hail2uさんのこの記事を読んで、EDJO (Every Declaration Just Once)というCSSの記述アプローチを知ったので、僕なりに考えたことをまとめてみる。
OOCSSとEDJO
OOCSSとEDJOの違いは、 名前を付ける向きだと思う。OOCSSでは、CSSからHTMLに、つまりCSSで定義したルールセットの名前をHTMLで使用するということ。そしてEDJOでは、HTMLからCSSに、HTMLの構造に名前が付き、その名前に当てるスタイルを定義するという流れだ。
デザインの意図やコンポーネントの見た目に対して名前を付けるのがOOCSS的アプローチで、文書構造や文書の意味に対して名前を付けるのがEDJO的アプローチなのかなと思う。
OOCSS的アプローチを取ると、.btn-large
や.text-center
といった名前になる。そしてHTML側で複数のクラスを組み合わせることでスタイルを当てていく。
そして、EDJO的アプローチを取ると、HTMLのclass属性値等に付けられたmenu
やbtn-next
といった名前に対してプロパティを割り振っていく(プロパティに対して名前をセレクタとして追加していく)。
HTMLとCSSの設計
HTMLとCSSの設計が破綻しやすい原因は、HTMLとCSS間の命名の主従関係が明確でないからではないか。
例えば、あるプログラミング言語とそのライブラリの間では、モジュールの命名の主従関係ははっきりしている。 ライブラリが付けた名前をアプリケーションコードが使用するといった具合に。
しかし、HTMLとCSSでは上記で述べたように、HTML側から名前を付ける場合とCSS側からの場合がある。 このように、双方向から名前を付けることができるから、HTMLとCSSは破綻しやすいのではないか。 そして、このHTMLとCSS間の命名を一方向にするアプローチが、OOCSSとEDJOだと思う。
しかし、これらの一方向のアプローチをとってもまだ完全ではない。 CSSには複数のプロパティを組み合わせることで意味を持つものがあるので、(完全な)EDJO的アプローチが良いとは思えない。 OOCSSの欠点はhail2uさんのブログにも書いているから割愛するが、僕自身もいわゆるマルチクラス設計について思うことはある。
ではどうすればいいのか。
僕は、HTML側からもCSS側からも命名をし、HTMLとCSSの間に そのバインディングをおこなう世界 があればいいと思う。
そして、バインディングをおこなう世界の例として、CSSプリプロセッサーのextend
が使えるのではないか。
%btn { // } %btn-large { // } %btn-blue { // } .btn-next { @extend %btn; @extend %btn-large; @extend %btn-blue; }
このように、CSS側からコンポーネントの見た目から付けられた名前(%btn
, %btn-large
, %btn-blue
)とHTML側から文書の意味から付けられた名前(.btn-next
)を@extend
を使ってバインディングできる。
ここに書いてあることは僕の妄想なので、正しいかどうかはわからない。 誰かに意見をもらいたいし、議論したい。
本当のCSS設計、もしくはWebページのデザインプロセス
CSSの設計 = セレクタ名をどう付けるか、って思っている人が多いので、年も明けたしここらで一度「CSSを設計する」とはどういうことか、考えていることをまとめてみる。
セレクタ名をどう付けるか
CSSのルールセットは現状全てグローバル定義なので、上手いセレクタ名を付けることで衝突を回避する必要がある。
そのための手法として、SMACSSでは.is-**
とか.l-**
みたいにプリフィックスを付けたり、BEMみたいに.Block__Element--Modifier
のような独特な記法でネームスペースを設けたりする。
CSSは、HTMLのどの要素にどんなスタイルを当てるか、という風に宣言的に記述する言語だ。 この特性ゆえに開発者は、このスタイルをどこの要素に適応させるべきかと考え、セレクタ名を付け、HTMLの属性値に書く。
しかし、この「セレクタ名をどう付けるか」と考えることが、Webページのデザインをする上で間違った思考なのではないか。
デザインプロセス
モノを作るときに最も大切なのは「コンセプト」だと大学の授業で聞いた。 デザインをしていく思考は、
- まずコンセプト、こういうモノが作りたいという欲求があって
- それに合わせてどうデザインするかという意図が生まれ
- 実装に落とし込んでいく
という流れだと思う。 このように、デザインはトップダウンに行われるべきだ。
Webページの場合
上記の3つをWebページのデザインに当てはめると、
という流れになる。
ここで大切なことは2つある。 ひとつは、なぜこのようなデザインになったのかをCSS記述者が知らないとセレクタ名が自然と出てこないということ。 デザインの意図を知らずに、見た目だけで再利用可能なルールセットを定義しようとするから、「ここの色を変えたらあそこの色まで変わってしまった」という事態が起こる。 同じ赤色でも、強調を表す場合とエラーを示している場合があったりする。 これらのスタイル(見た目)が同じだからと言って、同じ名前を付けてしまうのはデザインの意図を理解していないからだ。
そしてもうひとつが、セレクタ名がデザインの意図から自然と決まるので、それに合わせてプロパティを記述するということ。 これは今開発者が悩んでいる「セレクタ名をどう付けるか」という考えとは逆である。 そもそも、セレクタ名をどう付けるか考えることは、ボトムアップ的な考えで、デザインをするトップダウンなプロセスとは異なる。
デザインの意図を正しく理解できれば、意味を持ったセレクタ名が自然と決まるものだ。
以上、理想論でした!
実際の現場はこう上手くはいかないと思う。自分ひとりで作っているサイトでもない限り、CSSを書く人は渡されたpsファイルを見てどう作ろうかと考えるだろう。 また、デザインの変更は絶えず行われるので、修正に耐える設計、というものも考えないといけない。
上記の理想のプロセスをおこなうために、僕はCSSプリプロセッサーの他のルールセットを継承する機能(Sassでいう@extend
)が使えると思っていて、そのために独自のプリプロセッサーを作ったりもしている。
開発時には素早いデザインの修正に耐えれるような、細かく、見た目の性質を表したルールセットを定義し、本番時にはそれらをまとめた(継承した)意味を持つ単位のルールセットを定義する。
このルールセットの分割の粒度がCSS設計の本質だと思っている。