Mirrativ tech blog

株式会社ミラティブの開発者(サーバサイドエンジニア,iOSエンジニア,Androidエンジニア,機械学習エンジニア,インフラエンジニア, etc...)によるブログです

CSS Variablesを使ってWeb LP制作のエンジニア作業時間を0にした話

こんにちは。Webフロントエンジニアの駒木です。

Mirrativでは毎週の様に運営主催イベントやゲーム会社様とのコラボ企画イベント等が開催されます。 そのイベント情報をユーザーへお伝えするメディアとして、イベント毎にWebページ いわゆる LP ( Landing Page ) を制作・公開しています。

f:id:eightbeeeaaat:20210128003034p:plain
Mirrativで公開している多種多様なLP
ですが毎週の様に新しいイベントが企画・開催されますので、LPをエンジニアが都度制作していてはとても追いつきません。

そこでミラティブではCSS Variablesを活用することで、イベントの魅力が伝わるWeb LPをエンジニアが作業することなく制作・運用できる体制を構築しています。

本記事ではここまでに至った過程も含めお伝えします!

目指すはイベント運用の効率化と専門性の排除

遡ること半年ほど前、イベント企画チームの目標として『より多くのイベントを開催するため、PMだけでイベント / LPを立ち上げられるようにする!』という目標が設定されました。

当時はイベント準備にあたってGit操作、開発環境でのコマンド実行やデプロイ作業といったエンジニアではないメンバーには学習コストが高い作業が必要でした。

具体例を挙げると、イベントのマスタデータをDBへ反映する作業です。 マスタデータは複数人で頻繁に操作するためスプレッドシートで管理されていました。 データをDBへ反映するためにはスクリプト等でCSV出力した後にデプロイする必要がありました。

またLPはゲーム要素もあるリッチなWebアプリケーションを含むものもあるため、JSフレームワーク + SCSSで作られたSPA (Single Page Application)として実装されています。 そのため、色1つの変更でもフロントエンドの開発環境とCUIでのソースビルド作業が必要でした。

f:id:eightbeeeaaat:20210126170930p:plain
リッチなアプリケーションが実装されたLP

この様な運用体制だったため、PMは軽微な変更でもエンジニアに作業を依頼する必要がありました。 エンジニアは『マスタを変更したので反映をお願いします!』『LPも注意事項に一文加えたいので修正をお願いします!』といった依頼がある度に作業の手を止めて対応する必要があり、リリースには2日ほどエンジニアの工数を確保する必要がありました。

この状況を打開すべく、イベント運用の効率化と専門性を排除を目指す取り組みがスタートしました。 本記事では主にLP制作の課題解決にフォーカスしてご紹介します。

CSS Variablesを活用してイベントLP制作の課題を解決

まず初めに現在のLP制作における課題整理と解決の方向性を検討しました。

LP制作における課題

  • 運用効率が悪い
    • 変更の度にビルド & デプロイ作業が必要
    • 同種のイベントでも毎回微妙なデザインの差異が生まれるため、都度修正が必要
  • 作業に専門性が求められる
    • 開発環境を整える必要がある
    • CUIでの操作を覚える必要がある
    • Gitでのソースの取り扱いを覚える必要がある
    • デザインを変更するにはSCSSから対応するスタイルを見つけ、適切なスタイルを適用するための知識が必要

解決の方向性

  • アセットを更新せずに外部からデザインを変更できる様にする
  • 必要最低限の変更で完結するよう、デザイン変数を定めてテンプレート化する
    • 変数は画像 / テキスト / 色コードなど設定の学習コストが低く、 かつ最低限の数に絞る

1. CSS Variablesでアセットを更新せずに外部からデザインを変更する

外部からデザインを変更する手段を複数検討してみました。

案1. Web APIでスタイルを得て適用する: NG

万一APIからのGETに失敗した場合はデザインが当たらない状態のままユーザーが閲覧する可能性があります。 通信に成功しても遅延が発生した場合はスタイルが当たらない時間が発生しますのでNG。

案2. イベント毎に用意したCSSファイルを読み込ませて値を上書き: NG

追加でファイルを読み込むためAPI案と同じく遅延が問題となる可能性があります。 また、そのCSSを用意するには適用先のセレクタを完全に把握しておく必要があります。 セレクタの指定順などを間違えると正しく反映されない可能性もありデバッグも容易ではありません。

一度テンプレートとなるCSSを用意してしまえばその後は値を書き換えるだけの運用ですが、CSSファイルに不要な差分が紛れて劣化しないかエンジニアがレビューする必要がありますのでGitでの管理も必須。

遅延の問題、専門性が残ってしまう上にエンジニアの工数確保も必要なのでこれもNG。

案3. サーバレンダリング時に<style>要素を追記して値を上書き: OK(ただしCSS Variablesと併用)

サーバレンダリング時に直接HTMLに埋め込むので余計な通信が発生しません。これはなかなか良さそうです! ただ、単純にセレクタを列挙して値を上書きするならCSSファイルになっていないという違いだけなので案2.と同じく保守が問題。

これもNG?

いえ、まだ手があります!それが CSS Variables です。

CSS Variablesとは

その名の通りCSSにおける変数で、 -- で始まる自分で設定したkeyに対してCSSの値を持たせることができる機能です。 変数を呼び出す際は var(--hoge) と記述することで値を参照できます。 CSS VariablesはCSS カスタムプロパティとも呼ばれます。(MDN Web Docs)

サンプル
<html lang="ja">
  <head>
    <style>
      /* グローバルな初期値として設定した例 */
      :root {
        --color-red: #ff0000;
        --color-blue: #0000ff;
      }

      .text--red {
        color: var(--color-red);
      }

      .text--blue {
        color: var(--color-blue);
      }

      /* ブラウザがダークモードの時は彩度を落とす */
      @media (prefers-color-scheme: dark) {
        :root {
          --color-red: #660000;
          --color-blue: #000066;
        }
      }
    </style>
  </head>
  <body>
    <p class="text--red">赤いテキスト</p>
    <p class="text--blue">青いテキスト</p>
  </body>
</html>
通常 ダークモード
f:id:eightbeeeaaat:20210128005652p:plain f:id:eightbeeeaaat:20210128005725p:plain
f:id:eightbeeeaaat:20210128005754p:plain f:id:eightbeeeaaat:20210128005820p:plain

今度は赤と青を入れ替える <style> 要素を後ろに挿入してみます。 後勝ちの設定で変数を上書きできるため、その変数を利用している全てのスタイルを書き換えることもできます。

この様にCSS Variablesを利用すれば上書きしたいスタイルを反映できることが分かりました。

  ...
  </style>
 /* 変数を上書きするstyle要素を追加 */
  <style>
    /* 赤と青が入れ替わるように上書き */
    :root {
      --color-red: #0000ff;
      --color-blue: #ff0000;
    }

    /* ダークモード時の値も上書き */
    @media (prefers-color-scheme: dark) {
      :root {
        --color-red: #000066;
        --color-blue: #660000;
      }
    }
  </style>
</head>
<body>
  ...
通常時(上書き) ダークモード(上書き)
f:id:eightbeeeaaat:20210128005942p:plain f:id:eightbeeeaaat:20210128010003p:plain
f:id:eightbeeeaaat:20210128010444p:plain f:id:eightbeeeaaat:20210128010458p:plain
最大のメリット: CSS Variablesはビルドいらず

Sass/SCSSなどのプリプロセッサ、PostCSSといったポストプロセッサで用いられる$ などで定義する変数との重要な違いは、CSS Variablesはビルドが不要で主要モダンブラウザであればネイティブで動作する点です。(※)

イベント毎に用意したプリプロセッサの変数を読み込ませてビルドすることでも目的は達成できそうですが、追加実装せずにブラウザだけで動作するのであればそれに乗らない手はありません。

プリプロセッサを利用するための環境構築もビルドも不要でこの強力な変数機能を使えるようになったことは、個人的にはGrid Layoutに並ぶ近年のCSSにおける大変革と感じています。

サンプルではダークモード時の上書きを例にしていますが、同様にMedia Queryと組み合わせて

  • 画面幅が一定より狭かったら視認性を上げるためにfont-weightを太らせる
  • 印刷時は強制的にページを白黒にする

といったことも、変数を上書きするだけの少ないコード量で記述することができます。

YouTubeAppleInstagramなどでも利用されていますので、ご興味があれば是非各社のページを開発ツールなどで覗いてみてください。 また、流行りのCSSフレームワークであるTailwind CSSでも内部的にCSS Variablesが利用されています。

※ レガシーブラウザ対応はcss-vars-ponyfillなどをご利用ください

2. 過去のイベントLPからデザイン変数を定めてテンプレート化する

f:id:eightbeeeaaat:20210128085902p:plain CSS Variablesを使えばスタイルを上書きできるということが分かりました。 LPの中からイベント毎に書き換えたいスタイルを変数化すれば運用は回りそうです。

そこで次はLPのテンプレート化を進めていきます。 効率よくテンプレート化するためには、予めイベント毎に変わりうるデザイン要素、つまり「デザインの変数」を絞り込んでおくことが重要です。

  • テンプレートが受けとるデータ
    • イベントID
    • イベント開催期間
    • デザインの変数
      • 配色
        • キーカラー
        • 本文の色
        • ...
      • テキスト
        • キャッチコピー
        • ...

「イベント毎にどのデザインが変わるか分からない」という状況では、どの要素のどんな値も変更できるように全てを変数にしておく必要があります。 CSSのプロパティだけでも無数にありますので、LPの全要素を変数に起こすというのはあまり現実的とは言えません。

LPを1つ1つ開発していた当時はまさに前述の状況で、実装依頼されたデザインと前回の実装を上から順に見比べて適用していくという開発スタイルでした。 作業に慣れていた私でも 1イベントあたり半日〜1日かかりましたし、「今回はここも変わってたの!?」とイベントリリース前の最終チェック時に気づくということもありました。

対して【イベント毎に変更するのはコンポーネントAの背景色とコンポーネントBの文字色】とルールが定められていれば、2つのコンポーネントの変数にのみ意識を向ければ良いので抜け漏れといった作業ミスも減ります。

とはいえ、何もない所から一発で今後も運用し続ける変数を定義することは難しい作業です。 そこでまずは過去のLPを比較し、変更された回数が多い要素から順に名前付けして仮のデザイン変数として整理していきました。

f:id:eightbeeeaaat:20210125215023p:plain
ゲージコンポーネントのデザイン変数

テンプレート化によって損なわれたデザインのアレンジ性

LPの訴求力を高めるため同一種のイベントでもモチーフに基づいて担当デザイナーの創意工夫が行われ、コンポーネントのデザインがアレンジされることもありました。

色の変更等であれば変数化は容易ですが、レイアウトや形状の変化を複雑に組み合わせた変更など容易に吸収できない差分が生じる場合もあります。

f:id:eightbeeeaaat:20210126001941p:plain
イベント期間表現の差異

こうした細やかな意匠の変化を積み重ねることでイベントの世界観を演出・差別化できますが、差別化を推し進めるとスケールさせることが難しくなってしまいます。

上記のコンポーネントを例にすると、見出しテキストの有無、線の囲い方、塗り分けなどを全て変数にしたり、表現を切り変える為の変数を管理すればマスタ化は不可能ではありません。

ですが迂闊に変数を追加していくとチェックすべき変数も膨大な数となります。

デザイン作業 / 変数を適用する作業 どちらの工数も膨れ上がっていくことになりますので、テンプレート化する上では変数を削ぎ落とすことも必要な作業です。 こうした悩み多き変数はデザイナーチームに相談し、デザインの自由度を極力落とさずに成り立つバランスを見ながら摺り合わせを行いました。

3. Google Spreadsheetで入稿したデザインをDBへ同期するシステム

f:id:eightbeeeaaat:20210126052419p:plain デザイン変数を定義しても実際にLPに適用するには兎にも角にも値を読み書きする仕組みが無ければお話になりません。 エンジニアを介さずにPMがイベントマスタデータを入力できるようにする手段を検討した結果、この様なシステムが開発されました。

  • PMがイベントデータの登録で使い慣れたGoogle Spreadsheetで入稿
  • シートとDBを同期する管理機能
  • マスタ同期状況をSlackに通知

同じシステムでイベント毎のデザイン変数を管理することでPM自身の手でデザインを変更できるようにしました。 このシステムを実際に稼働させるまでの苦労はまた別の方から語られるでしょう...。

f:id:eightbeeeaaat:20210126052144p:plain
マスタ登録されたデザイン変数

4. マスタ化されたデザインをCSS Variablesとして適用

f:id:eightbeeeaaat:20210126054931p:plain

いよいよ、マスタ登録されたデザイン変数をLPに適用します。

  • マッピング用のCSSでCSS Variablesをコンポーネントへ割り振る
  • style要素にCSS Variablesとして反映
    • デザイン変数をサーバから受けられる様にテンプレート構文を記述
  • 文言や画像はテンプレート構文で直接埋め込むか、<script>要素に持たせてJSへ引き渡す
<head>
  ...

  /* イベント共通のマッピング用CSS */
  <link rel="stylesheet" href="https://.../roulette/style.css" />

  /* マスタ登録されたスタイルをサーバからCSS Variablesとして流し込む */
  <style>
    :root {
      --primary-color: [% primary_color %];
      --secondary-color: [% secondary_color %];
      ...
    }
  </style>

   /* アプリケーションで利用する変数はscript要素を経由させる */
   <script>
    if (!window.mr) {
      window.mr = {
        event_id: [% event_id %],
        text: {
         description: [% description %],
          ...
        }
      };
    }
  </script>
<head>

f:id:eightbeeeaaat:20210126142454p:plain

無事にマスタ登録したデザイン変数をCSS Variablesとして適用することができました!

f:id:eightbeeeaaat:20210126054635p:plain
CSS Variablesが適用されたイベントLP

[TIPS] イラストの色変更はSVGをコンポーネント化してPropsとして渡す

テーマ毎にイラストも変化させたい場合は、SVGをコンポーネント化した上でPropsとして上書きするという手段も使えます。

import * as React from "react";

export const Ribbon: React.FC<{
  color: string;
}> = (props) => {

  const { color } = props; // CSS Variablesから取り出した色を当てる

  return (
    <svg
      width="200"
      height="40"
      viewBox="0 0 200 40"
      xmlns="http://www.w3.org/2000/svg"
      xmlnsXlink="http://www.w3.org/1999/xlink"
    >
      <g>
        <path
          d="..."
          fill={color}
        />
        ...
      </g>
    </svg>
  );
};

f:id:eightbeeeaaat:20210126054056p:plain
SVGイラストの配色を上書き

まとめ

イベント運用の効率化と専門性の排除を推進したことで、PMだけで作業を完結できるようになり1イベントあたり2日ほど必要だったエンジニア作業時間を0にすることができました。

取り組みの開始から半年が経った現在では、Mirrativ運営が開催するイベント、ゲーム会社様とのコラボ企画イベントも含めると8種ほどのイベントが存在します。 これらのイベントが常に何かしら開催されているため、イベント数は月あたり数十にもなります。

その全てがWeb LPを持つわけでありませんが、それでも相当な数のWeb LPがエンジニアを介さずに制作・運用され続けています。 半年前の手作業による制作・運用フローのままであれば現在の様に多種多様なイベントをお届けすることは不可能だったでしょう。

一発で今後も運用し続ける変数を定義することは難しい

デザインの変数は1度決めればそこがゴールではありません。 イベントは常に改善されるので要素は増減しますし、デザインのトレンドも変わります。

ですが改善に強い一連のシステムを作ることができたことで、より良いユーザー体験をより早く提供すること出来るようになりました。

We are hiring!

このようなアグレッシブな改善をし続けるミラティブではサーバーエンジニアを募集中です!

  • ゲーム×ライブ配信サービスの開発をしたい
  • サーバーシステムの基盤の整備をしたい

といった方のご応募をお待ちしております!

www.mirrativ.co.jp

speakerdeck.com