前回の記事でも紹介したように、Topcoat は CSS だけで記述された UI フレームワークです。CSS のみのため、任意の JavaScript フレームワークと安心して組み合わせられますし、VM を必要としない分、モバイルデバイスでのパフォーマンスに有利な面もありそうです。
Topcoat はもともと PhoneGap 用に開発が始められたものなので、PhoneGap アプリをつくる機会があれば、是非試してみてください。
(PhoneGap アプリ開発を最初から勉強したい方には、こちらの連載がだんだんと充実してきてますのでおすすめです。企画レベルでは既に 16 話までできているという話なので、かなり充実したシリーズになりそうです)
ということで、CSS だけのコンポーネントがどんなつくりになっているのか、ちょっと興味を持った人のため、今回は Topcoat のアーキテクチャの紹介です。
ベースとスキン
Topcoat のコンポーネントは、「ベース」 と 「スキン」 の 2 種類に分けて定義されます。これは、機能と見た目の分離を実現するためで、それぞれ、ベースコンポーネントは使われ方を、スキンコンポーネントは見た目の方を、定義する役割を受け持ちます。 (ここだけならよく聞くお話ですね)
ベースとスキンの分離は "変数" を用いることで実現されます。実際にベースコンポーネントの中身を覗いてみると、殆どは以下のような変数の羅列です。
padding: var-padding; font-size: var-font-size; color: var-color; text-shadow: var-text-shadow;
一方、スキン側には、これらの変数の実際の値が記述されます。例えば、
var-font-size = 12px var-padding = 0 0.563rem var-color = #c6c8c8 var-text-shadow = 0 -1px rgba(0, 0, 0, 0.69)
のような感じです。
一見してわかるように、どちらも普通の CSS ではなく、構文から Stylus を利用していることが見て取れます。ベースの .styl ファイルと スキンの.styl ファイルを組み合わせて変数を置き換えれば CSS が生成されるという仕組みです。
ベースコンポーネントやスキンコンポーネントの内部構造も、それぞれ Stylus の機能を活用したものになっています。CSS プリプロセッサー大活躍です。
ベースコンポーネントの拡張とリセット
ほとんどのベースコンポーネントは基底クラスを持っています。例えば、Topcoat のボタンの場合は、.button クラスを継承する形で .topcoat-button クラスが定義されています。継承に使われるキーワードは @extend です。
.topcoat-button { @extend .button padding: var-padding; font-size: var-font-size; ... border-radius: var-border-radius; }
基底クラスは、スタイルのリセットが主要な役割です。リセットに必要なコードは utils.styl に定義されていて、それをコンポーネントの要件に応じて呼び出すのが基本的な形となっています。
下は .button の定義からの抜粋です。他のファイルを読み込むキーワードは @import です。
@import utils .button { inline-block() reset-box-model() ... text-decoration: none; }
ちなみに、utils.styl の中はこんな感じです。一般的に利用される種々の関数が定義されています。下のコードには波括弧がないのでわかりにくいかもですが、 Stylus の文法ではこれでよいようです。
reset-cursor() cursor: default; user-select: none; reset-box-model() box-sizing: border-box; background-clip: padding-box; ...
ところで、ベースコンポーネントには、状態が変化した時の差分も記述されます。ボタンであれば、以下のような感じです。
.topcoat-button { @extend .button ... } .topcoat-button:hover { background-color var-background-color--hover } .topcoat-button:active { background-color: var-background-color--down; box-shadow: var-box-shadow--down; } .topcoat-button:focus { border var-border--focus box-shadow var-box-shadow--focus outline 0 } .topcoat-button:disabled { @extend .button--disabled }
テーマ
それでは、スキンの方はというと、こちらはテーマという単位で扱われます。ベースコンポーネントとペアでファイルが作成される訳では無いのがポイントです。
標準で提供されるテーマには、デスクトップ用とモバイルデバイス用があります。両者の違いは、フォントの大きさや余白の値などです。モバイル用のテーマはより小さな画面向けに調整されています。
そして、デスクトップとモバイルデバイスのテーマそれぞれに、暗めの色と明るめの色を使った 2 種類のバリエーションがあります。ということで、計 4 種類のテーマが標準で提供されています。
例えば、デスクトップ用の暗い色のテーマを定義したファイルを開くと、中身は以下のようになっています。
@import variables-shared @import variables-desktop @import variables-dark
どの行も、先頭が @import であることから、他のファイルを 3 つ読み込みその内容と置き換えられている、ということがわかります。
明るい色のモバイルデバイス用のテーマの場合は以下の通りです。
@import variables-shared @import variables-mobile @import variables-light
両者が読み込むファイル名を比べてみると、1 行目はおんなじで、ここで、全てのテーマに共通するスキン情報だけをまとめたファイルが読み込まれます。2 行目では、画面サイズに応じて変わる記述が、3 行目では、色関連の記述が読み込まれます。
このように、大きさに依存する定義と、色使いに依存する定義が別のファイルに分離されているため、"デスクトップ & 明色"、"モバイル & 暗色"、といったスタイルも、組み合わせを変えるだけで実現できています。
これなら、自分の好きな色味だけ定義することも、比較的安心して始められそうですね。
具体的なテーマの定義
とすると、テーマを構成する個々のファイルの中身はどうなっているのかが気になるところです。ということで、まずは、色関連の情報の定義です。
例として、暗めのテーマ色が定義されている variables-dark.styl ファイルの中身を見てみると、
var-background-color = #595b5b var-background--body = #4B4D4E var-border = 1px solid #303233 var-box-shadow = inset 0 1px #727373 ...
という形式で、色、ボーダー、影などの値が記述されています。さらにファイルの下の方には、
/* List */ var-background-color--list = rgb(68, 72, 73) var-border-top--list-container = 1px solid rgb(47, 50, 52) var-border-top--list = 1px solid rgb(47, 50, 52) var-border-bottom--list = 1px solid rgb(94, 96, 97) ... /* Checkbox */ ...
と、コンポーネント固有の情報も列挙されています。つまり、全てのベースコンポーネントに対する色関連の情報が、全て 1 つのファイル内に記述されている訳です。
今度は、大きさ関連の定義の例として variables-mobile.styl ファイルの中身を見てみると、
var-font-size = 16px var-font-size--large = 1.3rem var-font-weight = 200 ... /* List */ var-padding--list-header = 4px 20px var-padding--list-item = 1.25rem var-font-size--list-header = .9em ... /* Checkbox */ ...
のように、各コンポーネントに固有の変数も含め、大きさや余白関連の定義が、こちらも 1 つのファイルに記述されています。
コンポーネントが増えると、ベースコンポーネントの方は素直にファイルが追加されますが、スキンコンポーネントの方は、テーマファイル内の記述が増える、というつくりです。
以上、まとめると、
- ベースコンポーネント情報は操作機能単位にファイルが分けられている
- スキンコンポーネント情報は属性機能単位でファイルが分けられている
- ベースコンポートは任意のテーマと組み合わせることができる
というお話でした。
最後にもう一点。Topcoat の定義には配置情報が含まれません。そのため、
- 任意のレイアウト用フレームワークと組み合わせて使用できる
も特徴の 1 つとされています。
コメントする