Vue3＋MediaQueryを使ってデバイス（画面幅）毎にデザイン切替してみる。

# この記事について

趣味と実益を兼ねて、ルービックキューブアプリを作りながらいろいろ勉強している所です。
※発端はRustの勉強でした（[Rust + seed でWebAssemblyを体験してみる。](https://qiita.com/etnk/items/35de53198e1c9f1d6959)）。

※2022年7月30日、ドメイン有効期限切れによりURL更新

https://www.cubetrain.silverbox.link/

少しずつ改良していってる所ではありますが、気になる点が出てきました。
<font color="Red">**スマホだとまともにプレイできない**</font>のです。外出中にちょっとプレイしようと思って気づきました。
この記事はこの点の改善記録になります。※現在は修正後のものがデプロイされています。

# 使用技術

[Vue3](https://v3.ja.vuejs.org/guide/introduction.html)
[Vuetify 3 Beta](https://next.vuetifyjs.com/en/getting-started/installation/)（※Vue3用は2022年7月9日現在、β版です）

# 問題点

修正前の画面は以下の様になっています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/866006/09fcff31-d4ff-c47a-f08f-087dfd6c02a9.png)

## 画面サイズ1920×1080前提にしてる

左側に設定メニュー、右側に操作履歴を表示しています。それぞれクリック操作で畳む事は出来ますが、スマホのサイズにすると、真ん中の領域がほぼ見えない状況になってしまいます。

## ボタンorキーボード操作を重視している事からくる色々

ちょっと自分の甘えから来ています。キューブ表示部分の操作で回転できるようにするのが解りやすいのですが、一旦それは先延ばしにしていました。その代わりにボタン操作で回転出来る様にしています。いちいち軸とボタンを確認しないと思った通りに動かしにくです。軸方向と記号を覚えている人ならキーボードで操作できるのでいいのですが、それが出来るのは多分少数です。自分もそんなに慣れてないです。

これによりボタンの数が多くなっています。操作性のみならず、画面領域も多くとってしまっています。画面幅が狭くなると、ボタンの位置が横並びでなく、下に配置されたりして、ボタンエリアが縦長になり、かつ本体のキューブ表示がその下に来るため、ほぼ操作できないです。

ブラウザの開発者モードで、Pixel5のサイズにして、履歴表示だけ折りたたんだ時です。キューブ表示は画面をスクロールしないと見えません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/866006/64698cd0-8020-4d3c-d240-a68b581035a5.png)

# 対応考察

上記問題点を解消しなくてはいけません。それぞれの問題点で対処法を考えていきます。

## キューブ操作

最小限の表示範囲でも操作できるようにする必要があります。キューブの表示は必須なので、その表示エリアの中で操作できるようにします。
ここに関してはこのアプリ特有なので詳細に説明はしませんが、以下の様にします。

- キューブの１ブロック上にホバーすると操作可能な４方向それぞれに矢印アイコンを表示する
- ホバーが出来ないデバイス（スマホなど）の為に、ブロックをクリック（押した瞬間）にも矢印ボタンを表示する
- 矢印アイコンをクリック（離した瞬間）すると、その方向に回転する

## 画面幅

今回のメインテーマです。スマホの画面幅でも使いたいとはいえ、ブラウザで使う場合には色々な情報を表示したいです。
ここで使用するのがMediaQueryです。Vueに限った事ではなく、画面幅やデバイスに応じてcssを切り替える技術です。

https://developer.mozilla.org/ja/docs/Web/CSS/Media_Queries/Using_media_queries

これをVue3上で効率的に出来るモジュールがあります。説明によるとVuetifyのプリセット設定がある様です。

https://bestofvue.com/repo/craigrileyuk-vue3-mq

こちらを使用して画面幅によるデザイン切替を行っていきます。

### 表示する情報の優先順位

1. 正面ビュー：これが無いとアプリになりません。
1. 背面ビュー：必須では無いですが、重要度は高いです。切り替えたい所です。
1. 操作履歴：操作戻しなどをする際に必要になりますが、常駐である重要度は低いです。
1. 操作ボタン群：ブラウザで見る時しか使わないものです。キーボードが想定される画面幅以外では不要です。
1. 設定メニュー：プレイ開始時などしか使わないものです。

### 隠した時の対応

隠すのは良いですが、使いたい時もあるので、表示できる様にする必要があります。対応を考えます。

#### 背面ビュー、操作ボタン群

基本は切り替えスイッチを付けます。スイッチは設定メニューに追加します。
画面が狭い時、それぞれの表示ブロック内ではデザインが崩れない様にします。
ブロック単位で、折り返しする様にして、縦スクロールで完結する様にします。

#### 設定メニュー、操作履歴

両方ともに、折り畳み可能にはなっています。しかし、表示した時はメイン部分の幅も狭まります。画面幅が狭い時はオーバーレイ表示にして画面崩れを防ぎます。

# 実装

## 前準備

Vue3用のMediaQueryモジュールをインストールします。

```console:コマンドラインからインストール
yarn add vue3-mq
```

main.tsに記述追加します。[公式ページ](https://bestofvue.com/repo/craigrileyuk-vue3-mq#2-plugin-configuration)によるとvuetifyのプリセットがある様なので、組み込みます。

```typescript:main.ts
import { Vue3Mq } from "vue3-mq";
// ・・中略・・
app.use(Vue3Mq, {
  preset: "vuetify"
});
```

これだけだと以下のエラーが出てきます。型ファイルが無いことが原因の様です。
※2022年7月17日現在の私の状況におけるものです。最新だと発生しないケースもあると思います、

```console
TS7016: Could not find a declaration file for module 'vue3-mq'. '/vue/cubetrain/node_modules/vue3-mq/dist/vue3-mq.umd.js' implicitly has an 'any' type.
  Try `npm i --save-dev @types/vue3-mq` if it exists or add a new declaration (.d.ts) file containing `declare module 'vue3-mq';`
```

回避の為にsrcフォルダ直下に`vue3mq.d.ts`ファイルを追加します。

```typescript:vue3mq.d.ts
declare module 'vue3-mq'
```

## アプリ側修正

準備が出来たので、アプリ側修正に移ります。操作方法の改善に関しても修正しましたが、今回の記事とは目的が異なるので省きます。

### 操作メニュー、操作履歴の折り畳み、狭い時のオーバーレイ表示

なんと、この制御は先ほど指定したプラグインが全てやってくれる様です。後述画像の様に、画面が狭い時は表示する時にオーバーレイ表示にしてくれます。広い時には通常のメニュー表示になります。操作メニューだけでなく、操作履歴もです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/866006/3bd4ea98-8fc3-f7b7-9677-0023df7db560.png)

強いて修正した事を言えば、メニューの表示を`v-if`で制御していたのですが、[Navigation drawers](https://next.vuetifyjs.com/en/components/navigation-drawers/)の正式制御方法である`v-model`で指定する様にしたことぐらいです。

### 操作ボタン群、背面ビューの折り畳み

前述メニューが折りたたまれるのは画面幅が1280pxの時の様です。それ以下になった時に操作ボタンの非表示やビューの表示方法の切り替え（横並びから縦ならびへ）を行います。

画面幅のエイリアスに関しては、[Vuetifyの公式ページ](https://next.vuetifyjs.com/en/styles/float/)に記載されていました。[Vue3 MediaQuery](https://bestofvue.com/repo/craigrileyuk-vue3-mq#2-plugin-configuration)によると、`mq.current`でリアクティブに情報を取得出来る様です。

computedの関数で、画面サイズ情報を考慮して値を算出し、それを参照する様に変更します。

```vue:template部の表示制御部分
            <v-col md="4" v-if="caredButtonPanelVisible">
              <RotationPanel
                @rotateAction="onRotateAction"
              />
            </v-col>
            <v-col md="8">
              <WasmScreen
                id="wasmelemid"
                :cubeViewType="caredCubeViewType"
                ref="wasm"
                @rotateAction="onRotateAction"
              />
            </v-col>
```

```typescript:setUp部のcomputed処理部分
    const caredButtonPanelVisible = computed((): boolean => {
      return isButtonPanelVisible.value && ['lg', 'xl'].includes(mq.current)
    });
    const caredCubeViewType = computed((): CubeViewType => {
      if (cubeViewType.value == "horizon" && ['xs'].includes(mq.current)) {
        return "vertical"
      }
      return cubeViewType.value;
    });
```

画面が狭くなると、自動でこんな表示になる様に出来ました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/866006/cae685a3-19c4-48d7-01e1-95014c889e83.png)

# 教訓

最近ではPCだけでなく、スマホ、タブレットなど各種デバイスで操作するシーンが想定されます。
画面サイズもそうですが、PCでは便利に使えるホバーイベントがスマホなどではありません。それらを頭に置いたうえで操作性などを考える必要があります。
また、メニュー部分など主要な部分はフレームワークで対応してくれるケースもある事は頭に置いておいたほうが良さそうです。