この記事はドワンゴ AdventCalendar 2017の17日目の記事です。
dwangoアドベントカレンダー17日目を担当させていただきます @ln-north です。デザイナーとして2016年度新卒として入社し、もうすぐ2年になります。
エンジニアさんで埋められるカレンダーの中、ひっそりとデザイナーも参加させていただきます、どうぞお手柔らかに…。
はじめに
ここ何年かのWebフロントエンド界隈の動きは非常に大きくそして速く、デザイナーから見ても様々なパラダイムシフトが起こっています。scssやwebpackからHTML5やCSS3まで…本当に大変ですよね。
特に最近はReactやVueなど、 コンポーネント指向 のWebシステム開発が発展を遂げています。Web Componentsなども含め、流れを見てるとおそらくWebはこのコンポーネント指向に向かい、しばらく進んでいくのだろうという感じがします。
私もここ1年半ぐらいニコニコ生放送でReactを使った開発を行って来ましたが、デザイナーの参画の仕方も随分変わってくるだろうなぁと感じました。
今回は、私が感じた、デザイナーがどうコンポーネント指向のフロントエンド開発環境と向き合って行く・参画していこうかか、みなさん困っているであろうデザイナーとエンジニアの境界領域のもろもろを、振り返りも踏まえて書いてみることにします。
参考
-
今回のReact開発の背景は、一緒に仕事させていただいてる @kondei さんの ニコニコ生放送の watch ページを MobX で作り直している話 の2~15ページを参照してください
-
これまた一緒に仕事させていただいている、 @misuken さんの、第2のドワンゴ Advent Calendar 2017 で22日目の記事 React + TypeScript + CSS Modules によるコンポーネント指向フロントエンド開発の流れと知見も参照することを強くおすすめします。 (@misuken さんとは隣の席で、かなり頻繁にViewComponentとデザインの境界領域について議論して進めており、設計は彼の影響を強く受けています)
コンポーネントの分割粒度 Atomic Design について
今回の開発ではAtomic Designを用いました。この考え方自体はかなりプリミティブで直感的だったので、一応勉強会が開かれましたが、初見でもデザイナーが理解しやすく取り入れやすかったと感じます。
Atomic Designでは、厳密に各段階に何を入れるかを定義していないので、多少混乱が起こるのかなと思っていましたが、今のところはAtomにはHTMLの原始的な要素コンポーネント + その拡張コンポーネントを入れる、OrganismにはTemplateに入れるためにある程度完成された状態のコンポーネントを入れることは明白だったので、Moleculeにはその途中を入れていく、という考えであまり混乱なく成り立っているようです。
結果としてmolecule層が厚くなり、その部分の整理に一工夫は必要でした。(また第2のドワンゴ Advent Calendarでmisukenさんが詳しく書くかもしれません)
スタイル都合のコンポーネント発生を防ぎたい
おそらくコンポーネント指向開発において一番困ることの一つに、同じ機能を持っているのにスタイルや表示順が違う場合にコンポーネントが発生してしまうことかと思います。
同様の悩みは任天堂さんでも起こっているようでして、
Nintendo Switchの中ではReactが動いてる!Nintendo eShop開発秘話を聞いてきた
任天堂さんでは、すべてのシーケンスを一つ一つ確認するようにしていたようですが、今回の案件ではデザインと開発が同時並行で進むことと、PCWebはSwitchの画面よりも大きく表示状態もとても多いため、困難が予想されました。
これについては以下のような対応を行って頂きました。
使用するスタイルファイルを対象コンポーネント先で差し替えできるようにする
CommentPanel(Organism)内で使いたいForm(Molecule)やButton(Atom)と、RegisterPanel(O)内で使いたいForm(M)やButton(A)が違うような場合に、CommentPanel/RegisterPanelコンポーネントでForm(M)やButton(A)のスタイルファイルを差し替えできるような状態にしてもらいました。概略を下の図に示します。
- Atoms
- Buton
- button.scss
- Molecules
- Form
- Form.scss
- Organism
- CommentPanel
- comment_panel.scss
- _form.scss
- _button.scss
- RegisterPanel
- register_panel.scss
- _form.scss
- _button.scss
これによって、スタイルがコンポーネントの構造を見ることがなくなり、コンポーネントが機能として独立でき、スタイルを非常にうまく分離することができるようになりました。
さらには、スタイルファイルのスコープが今回の場合だとCommentPanel, RegisterForm内で収まるので、保守性も大きく向上したと思います。
ただ、buttonの機能として収めたいもの(例えば、reset.cssのようなスタイルを書きたいとか、enabled/disabledのときのcursorの挙動は同じにしたいと言うようなもの)は引き継ぎたいので、以下のようにcss_modulesのcomposes機能でimportします
.form {
composes: form-base from "../../Molecules/Form/form.scss";
background-color: gray;
font-size: 12px;
}
.text-box {
composes: text-box-base from "../../Molecules/Form/form.scss";
width: 100%;
height: 32px;
border: 1px solid yellow;
}
.button {
composes: text-box-base from "../../Atom/Button/Buton.scss";
background-color: blue;
color: white;
text-align: center;
}
このように書いてみると、差分が非常に明白で、CommentPanelスコープがどういうものなのかをはっきり示せていると感じることが出来ます。
スタイルにおけるコンポーネントの内、外の分割
また、さらなる再利用性の向上の為、スタイルファイルにおけるコンポーネントの内、外の分割もcss moduelsのcomposes機能で実現しました。
- Organism
- CommentPanel
- CommentPanel.tsx
- comment_panel.scss
- _form.scss
- _button.scss
例えば、先程のこの箇所では、_form.scss
に flex-grow: 1
のような、 comment_panel.scss
で display: flex
が使われる前提(親を知っている)ようなスタイルを書くと、あとで _form.scss
を再利用できなくなります。
そのために、まず以下のようにtsxで一番上だけは comment_panel.scss
の .form
を当てるようにし、内側には _form.scss
を当てるようにします。(以下のコードは雰囲気だけ伝えるために書いたので、実際は動かないかもしれません…)
const CommentPanelClassNames = require('./comment_panel.scss');
const FormClassNames = require('./_form.scss');
const ButonClassNames = require('./_button.scss');
<CommentPanel className={CommentPanelClassNames.CommentPanel}>
<Form className={CommentPanelClassNames.PostForm} classNames={FormClassNames}>
<Button className={CommentPanelClassNames.PostButton} classNames={ButtonClassName}>
</CommentPanel>
内のスタイルは、comment_panelに依存するサイズなどのスタイルは記述しません。(上に書いたスタイルもそのようになっていると思います。)
その上で、comment_panelのほうでそれらの配置情報などの、外がどう使うかの情報を記述します。
.comment-panel {
display: flex;
flex-flow: row nowrap;
}
.post-form {
composes: form from "./_form.scss";
flex-grow: 1;
}
.post-button {
composes: button from "./_button.scss";
width: 120px;
flex-shrink: 0;
}
こうすると、またさらに差分が明白になり、comment_panelがどういうものなのかをうまく表現できている上、_formを別の場所でそのままコピーして使う、form.scssに戻す、というようなことが簡単にできるようになり、再利用性が向上します
他のプロダクトではどうなってるだろうか...
ちなみに、AbemaTVさんもReact + Atomic Designでの開発をしていることで有名ですが、吐き出されているHTMLを見てみると、コンポーネントを必ずdivか何かでwrapしている法則性があるようで、ここでレイアウトのスタイル定義をしているようで、先日のmizchiさんの記事: React とGUI 設計論、あるいは新世代のホームページビルダー のようにレイアウト要素として分離している, もしくはコーディングルールや意識のようなものがあるのではないかなと予想しています。(違ったら大変申し訳ありません...)
このレイアウト解決手法も私は一つの手段だと考えており、CSSをあんまり頑張って考えなくて良くなるので、楽になる部分もあり、一つの良い手段だと感じています。
しかしHTMLにスタイル依存を入れることを極力避けたいので、今のところはできるだけやらないようにしています。ここはどちらかと言うと、CSSへの挑戦状のようなものなのかもしれません…。
スタイルが末端に集まってしまう問題への対策
上記は非常に便利な仕組みなんですが、便利すぎてたくさん使ってしまい、結果としてスタイルがすべてMoleculesやOrganismの末端に集まってしまい、副作用として、同じスタイルをいろんな箇所でコピペして使う、という状況がおきてしまいました。
現在この解決策として、真に共通なスタイルはその大本にスタイルを集め、それをcomposesして使っていく、という手法を実験的に進行中です。
- Atoms
- Buton
- button.scss
- button-hoge-type.scss (new)
- button-fuga-type.scss (new)
- Molecules
- Form
- form.scss
- form-hoge-type.scss (new)
- Organism
- CommentPanel
- comment_panel.scss
- _form.scss <= hoge-typeをcomposes
- _button.scss <= hoge-typeをcomposes
- RegisterPanel
- register_panel.scss
- _form.scss <= hoge-typeをcomposes
- _button.scss <= fuga-typeをcomposes
これもひとえに前述の2つの整理を行えたからこそスムーズに進んだ恩恵であります。
デザイナーとしてもSketchなどで見た目のコンポーネントを考えてはいるのですが、それがコードに表現できることはあまりなかったのですが、今回は非常にその意図を表現できるようになっています。
composesの仕組み自体は単純なので、デザイナーですべてスタイルの構造定義を行うことが出来るところも良いポイントです。
HTMLがフロントエンドエンジニアの持ち物になっていく
結果として、コンポーネントはUI Kitというよりも、機能集合という状態になったため、コンポーネントの分割粒度の設計 => 機能設計となります。そのため、HTMLも基本的にはエンジニアさんが設計していくことになります。(misukenさんとも、HTMLはデータフォーマット表現なわけだから、もうエンジニアの領分と考えても良いのではないかという話をしました。)
以下のようなAtomicDesignを使ったReactなどのコンポーネント指向開発でも、基本的にはフロントエンドエンジニアがHTMLを構成する、という状態のようです。
Atomic Design powered by React @ AbemaTV
Nintendo Switchの中ではReactが動いてる!Nintendo eShop開発秘話を聞いてきた
他の粒度分割でも、おそらく似たような状態になるかと思います。今までSmartyやjade, ejsなどで比較的デザイナーもHTMLを書く状態があったと思うのですが、すこし遠くなった印象を持っています。ただ、今振り返ると、ほとんど不安や心配はありません。しかし、ここが一番デザイナーとエンジニアの境界領域になっています。
このような境界領域は以下のようなコミュニケーションでうまく仕事を進めています。
HTMLの境界領域でのコミュニケーションについて
まず、機能要件の段階でプランナー・エンジニア・デザイナーで合意を取るようにします。重要なのは機能イメージが合致していることです。
次に、主にOrganism程度の粒度で、簡単な表示機能がわかるレベルの見た目、説明、表示状態のリストなどを共有します。
その後、エンジニアさんのHTML構造設計を行ってもらい、デザイナーでレビューをし、認識が合致してからコンポーネントを作ってもらうというようにしてます。レビューの際は以下のような内容に注意するようにしました。
- class名が当たってるか
- 必要な状態が揃っているか
- class名の名前
- 名前の意味を確認すると、作ってるもののゴールが一致しているか大体わかります
- まずレイアウトがこのHTMLで再現できるかどうか
- 普段からある程度これをHTML/CSSで組むとどうなるかを考えておくと良いです
- 基本的にはハックしない程度のCSSで限界まで吸収することを念頭に置きます
CSS3になってからというもの、非常にCSSが柔軟になり、ほとんどwrapperがなくても当てられるようになりました。おまけでまたいくつか紹介します。
スタイルの構造設計の中で分かってきたUI設計
いろいろ書いてきましたが、スタイルの構造をデザイナー側で持つことが出来たので、デザイン側の設計もよく考えるべき状態になりました。
これはまだ思案中のものも多く含まれますが、書いていきたいと思います。
コンポーネントに分けることにより、より一つ一つのUIに着目するようになった
コンポーネント指向開発におけるデザイナーへの恩恵の一つは、一つ一つのUIにより着目する機会が増えたことです。
例えば、UIに関するデータを取る際、何を取ればいいかわからないというケースや、仮説がでかくなりすぎるというケースがあり、結果としてとりあえずヒートマップ取ってみようという流れがあったりしたのですが、UI一つ一つの機能や差分に着目できるので、この機能の核はどこか(もっと言えばKPIはどこか)、差分を書いたが、これは何をもたらすものなのか、をとても良く考えるようになりました。
スタイルパターンについて
以下のようなものは、コンポーネントにほとんど非依存のスタイルということが分かってきました。
- カラースキーム
- in light, in darkで振り分けたり、面積で振り分けたりするなどの構造も持たせたい
- フォントファミリー
- インタラクション領域の最小サイズ
そして、以下の様なものは、コンポーネントに非依存にすることが出来て、かつ揃えることでより審美性が高まりそうだと考えているものです。
- フォントサイズのパターン
- バーティカルリズムのパターン
- ミクロスペースのパターン
- バーティカルリズムとグリッドを意識したコンポーネントのサイズパターン
このようなものはスタイルパターンとして、コンポーネントとは別の領域で扱うべきだと考えています(現在一部が進行中)。そしてこの定義はUIの使いやすさとグラフィックとして与える印象に関わります。
UIのアプリケーション依存度について
アプリケーション内には様々なUIがありますが、一つはアプリケーションにどれ位依存しているか、どれだけアプリケーションのドメインと切り離して考えることができるかの尺度で評価することができそうということが分かってきました。
- アプリケーション依存度が低い
- チェックボックス・ラジオボタン
- テキストボックス
- ボタン
- などなど…
- アプリケーション依存度が低くもないが高くもない
- 検索フォーム
- 設定パネル
- 番組リスト
- などなど…
- アプリケーション依存度が高い
- プレーヤー
- コメント投稿フォーム
- コメントパネル
- などなど…
そして、これらは
- アプリケーション依存度が低い
- コンポーネント上の特徴: ほとんどスタイルパターンの組み合わせで成立する
- UI上の特徴: ユーザーに一度使い方を学習させる必要が殆ど無い
- アプリケーション依存度が低くもないが高くもない
- コンポーネント上の特徴: 「スタイルが末端に集まってしまう問題への対策」の項で話したstyle-typeができる
- UI上の特徴: ユーザーに使い方を学習させる必要が、アプリケーション依存度が高いUIに比べると少ない
- アプリケーション依存度が高い
- コンポーネント上の特徴: organismなど粒度が大きいコンポーネントになる、末端にスタイルが当たっていく、style-typeの作りようがない
- UI上の特徴: ユーザーに一度使い方を学習してもらう必要がある物が多い
という特徴で、階層がわりと一致すると考えています。そして、アプリケーション依存度の高いものをたくさん作ると、アプリケーションの操作感が落ちるのではないかという仮設を立てています。
例えば、YouTubeやGoogleMapなどのアプリケーションを見てみると、なるべくアプリケーション依存度の低いもので作られていることがわかります。
この特性を利用し、UI設計において、
- なるべくアプリケーション依存度の低いものを利用する
- アプリケーション依存度の高いものを利用する際はそのメリットによるパフォーマンスとの釣り合いが取れるかどうかを慎重に検討する
といった作戦を取ることができます。
ちなみに、Googleがマテリアルデザインのコンポーネント集を出して利用を推奨しているのは、学習の必要なUI群を広く利用してもらい、アプリケーション依存度を下げようという試みだと解釈しています。
これらコンポーネント化によってわかるアプリケーション依存度の概念とスタイルパターンの組み合わせで統制されたUIを作っていければ、より良いUIを提供できるのではないかと考えています。
そのためには、デザイナーにも設計やそのものを解釈する力、パターンを見抜く能力がより必要になってくるのではないかと考えます。(もともと必要だろと言われればそれまでなんですが、より視力よく認識したということで)
おわりに
長々と書いてきてしまったのですが、コンポーネント指向の開発を進めていく中で、デザイナーが学ぶことは非常に多く、すすめる際は一つ一つ着実にデザイナーの理解度を見たほうが良いかもしれないと思いました。
理解したあと、整理されたあとのメリットはデザイナー、エンジニアともに非常に大きいので、怖がらずに積極的に取り組んで良いものだと感じました。
ここまでやるならスタイルもエンジニアさんが書けば良いのでは?という声もありそうですが、スタイルの構造や、とくにデザインカンプで表現しにくいスタイルの「振る舞い」についてはデザイナーが持ったほうが、スムーズなケースが多いように感じています。今のCSSはその意図を非常によく表現できるように進化していますし。(ちゃんとデザイナーはこのあたりも学ぶべきだと思っています)
エンジニアがデザインを理解するという流れが起こっているようですが、このような境界領域はデザイナー側からも積極的に理解していくと良いこともあると感じています。
領域の境界は必ずあるので、なくても良い境界を減らしていくこと、無くならないであろう境界はコミュニケーションを怠らないことが大事だと感じています。
今年、公私でCSSを書いた行数を数えたら6万行を超えていたので、来年はなるべく少なくなるなるような効率化が出来たらなぁと思います。
いろいろ大変な状況もありますが、これからも効率的にできるところをどんどん広げていき、できた時間で精一杯ユーザーの皆様が納得していただけるUIを作り、楽しいと思っていただけるサービスを作ることを目指していきますので、これからもどうぞよろしくお願いします。
おまけ
css moduleのcomposesによるスタイル的な意味の付加
css modulesのcomposes機能はとても便利で、scssのmixinやextendなどのようにstyleをコピーするだけではなく、class名を付加してくれるというところが非常に便利なポイントです。
<div class="OperateComponent">
<Button className="CreateButton" />
<Button className="DeletButton" />
<Button className="OptionButton" />
</div>
このボタンが横並びで等間隔の場合、人によっては <ul>
, <li>
を使いたくなるかもしれません。しかし、以下のようにしてcomposesを使うと、そのclass名付加を活かして li
をスタイルだけ表現することが出来ます。これはとても便利な技法なので積極的に使うべきだと感じました。
.operator-component {
display: inline-flex;
flex-flow: row wrap;
}
// これが共通スタイル
.local-button-style {
width: 200px;
height: 40px;
text-align: center;
// composesしているのでclass名は各ボタンに紐付いているため、
// liなどのタグはないが、「隣接」マージンを与えられる
& + & {
margin-left: 8px;
}
}
.create-button {
composes: local-button-style;
}
.delete-button {
composes: local-button-style;
}
.option-button {
composes: local-button-style;
}
wrapを使わない背景色の引き伸ばし
背景がブラウザ100%で、その中身のwidthが固定値みたいな場合、必ずwrapとかinnerとかすると思うんですが、コンポーネントだとあまりレイアウト都合のdivを入れたくないので避けたいです。そこで、ややハック的ですが幾つかの方法で背景色を左右に引き伸ばすことが出来る方法を編み出したので紹介します。(それぞれいいところ悪いところあります)
before, afterで突っ張り棒を作る
@mixin expand-side-background($width) {
display: flex;
flex-flow: row nowrap;
flex-grow: 1;
width: 100%;
&:before,
&:after {
content: "";
display: block;
width: calc(50% - #{width} / 2);
flex-shrink: 0;
background: inherit;
}
}
before, afterで背景色を引く
@mixin expand-side-background($width) {
width: $width;
position: relative;
// コンテンツを中央寄せにする
left: 50%;
transform: translateX(-50%);
box-sizing: border-box;
// コンテンツの両端にページ幅までのboxを作る
&:before,
&:after {
content: "";
display: block;
top: 0;
width: calc(50vw + #{$width} / 2);
height: 100%;
background: inherit;
}
&:before {
left: calc(-50vw + #{$width} / 2);
}
&:after {
right: calc(-50vw + #{$width} / 2);
}
// これがないと幅を縮めたときにブラウザ中央にい続けてしまう
@media screen and (max-width: $width) {
transform: none;
position: static;
&:before,
&:after {
display: none;
}
}
}
paddingで引き伸ばす
@mixin expand-side-background($width, $padding-left, $padding-right) {
box-sizing: content-box;
width: $width - $padding-left - $padding-right;
padding-left: calc(50% - %{$width / 2 - $padding-left});
padding-right: calc(50% - %{$width / 2 - $padding-right});
}
borderで引き伸ばす
@mixin expand-side-background($width) {
box-sizing: border-box;
width: 100%;
border-left-width: calc(50% - %{$width / 2);
border-right-width: calc(50% - %{$width / 2);
border-left-style: solid;
border-right-style: soild;
}