クレイの asachun です。
ついに DocBase もダークテーマに対応しました!(長らくお待たせしました……!)
今回はデザイン・フロントエンドの観点から、どのようにダークテーマ対応を進めていったかご紹介します。
フロントエンド改修時にダークテーマ対応の話は出ていた
実はフロントエンド改修の段階でダークテーマ対応を検討し始めていました。ですが、フロントエンド改修が大きな作業のため、同時に進めることは避けていたのです。
ただフロントエンド改修の際に、ダークテーマ対応を見据えた仕組みも作っていただいていました。
他の作業の合間に少しずつダークテーマ対応を進め、この度リリースとなりました
デザインの段階でライト⇔ダークのカラーを定義
フロントエンド改修の際にデザインデータも Figma で作り直したのですが、そのときに汎用的なカラーはダークテーマ時のものも定義していました。
Figma でのダークテーマ制作の際には Dark mode swither プラグインに大変お世話になりました!
Color styles を定義するときに、Light/bg/base
のようにLight|Dark
を含めたスラッシュ区切りの名前にすると、プラグイン実行時に一発でダーク/ライトカラーを変換してくれます。
コンポーネント単位で実行すれば、Light
やDark
の含まれる複数の Cololr styels をいっぺんに変換できるので大変重宝しました。
(ときどき変換に失敗するときもあるのですが、1つ1つ切り替えるより断然楽です!)
あと Figma はコンポーネントを選択したら、子孫コンポーネントで使われてる色をすべて表示してくれるのがいいですよね。色の変更も簡単にできますし、この機能を考えた人は神だと思います。
テーマ切り替えの実装
テーマ切り替えの仕組みはパートナーのフロントエンドエンジニアの方に実装していただいたので、ブログ用の文章もご寄稿いただきました ありがとうございます!
(こちらのセクションはご寄稿いただいたものになります)
DocBase でのテーマ切り替えの実装には、styled-components がビルトイン機能として提供している ThemeProvider コンポーネントを使用しています。
フロントエンド改修のための技術選定の際(2020年頃)に、Sass から styled-components への移行を決定し、そのときにテーマ切り替えの仕組みも ThemeProvider
を使って導入しました。
この ThemeProvider
を使うと、前述のような汎用カラーの定義を元に、色の値だけが異なる同じ型のオブジェクトをテーマごとに用意することで(下記 lightTheme
と darkTheme
)、そのオブジェクトを切り替えるだけで簡単にテーマ管理ができるようになります。
const lightTheme = { /* ライトテーマの色指定 */ }
const darkTheme = { /* ダークテーマの色指定 */ }
const App = () => {
return (
<ThemeProvider theme={ theme === 'light' ? lightTheme : darkTheme }>
<FooComponent /> { /* ThemeProvider配下のコンポーネントがテーマ対応される */ }
</ThemeProvider>
)
}
コンポーネント固有のテーマ別の色指定
前述の ThemeProvider
を使った色(テーマ)の管理は、汎用カラーでどのコンポーネントからでも参照できるスコープになるので、いわばグローバル変数のような扱いになります。
ただ、実際のコンポーネント作成では、グローバル変数にする必要のないコンポーネント固有の色指定も存在し、その場合はグローバルスコープを避けてローカル変数のような扱いにしたいです。
そのような「色指定をコンポーネント内に閉じ込めたい」ケースでは、styled-components が提供している styled-theming というライブラリをベースに、DocBase 独自にカスタマイズした theming
関数を用意して対応しました。
const BarComponent = styled.div(() => {
// 独自関数themingを使って、コンポーネント内に閉じたテーマ別(light/dark)の色指定を行う
const colors = {
text: theming({
// そのコンポーネントでしか使用しない色をハードコーディング
light: '#f4f5f6',
dark: '#323233',
}),
bg: theming({
// 定義済みの汎用カラー(グローバル変数)を参照することも可能
light: c => c.blue_800,
dark: c => c.blue_100,
}),
}
return css`
color: ${colors.text};
background-color: ${colors.bg};
`
})
上記のように定義できる theming
関数を用意したことで、グローバルスコープの色指定が増え過ぎることを避け、そのコンポーネント内だけでテーマ別の色指定を完結できるようになりました。
また、TypeScript の型チェックやコード補完を利用することで、必ず light / dark の両方が色指定されていることを自動的にチェックできたり、定義済みの汎用カラーを補完候補から簡単に選べるようにもなりました。
現在の DocBase では、上述の ThemeProvider
コンポーネントと theming
関数の2種類を使い分けて、テーマの切り替えを実現しています。
よかったこと
先にライト・ダーク含めた汎用カラーを定義しておいたので、ダークテーマ対応が楽になった
フロントエンド改修の際に汎用カラーのライト・ダークテーマもある程度定義していました。
その際に、既存コンポーネントでもなるべく汎用カラーを使うよう修正し、新規コンポーネントも汎用カラーを使うようにしました。
(もちろん汎用カラーを使っていないコンポーネントもありますが)
このおかげで、ダークテーマ対応を始めたときには、汎用カラーを使ったコンポーネントはほとんど手を加える必要はありませんでした。
またなるべく汎用カラーを使うようにしたことで、デザインデータにありがちな「見た目はほとんど同じ色なのにカラーコードが違う」ミスをほぼ無くせました。
コンポーネントの色を決めるときはなるべく定義済みの Coloer styles を使用します
Storybook があった
フロントエンド改修の際に、主要なコンポーネントは Storybook に登録していました。
こちらのおかげで確認作業がだいぶ楽になりました……!
Color の変数を TypeScript オブジェクトにしたので候補から楽に選べる
「テーマ切り替えの実装」で書いていただいたように、Color の変数を TypeScript のオブジェクトとして定義しています。そのため、styled-components で利用する際に${theme.}
を打ち込んだだけで定義済みの変数がサジェストされるようになりました。
typo も防げますし、直感的に記述できるので、かなり開発体験もよくなったと思います!
大変だったこと
メモ本文の文字色を試行錯誤
DocBase には文字色を指定する機能があります。
選択した文字列を<span style="color:{{カラーコード}};"></span>
で囲むシンプルなものです。
それらの色は、ダークテーマに切り替えたときに著しく可読性が落ちるため、調整する必要がありました。
ダークテーマ時には文字色を反転→色相だけ戻す
こちらはエンジニアの方に「js で文字色を反転して、輝度を暗い背景に合わせたあと、色相だけ元に戻せばいいのでは?」とアイデアをいただきました。
白背景で可読性の高い文字色の輝度を反転させたら、そのまま暗い背景で見やすい輝度になるわけです。
実際には以下のように実装してもらいました。
- メモ本文内の
style
属性でcolor
が指定されているspan
要素を抽出 style
属性で指定されたcolor
の値を反転- 輝度の値が反転、色相が180度変わります
- 色相を再度180度回転させて元の色相に戻す
実装としてはPolished
ライブラリを使い、該当のカラーコードにcomplement(invert(${textColor}))
をかけています。
Polished とは、Sass の Mixins や functions を React でも使えるライブラリです。invert
は指定のカラーコードを反転した値、complement
は補色(いわゆる反対色)を返します。
そして After がこちら!
う〜〜〜〜ん! 緑とか紫は見やすくなったけどグレーとかピンクはまだ見づらい!
単純に反転+色相移動では足りないようです。
DocBase はテキストを読むサービスなので、文字と背景のコントラスト比4.5:1
は達成したいところです。
まずはライトテーマを微調整
そもそも、ライトテーマでもいくつかコントラスト比4.5:1
を達成していないものがあるので、合わせて調整しました。
(2番目のグレーは disable 状態など、読ませることを意識していない想定だったので対象外としています)
← Before After →
色の違いは少しわかりづらくなりましたが、見やすくなりました!
ピンクやオレンジ、黄色は明度を下げすぎると別の色になってしまうので、だいぶ調整に苦労しました。。
また、彩度が低いと反転したときに色の判別が難しくなるので、彩度を高めにしています。
ダークテーマの文字色調整
お次はダークテーマで文字色を反転したときの調整です。
complement(invert(${textColor}))
したときの色が全体的に暗く感じたので、Polished
ライブラリで使えるlighten
関数を追加しました。
lighten
は、指定した値に応じて明るくしたカラーコードを返してくれます。
値は0~1
の間で指定できるのですが、この調整もけっこう悩みました。。
明るくすればするほど可読性は上がるのですが、同時に白っぽくもなっていくため、色の判別がつきづらくなるデメリットがありました。
ひたすらブラウザ上で値の調整とリロードを繰り返し、最終的には以下の値になりました。
(この作業は丸1日ぐらいやっていた気がします……)
lighten(0.06, complement(invert(${textColor})))
そして実際の見え方はこちら!
全体的に見やすくなりましたね!
デザイナーも意外と泥臭い作業をしています。
終わりに
デザインの観点からダークテーマ対応について語ってみました。
個人的にはデザイン作業を Figma でやったことでかなり効率が上がったと思います。
ダークテーマについて、「ここが見づらい」などご意見があればお気軽にサポートまでご連絡ください!
このエントリーに対するコメント
日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)
- トラックバック
「いいね!」で応援よろしくお願いします!