食べログスタイルガイドを使ったカード型UIの汎用モジュール作成

この記事は食べログアドベントカレンダー2020の13日目の記事です。

 12/13（日） 担当、WEBデザイナーの @mamiyan です！
食べログデザイナーがこのAdventCalendar2020に参加しているのは後にも先にも私一人なので心細いですが、食べログのコーディング（HTML5/Sass）について少しでも知っていただければ幸いです。

今回はスマートフォンサイト（以下、SPと略します）の [食べログ店舗詳細TOP](https://s.tabelog.com/tokyo/A1302/A130204/13018162/) で使っているカード型UIを汎用モジュールとして食べログスタイルガイドに追加するにあたり、どのようにモジュール化を検討したのかについてフォーカスした記事です。以下のキャプチャのUIですね。

■カード型UI
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225425/93202783-5dc0-5049-80c2-d18d4a417577.png" width="50%">

:sparkles:この記事を読んでほしい人

- カード型UIの汎用モジュール化に悩んでいる人
- カード型UIの汎用モジュール化の一例を知りたい人
- [FLOCSS](https://github.com/hiloki/flocss)を使ったカード型UIの作り方を知りたい人
- 食べログがどのような観点で汎用モジュール化をしているのか気になる人

# １）食べログスタイルガイド（Tabelog Styleguide）とは
<img width="807" alt="スクリーンショット 2020-10-08 16.07.19.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225425/87a199a6-2466-0f6d-78b1-abc3f94222fe.png">


>Tabelog Styleguide（以降「スタイルガイド」）は、 食べログで定義済みのスタイル（コンポーネント、モジュール）を一覧化し、 制作を行う上での記述ルールなどをドキュメント化したものである。
スタイルガイドは、新規ページ作成の際のクオリティー確保と、メンテナンス性の向上を目的としている。
既存ページの修正・更新作業の場合はこの限りではない。
スタイルガイドは、新たなルールの追加や改修が必要な際には、随時更新を行うものとする。
引用：食べログスタイルガイド（外部非公開）

上記の通り、食べログサイトを運営している上で、定義済みのモジュールが一覧化されているガイドラインです。
その他にカラー定義や便利なmixinなども掲載されており、見本のモジュールとHTML/slimがセットになっているのでコピペで使い回しができます。
なお、管理運用はフロントエンドエンジニアとデザイナーで行っています。
セキュリティの観点から外部公開をしていない為、一部を画面キャプチャで紹介します。

 **▼食べログのデスクトップのカラーパレット** 
<img width="1129" alt="ヘッダーなし1のコピー.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225425/6de4ea23-9679-916b-734f-7d96d46c5057.png">




 **▼食べログのSPのc-medal-area** 
<img width="1129" alt="ヘッダーなし2のコピー.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225425/04098c92-e625-5f92-0553-835bd976817a.png">



# ２）Componentか？それともProjectか？
現在、このカード型UIは複数ページで登場します（構成は少し異なりますが）
▼[SPエリアトップ](https://s.tabelog.com/tokyo/)のエリアランキングTOP3
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225425/b689a4a2-9b2f-7600-6356-07bdba03f130.png" width="50%">

▼[SPエリアトップ](https://s.tabelog.com/tokyo/)のジャンルランキング TOP20
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225425/b010d3a1-8f5d-9640-fa4e-1397d829f21d.png" width="50%">




今回担当の案件ではカード型UIが初登場だったので2-3度登場してから汎用モジュールを検討しても問題ないのですが、着手時点でそれ以外のページでも使用することを把握済みだったので最初の段階から汎用モジュール化を推し進めました。

食べログは[FLOCSS](https://github.com/hiloki/flocss)をCSS構成案として取り入れているため、それをもとに汎用モジュールを決めていきます。

>  **▼ FLOCSS** 
> 1. Foundation - reset/normalize/base...
> 2. Layout - header/main/sidebar/footer...
> 3. Object
>    3-1. Component - grid/button/form/media...
>    3-2. Project - articles/ranking/promo...
>   3-3. Utility - clearfix/display/margin...
> 他のOOCSSに基づくフレームワークおよびスタイルガイドと同様に、 再利用性や拡張性を持たせるために、これらのルールで分類をします。

> 引用：[FLOCSSの基本原則](https://github.com/hiloki/flocss#%E5%9F%BA%E6%9C%AC%E5%8E%9F%E5%89%87)

ComponentとProjectの定義は以下です。
 
> ▼Componentとは
再利用できるパターンとして、小さな単位のモジュールを定義します。
一般的によく使われるパターンであり、例えばBootstrapのComponentカテゴリなどに見られるbuttonなどが該当します。
出来る限り、最低限の機能を持ったものとして定義されるべきであり、それ自体が固有の幅や色などの特色を持つことは避けるのが望ましいです。


> ▼Projectとは
プロジェクト固有のパターンであり、いくつかのComponentと、それに該当しない要素によって構成されるものを定義します。
例えば、記事一覧や、ユーザープロフィール、画像ギャラリーなどコンテンツを構成する要素などが該当します。
> 引用：[FLOCSSのObject](https://github.com/hiloki/flocss#object)

FLOCSSではComponentのclass名は `c-`、Projectは `p-`、Utilityは`u-`の接頭辞を使って明確化しています。またCSSクラス名の命名規則もFLOCSSと相性の良いMindBEMdingを採用しています。

汎用モジュール化にあたりまず考えたのは、カード型UI（＝Object）をComponentとProjectどちらに分類すべきかです。
どちらにするのか悩むことが多々ありますが、最小単位のComponentとそのComponentなどの集合体をProjectと考え分類します。
このComponentとProjectの考え方を取り入れることで汎用モジュールの役割を明確にし、複数人開発する上での概念的共有を行なっていきます。

:warning: 注意
なお一部リプレイスを進めており、新しい部分は  [Atomic Design+styled-components](https://note.com/tabelog_frontend/n/n4b8bcb44294c)を採用しています。今後はAtomic Design+styled-componentsを使った実装方法で置き換えていく流れなので、FLOCSS・Sassは減少していく予定です。詳しくは [食べログ フロントエンドエンジニアブログ](https://note.com/tabelog_frontend)をご参照ください。


さて、前提条件の話は終わりにして、本題の食べログで使うカード型UIの話に戻ります。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225425/93202783-5dc0-5049-80c2-d18d4a417577.png" width="50%">


分解すると上部がサムネイル、下部にジャンル名とレビュー、dinner/lunch予算とコメント数があります。
はじめはカード1枚を指すComponentとしてc-list-cardというクラス名で作成することを検討していましたが、カード型UIが最小単位のコンポーネントではないことやカードの中に既存のComponent（クラス名：c-rating-v2  ※レビューを表す汎用モジュール）が存在しており、それに依存することになるのでProjectが最適だと判断しProjectで作成しました。

つまり、カード型UIの大きな枠組みはprojectで、中の小さな複数のモジュールはcomponentと切り分けていきます。

## p-vertical-rst
Project名は レストラン情報が垂直に並んでいるモジュールという意味合いで、p-vertical-rstと名付けました。以下、ソースコードは分かりやすいように簡略化しています。
![a77c4137-d5e2-75be-0bb2-aafe3c54c0ee.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225425/73379b89-506c-f3f2-5918-93584e945f6a.png)


```html:p-vertical-rst
<a class="p-vertical-rst" href="">
  <div class="p-vertical-rst__photo">
    // 写真
    // title
  </div>
  <div class="p-vertical-rst__text">
    // 詳細データ
  </div>
</a>
```

```scss

.p-vertical-rst {
  display: block;
  width: 150px;
  height: 100%;
  &__photo {
    position: relative;
    border-radius: 4px 4px 0 0;
    width: 100%;
    height: 94px;
    overflow: hidden;
    pointer-events: none;
  }
  &__text {
    padding: 0.8rem;
    border-radius: 6px;
    color: $g-text-color;
    background: #ffffff;
  }
}
```

## c-rating-v2
Projectの中に入ってくるComponentはレーティング部分と価格表示です。
こちらはカード型UIを作成するタイミングで一緒に新規作成した物ではなく、既存のモジュールとして用意されていました。

![69ebdc8e-09f1-e3ce-b258-cf145242b5f2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225425/b074cfa8-bf4a-dd89-a3e9-ed37b9f03e0e.png)

<details>
<summary>HTML</summary><div>

```html
//一例
<p class="c-rating-v2 c-rating-v2--val10">
  <span class="c-rating-v2__time c-rating-v2__time--dinner">Dinner:</span>
  <i class="c-rating-v2__star"></i>
  <b class="c-rating-v2__val">1.00</b>
</p>
```
</div>
</details>

<details>
<summary>Sass</summary><div>

```sass
.c-rating-v2 {
  $this: &;

  display: inline-block;
  word-wrap: normal;
  overflow-wrap: normal;
  white-space: nowrap;
  vertical-align: text-bottom;
  line-height: 1;
  font-size: 1.4rem;
  @include clearfix;

  + * {
    margin-left: 0.4em;
  }

  &__time {
    display: block;
    float: left;
    margin-right: 0.2em;
    width: 1em;
    height: 1em;
    overflow: hidden;

    &:before {
      display: block;
      letter-spacing: 1px;
    }
    &#{&}--dinner {
      color: #336aa2;
      &:before {
        @include glyph(tabelog, dinner2);
      }
    }
  }

  &__star {
    display: block;
    position: relative;
    float: left;
    margin-top: -1px;
    margin-right: 0.2em;

    &:before,
    &:after {
      display: block;
      top: 0;
      left: 0;
      letter-spacing: 1px;
      //content上書きが難しいためglyph指定直書き
      font-family: 'Tabelog Glyph';
      font-style: normal;
      font-weight: normal;
      font-variant: normal;
      -webkit-font-smoothing: antialiased;
      text-transform: none;
      speak: none;
    }
    &:before {
      position: relative;
      color: $g-icon-color--gray-extra-light;
      text-shadow: 0 0 1px $g-color--white;
      content: '\f603\f603\f603\f603\f603';
      #{$this}#{$this}--none & {
        content: '\f605';
      }
      #{$this}#{$this}--val00 & {
        content: '\f605\f605\f605\f605\f605';
      }
    }
    &:after {
      position: absolute;
      color: $g-key-color;
      #{$this}#{$this}--val10 &,
      #{$this}.is-val10 & {
        content: '\f603';
      }
      #{$this}#{$this}--val15 &,
      #{$this}.is-val15 & {
        content: '\f603\f604';
      }
      #{$this}#{$this}--val20 &,
      #{$this}.is-val20 & {
        content: '\f603\f603';
      }
      #{$this}#{$this}--val25 &,
      #{$this}.is-val25 & {
        content: '\f603\f603\f604';
      }
      #{$this}#{$this}--val30 &,
      #{$this}.is-val30 & {
        content: '\f603\f603\f603';
      }
      #{$this}#{$this}--val35 &,
      #{$this}.is-val35 & {
        color: #ff6a00;
        content: '\f603\f603\f603\f604';
      }
      #{$this}#{$this}--val40 &,
      #{$this}.is-val40 & {
        color: #ff6a00;
        content: '\f603\f603\f603\f603';
      }
      #{$this}#{$this}--val45 &,
      #{$this}.is-val45 & {
        color: $g-icon-color--attention;
        content: '\f603\f603\f603\f603\f604';
      }
      #{$this}#{$this}--val50 &,
      #{$this}.is-val50 & {
        color: $g-icon-color--attention;
        content: '\f603\f603\f603\f603\f603';
      }
    }
  }

  &__val {
    display: block;
    float: left;
    font-family: Arial;
    // bタグ等のboldスタイル打ち消し
    font-weight: normal;
    &#{&}--accent {
      color: $g-text-color--emphasis;
      #{$this}.is-highlight #{$this}__star + & {
        color: $g-text-color--rating;
      }
    }
    &#{&}--strong {
      color: $g-text-color--rating;
      font-weight: bold;
    }
    #{$this}__star + & {
      font-size: 1.08em;
    }
    #{$this}__val00 + & {
      margin-top: -0.2em;
    }
    #{$this}#{$this}--none + & {
      display: none;
    }
  }
}
```
</div>
</details>



![1dc78243-40e8-563e-8456-a4020e9bf1c9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225425/4e0eb33e-b49a-c6d8-99b8-7205aab6d88d.png)



<details>
<summary>HTML</summary><div>

```html
<p class="c-rating-v2">
  <span class="c-rating-v2__time c-rating-v2__time--lunch">Lunch:</span>
  <span class="c-rating-v2__val">-￥999</span>
</p>
```
</div>
</details>

Sassは上記と同一



## c-grids
また、カード型UIは複数作成し横並びにすることが多いので、横並びレイアウトの汎用モジュールも作成しました。
c-gridsの横並びが分かりやすいようにダミーでサムネイルを入れています。
実際にはc-gridsの中に `p-vertical-rst` を設置し、カード型UIを並べるイメージです。
![c-grids.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225425/280eff1f-a39a-88b5-071c-c2f549533443.png)



<details>
<summary>HTML</summary><div>

```html

<ol class="c-grids">
  <li class="c-grids__item">
    <img alt="レストラン タベログ - 料理写真:" src="" width="150">
  </li>
  <li class="c-grids__item">
    <img alt="レストラン タベログ - 料理写真:" src="" width="150">
  </li>
  <li class="c-grids__item">
    <img alt="レストラン タベログ - 料理写真:" src="" width="150">
  </li>
  <li class="c-grids__item">
    <img alt="レストラン タベログ - 料理写真:" src="" width="150">
  </li>
</ol>
```

</div>
</details>

<details>
<summary>Sass</summary><div>

```sass
.c-grids {
  $item-width: 152px;
  display: flex;
  padding-bottom: 0.8rem;
  &__item {
    position: relative;
    margin-left: 0.8rem;
    border: 1px solid $g-border-color--light;
    border-radius: 6px;
    width: $item-width;
    height: 100%;
    box-sizing: border-box;
    &:first-of-type {
      margin-left: 1.6rem;
    }
    &:last-child::before {
      position: absolute;
      top: 0;
      left: 100%;
      width: 1rem; // padding-right分
      height: 1px;
      content: '';
    }
  }
}
```

</div>
</details>

## 最終形態
最終的にこのような組み合わせになりましたー！
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225425/c223e996-d865-ea09-9921-b66433494c17.png" width="70%">


# ３）まとめ
ProjectとComponentの役割を理解しながら汎用モジュールを細分化していくにあたり、どれを一塊として切り出していけばいいのか自分の中で整理がつきました。
ただ1つ反省点があります。
c-gridsのSassを見てもらえれば分かりますが、横幅のpx指定やborderなどの装飾を担うように書いてしまいました。
c-gridsはあくまでも横並びレイアウトとしての役割のみに徹して、それ以外の細かな装飾は配下のモジュール（今回で言うとp-vertical-rst）が担うべきでした。
モジュールを分解していったとき、どこまでをどのモジュールに担うべきかはしっかり検討した上で作成するべきですね。
後ほど修正してデプロイしようと思います、、、

# ４）最後に
食べログのデザイン部門を更なる強化をするべく、新たなメンバーを募集中です。

- 大規模Webサービスの開発／運用に携わりたい方
- 多くのユーザーの目に触れ、多くの反響をいただくことをやりがいと感じられる方            
- 外食産業、ユーザーの外食体験をデザインの力で変えていきたい方    
- 食べること・飲むことが好きで、食べログが好きな方

ご応募をお待ちしております！


株式会社カカクコム 食べログデザイナー 採用ページ
https://hrmos.co/pages/kakakucom/jobs/1031001


明日はエンジニア @taigen さんの Kotlin Android Extensions syntheticsのdeprecatedに伴う対応 に伴う対応です。お楽しみに！