Vue.jsでページ全体のカラーをコントロールする

先日Whalebirdのバージョン0.5.0をリリースした.このリリースに,アプリのテーマカラー変更が含まれている.

中身はElectronの上でVue.jsが動いているだけなのだが,どうやってテーマを変更するか,だいぶ悩んだので書いておく.

クラスをすげ替える

普通に作っていて,「あーここはスタイル変えたいな」と思ったら,大抵の場合はつけるクラスをすげ替えるだけでいける.

<template>
  <div :class="customizedClass">
  </div>
</template>

<script>
export default {
  data () {
    return {
      customizedClass: 'hoge'
    }
  }
}
</script>

<style lang="scss" scoped>
.hoge {
  background-color: #000000;
}

.fuga {
  background-color: #ffffff;
}
</style>

こんな感じにしておいて,customizedClassfuga に切り替えたりすればクラスが変わる. これによりデザイン変更が可能だ.

ただ,テーマカラー変更のように,全ての画面において,全体の色を変えたいという用な要望の場合,いろんな画面でこういった切り替えを作らなければならないので,あまり現実的な解とはいえない.

また,クラスの挿げ替えは,用意されたクラス1から用意されたクラス2というような変更しかできない. 例えば将来的に「ユーザが好きなテーマを作れる」というような機能を実装したくなった場合に,まったく対応できなくなる.

style属性を上書きする

クラスをすげ替えるのではなく,cssのスタイル定義を直接コンポーネントに当てることもできる.

<template>
  <div class="hoge" :style="style">
  </div>
</template>

<script>
export default {
  data () {
    return {
      style: {
        'background-color': '#ffffff'
      }
    }
  }
}
</script>

<style lang="scss" scoped>
.hoge {
  background-color: #000000;
}
</style>

というようにしておけば,hoge クラスがあたった上で,style属性で background-color を上書きできる.

確かにこれなら,ユーザが定義したカスタムテーマという拡張が入った時にも対応できそうだ. ただし,やはり背景変更が必要な全てのコンポーネントでstyle属性の上書きが必要になる.上書きが必要ということは,その都度,style属性に指定する値を取得・計算する必要があるという意味でもある.

これはかなり面倒くさい.

css変数を使う

上記の改造で,background-color を直接上書きするのではなく,css変数をVue.js側から操作するパターン.

<template>
  <div :class="customizedClass">
  </div>
</template>

<script>
export default {
  data () {
    return {
      style: {
        '--background-color': '#ffffff'
      }
    }
  }
}
</script>

<style lang="scss" scoped>
.hoge {
  --background-color: #000000;
  background-color: var(--background-color);
}
</style>

と,これだけ見ると,先ほどと大差ない感じはする.

ただし,これがcss変数になっている点で大きく変わることになる. 今,コンポーネント内のstyle定義は全てscopedにしている.そのため,この中で定義したクラス等はこのコンポーネント内でしか使えない.

しかし,これを全てのコンポーネントのベースとなる場所で,scopedを外してやったらどうか.

即ち,全てのコンポーネントの親となる App.vue のようなものを用意してあり,

<template>
  <div id="app">
    <router-view></router-view>
  </div>
</template>

<script>
export default {
}
</script>

<style lang="scss">
body { font-family: 'Noto Sans', sans-serif; }
</style>

みたいな定義になっているとする.この中で先ほどのcss変数を使ったらどうか.

<template>
  <div id="app" :style="style">
    <router-view></router-view>
  </div>
</template>

<script>
export default {
  data () {
    return {
      style: {
        '--background-color': '#ffffff'
      }
    }
  }
}
</script>

<style lang="scss">
body { font-family: 'Noto Sans', sans-serif; }

#app {
  --background-color: #000000;
  background-color: var(--background-color);
}
</style>

としておく.で,子コンポーネント側では,

<template>
  <div class="hoge">
  </div>
</template>

<script>
export default {
}
</script>

<style lang="scss" scoped>
.hoge {
  background-color: var(--background-color);
}
</style>

としておく.

--background-colorcss変数で,unscopedなstyleの定義となっているので,App配下のコンポーネント内の全ての場所で使える.

つまり,styleの更新はApp.vueだけで行えば良くなる.

コンポーネントのスタイルを全て変数を使った形式に書き換えておく必要はあるが.

これなら,ユーザ定義のカスタムテーマを導入した際にも,Appで変数を変更できるようにしておくだけで済む.

というわけでダークテーマが出来上がった

f:id:h3poteto:20180421214948p:plain

ちなみに色彩のセンスはまったくないので,Mastodonの公式で使用している色をかなり真似ている.

そのうちテーマ色もカスタマイズできるようにするので,センスある人は自分の好きな色に変えてください.

こちらが参考になった.

qiita.com