⚒️

Vue3 / Nuxt3で可変式サイドバーを実装した件

2024/11/11に公開

インターンでサイドバーが可変できるようにするコードを書いたので、その実装内容とコードを載せておきます。

動作のイメージ

実際の動作イメージは、このような感じになりました。
サイドバーとメインコンテンツの境界付近にマウスを持ってくると、リサイズハンドルが出現、マウスカーソルや境界の色が変化するようにしています。

環境

NuxtとTailwind CSSを利用しています。

完成系

<script setup lang="ts">
const defalutSidebarWidth = 288 // px
const maxSidebarWidth = 600 //px
const cookieSidebarWidth = useCookie("sidebarWidth")
const sidebarWidth = ref(Number(cookieSidebarWidth.value) || defalutSidebarWidth)
const isDragging = ref(false)
const dragStart = (e: MouseEvent) => {
  isDragging.value = true
  const startX = e.clientX
  const startWidth = sidebarWidth.value
  // ドラッグ中はユーザー選択を無効化
  document.body.style.userSelect = 'none'
  const onMouseMove = (e: MouseEvent) => {
    if (isDragging.value) {
      const newWidth = startWidth + (e.clientX - startX)
      sidebarWidth.value = Math.max(defalutSidebarWidth, Math.min(maxSidebarWidth, newWidth))
      cookieSidebarWidth.value = String(sidebarWidth.value)
    }
  }
  const onMouseUp = () => {
    isDragging.value = false
    document.body.style.userSelect = ''  // ドラッグ終了時にユーザー選択を再度有効化
    document.removeEventListener('mousemove', onMouseMove)
    document.removeEventListener('mouseup', onMouseUp)
  }
  document.addEventListener('mousemove', onMouseMove)
  document.addEventListener('mouseup', onMouseUp)
}
</script>

<template>
    <div name="sidebar" :style="{ width: sidebarWidth + 'px'}">
        <!-- サイドバーの中身 -->
    </div>

    <!-- リサイズハンドル -->
    <div class="w-1 cursor-col-resize hover:bg-gray-300 active:bg-gray-400" @mousedown="dragStart($event)" />
</template>

実装

実装した内容自体はそこまで複雑ではなく、

  1. マウスが押されたときのx座標を取得し、isDraggingTrueにする。
  2. isDraggingTrueかつマウスが移動したら、移動先の新たなx座標を取得
  3. 移動前と移動先のx座標の差を計算し、移動したx座標分を移動
  4. 最後に、isDraggingFalseに、イベントリスナーを削除
    しています。

個人でUnityを触っていたので、なんとなくUnityでマウスの実装を書くときとかなり似ている書き方になっています。

ちょっとひと工夫

実装内容のちょっとしたひと工夫を2つほど、紹介したいと思います。

ドラッグ動作が発生するならば、ユーザー選択を無効化すべし

これUnityでは一切気にしていなかった点で、実装してみて初めて気づいたのですが、
ユーザー選択を無効化しないと、サイドバーのサイズを動かしているときに、他の要素を選択してしまう
というWebならではの動作がありました。

確かに、考えてみれば「うん、そうだわな...」となりますが、実装してみるまで気づかないものですね。
ユーザーアクセシビリティに直結すると思ったので、ドラッグ中はユーザー選択を無効化する処理を追加しています。ちょっとした動作の違いでも、操作感だったり、使い心地だったりが変化してきますが、それが一番顕著に出た実装内容だったなと後から振り返ってみて思いました。

変数にはデフォ値を必ず設定すべし

あと、これも当たり前と言えば当たり前ではありますが、変数に対するデフォ値を必ず設定するという重要性に気づきました。今回、Webページを閉じても、Cookie上にサイドバーの値を保持するような実装になっています。最初はCookieに値が存在しない場合の処理が抜けており、sidebarWidthnullになってしまうという問題が発生しました。なので、Cookieに値が存在しない場合は、デフォ値を読みにいくようになっています。

リバナレテックブログ

Discussion