fragmentary notesITエンジニアの気まぐれメモ

Nuxt3 + Vuetify3 ブログにダークモードを設定してみた

Introduction

昨今のトレンドとして黒を基調としたテーマの画面に切り替えるダークモードを用意しているWEBサイトやアプリが多きなってきている印象があります。個人的には明るい画面より黒い画面のほうが見づらい・目が疲れる気がするのと、ダークモード対応UIから非対応の明るいUIに飛びそのギャップに目がやられるほうが嫌なので、あまりダークモードを好んで使ってはいません。とはいえ、ダークモードを好む・必要とする人がそれを選べないのもアクセシビリティの観点からいかがなものかと思い、この度 ダークモード対応してみました。

今回実施した対応は下記の通り。

  • Vuetify3のtheme切替機能を利用し、ダークモードのcolor themeを用意
  • トグルスイッチで、いつでもライトモード/ダークモードを切替可能にする
  • 切替内容を記憶し、次回表示時は前回指定したモードを設定する
  • 初回表示時はシステムの設定に基づき、ライトモード/ダークモードを初期設定する

対応内容1:Vuetify3のtheme上にダークモードのcolor theme用意する

Vuetify3には、事前にlightモード / darkモードそれぞれの色定義を設定することができます。これまではlightモードしか使用していなかった、かつ色定義をVuetifyの設定上ではなく、各コンポーネントやcss定義に直書きしていたため、まずはそれらの色定義をすべてtheme設定に外だしするところからスタート。Vuefityのconfigファイル上に必要な定義をlightモード / darkモード それぞれ設定していきます。

export default defineVuetifyConfiguration({
  theme: {
    defaultTheme: 'light',
    themes: {
      light: {
        colors: {
          page_title:'rgba(0, 0, 0, 0.87)',
          menu_text:'rgba(0, 0, 0, 0.87)',
          background:'#F9F9F9',
          main_text: '#737373',
          post_title: '#444',
          img_border: '#DDDDDD',
          code_bg: '#f1f1f1',
          code_text: '#000000',
          toc_bg:'#eceff1',
          th_bg:'#e9e9e9',
          filter:'#bbbbbb',
          footer_bg: '#F5F5F5',
          footer_text: '#555555'

        },
      },
      dark: {
        colors: {
          page_title:'#FFFFFF',
          menu_text:'#FFFFFF',
          background:'#303030',
          main_text: '#FFFFFF',
          post_title: '#FFFFFF',
          img_border: '#151515',
          code_bg: '#202020',
          code_text: '#FFFFFF',
          toc_bg:'#202020',
          th_bg:'#202020',
          filter:'#151515',
          footer_bg: '#212121',
          footer_text: '#FFFFFF'
        },
      }
    }
  }
})

次に各コンポーネントやcss定義に直書きして色設定個所を上記定義を参照するように書き換えます。

DOMレベルで指定できる部分については、DOMレベルで上記定義のクラスを設定します。本文テキストの色指定はtext-<色定義ID>、背景色指定はbg-<色定義ID> と指定します。

<v-app class="bg-background text-main_text">
…
</v-app>

CSSファイル上で指定が必要なもの(記事本文等、動的にDOMが生成され、事前にDOM上に情報付与できないケース)は、CSSファイル上で指定します。指定の仕方は、rgb(var(--v-theme-<色定義ID>))

h1{
  color: rgb(var(--v-theme-post_title));
}

併せて、vuetifyのTOPレベルDOM v-app上に、lightモード / darkモード のどちらを採用するかを指定するリアクティブ変数:theme を設定。これでthemeの設定値に応じて、lightモード / darkモード それぞれの色定義に切り替えることができます。

<v-app :theme="theme" class="bg-background text-main_text">
<script setup lang="ts">
…
const theme = ref('light')
…
</script>

対応内容2:トグルスイッチで、いつでもライトモード/ダークモードを切替可能にする

ヘッダー上にトグルスイッチを追加し、lightモード / darkモードを切替できるようにします。

<v-switch v-model="darkMode" @change="changeDarkMode"></v-switch>

イベントハンドラー:changeDarkMode内で、スイッチの状態をチェック、その結果をリアクティブ変数:theme に設定します。

<script setup lang="ts">
…
const theme = ref('light')

const darkMode = ref(false)

const changeDarkMode = () =>
{
  // darkModeのスイッチがON(True)の場合
  if(darkMode.value)
  {
    // リアクティブ変数:theme を 'dark' に設定
    theme.value = 'dark'
  }
  // darkModeのスイッチがOFF(False)の場合
  else
  {
    // リアクティブ変数:theme を 'light' に設定
    theme.value = 'light'
  }
}
…
</script>

対応内容3:切替内容を記憶し、次回表示時は前回指定したモードを設定する

対応2までで、lightモード / darkモードの切り替えが可能になりました。次に切り替えた内容を記憶し、次回訪問時も前回指定したモードが初期表示されるようにしたいと思います。切替設定内容をWEBブラウザのlocalstorageに記録し、ページ表示時にその値を参照するようにしましょう。

<script setup lang="ts">
…
const theme = ref('light')

const darkMode = ref(false)

// ページ表示時に実行
onMounted(() => {
  // ローカルストレージ上のdarkモード設定を確認
  const disMode = localStorage.getItem("display_mode")
  // ローカルストレージに記録された内容が 'light' or 'dark' の場合
  if(disMode == 'light' || disMode == 'dark')
  {
    // ローカルストレージに記録された内容に従って画面表示する
    theme.value = disMode 
  }
  // darkモードが指定されている場合
  if(disMode == 'dark')
  {
    // トグルスイッチをdarkモード有効化状態(ON)に切り替える
    darkMode.value = true
  }
})

const changeDarkMode = () =>
{
  // darkModeのスイッチがON(True)の場合
  if(darkMode.value)
  {
    // リアクティブ変数:theme を 'dark' に設定
    theme.value = 'dark'
  }
  // darkModeのスイッチがOFF(False)の場合
  else
  {
    // リアクティブ変数:theme を 'light' に設定
    theme.value = 'light'
  }
  // lightモード / darkモードの選択状態をlocalstorageに記録
  localStorage.setItem("display_mode", theme.value)
}
…
</script>

対応内容4:初回表示時はシステムの設定に基づき、ライトモード/ダークモードを初期設定する

初回時はシステム設定上 darkモードが有効化チェックし、有効であればdarkモードを採用するようにします。ただし、2回目以降でlocalstorageにlightモード / darkモードの指定が記録されている場合は、こちらを優先するようにします。

<script setup lang="ts">
…
const theme = ref('light')

const darkMode = ref(false)

// ページ表示時に実行
onMounted(() => {
    // システムのdarkモード設定を確認 → darkモード指定時
    if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
      // darkモードとして画面表示する
      theme.value = 'dark'
      // トグルスイッチをdarkモード有効化状態(ON)に切り替える
      darkMode.value = true
    }

  // ローカルストレージ上のdarkモード設定を確認
  const disMode = localStorage.getItem("display_mode")
  // ローカルストレージに記録された内容が 'light' or 'dark' の場合
  if(disMode == 'light' || disMode == 'dark')
  {
    // ローカルストレージに記録された内容に従って画面表示する
    theme.value = disMode 
  }
  // darkモードが指定されている場合
  if(disMode == 'dark')
  {
    // トグルスイッチをdarkモード有効化状態(ON)に切り替える
    darkMode.value = true
  }
})

const changeDarkMode = () =>
{
  // darkModeのスイッチがON(True)の場合
  if(darkMode.value)
  {
    // リアクティブ変数:theme を 'dark' に設定
    theme.value = 'dark'
  }
  // darkModeのスイッチがOFF(False)の場合
  else
  {
    // リアクティブ変数:theme を 'light' に設定
    theme.value = 'light'
  }
  // lightモード / darkモードの選択状態をlocalstorageに記録
  localStorage.setItem("display_mode", theme.value)
}
…
</script>

サンプルコード

上記対応をした完全なサンプルコードは下記GitHubのブランチに残しています。ご参考までに。

nuxt3weblog / darktheme branch

終わりに

というわけで最低限のdarkモード対応を実施してみました。改めて思ったことは。。。色指定をソースコードに直書き、ダメ、絶対!ほとんどの作業がこの直書きされた色指定の外だし対応でした。

今回、ダークモードっぽい形ということで色指定をしてみましたが、これはあくまで暫定版。とりあえず全部黒で塗りつぶしちゃえばいいか、と安直な考えて対応しているので、色合い・濃度は今後 調整したいと思います。

また、対応内容3の「切替内容を記憶し、次回表示時は前回指定したモードを設定する」についても、一度lightモードで描画したのちに、darkモードに切り替えるという実装をしているので、表示直後 白→黒に切り替わる という動きをしてしまいます。。。これもイケてないので、いずれはもっとよい感じにできないかと考え中。

参考文献

【Vue3+TypeScript+Vuetify+Vite】動的なテーマの切り替え - Qiita

Vuetify3 の基本 - Chapter 20 色の設定 (color, bg-color, class) - Zenn

Theme 変更点(Vuetify 2 〜 Vuetify 3 アップデート) - Zenn

Vuetify 端末のダークモード設定を自動で取得してテーマに反映する - Qiita

Vuetifyでダークとライトテーマを切り替える機能を実装する - Qiita

  • Home
  • /
  • Posts
  • /
  • Nuxt3 + Vuetify3 ブログにダークモードを設定してみた
Tech-TIPS

Comments

© 2024 shunya_wisteria