Skip to content

🎯 useClickAway - クリック外部検知の神器

🌟 紹介

ドロップダウンメニューやポップアップコンポーネントを開発しているとき、ユーザーがメニューの外部をクリックしたら自動的にメニューを閉じたい場合があります。それが useClickAway の活躍の場です!✨

この便利な Hook は"要素の外部をクリック"するイベントを専門的に検知する親切なアシスタントのようなものです。ドロップダウンメニュー、ポップアップ、サイドバー、または「外部をクリックして閉じる」機能が必要なあらゆるコンポーネントに対して、簡単に対応できます!

🎯 主な機能:

  • 🔍 インテリジェント検知 - クリックがターゲット要素の外部で発生したかどうかを正確に識別
  • 🎨 柔軟な設定 - カスタムイベントタイプ(click、touchstart など)をサポート
  • 📱 マルチデバイス対応 - PC とモバイル端末のインタラクションを完全にサポート
  • 🚀 パフォーマンス最適化 - イベントのバインドとアンバインドを自動管理し、メモリリークを防止

🚀 コードデモ

🎯 基本的な使い方 - 外部クリックで閉じる機能の実装

最も一般的なシナリオ:メニューの外部をクリックしたときにメニューを閉じる

html
<template>
  <div class="demo-container">
    <!-- 🎨 これは検知対象の要素です -->
    <div 
      ref="menuRef" 
      class="dropdown-menu"
      :class="{ active: isMenuOpen }"
      @click="toggleMenu"
    >
      <span>{{ isMenuOpen ? '📂 メニューが開いています' : '📁 クリックしてメニューを開く' }}</span>
      <div v-if="isMenuOpen" class="menu-content">
        <div class="menu-item">🏠 ホーム</div>
        <div class="menu-item">📊 データ</div>
        <div class="menu-item">⚙️ 設定</div>
      </div>
    </div>
    
    <p class="tip">💡 メニューの外側の任意の場所をクリックしてみてください、メニューが自動的に閉じます!</p>
  </div>
  </template>
js
import { ref } from 'vue';
import { useClickAway } from '@vant/use';

export default {
  setup() {
    // メニュー要素の参照
    const menuRef = ref();
    // メニューの開閉状態
    const isMenuOpen = ref(false);
    
    // 🎯 マジックモーメント:メニューの外部クリックを検知
    useClickAway(menuRef, () => {
      if (isMenuOpen.value) {
        isMenuOpen.value = false;
        console.log('🎯 外部クリックを検知、メニューを閉じました!');
      }
    });
    
    const toggleMenu = () => {
      isMenuOpen.value = !isMenuOpen.value;
      console.log(`📂 メニューが${isMenuOpen.value ? '開いた' : '閉じた'}!`);
    };

    return { 
      menuRef, 
      isMenuOpen, 
      toggleMenu 
    };
  },
};

📱 モバイル端末対応 - カスタムタッチイベント

モバイル端末では、クリックイベントよりもタッチイベントを検知したい場合があります:

html
<template>
  <div class="mobile-demo">
    <div 
      ref="sidebarRef" 
      class="mobile-sidebar"
      :class="{ open: isSidebarOpen }"
    >
      <div class="sidebar-header">
        <h3>📱 モバイルサイドバー</h3>
        <button @click="closeSidebar">❌</button>
      </div>
      <div class="sidebar-content">
        <div class="nav-item">🏠 ホーム</div>
        <div class="nav-item">👤 マイページ</div>
        <div class="nav-item">📋 注文リスト</div>
      </div>
    </div>
    
    <button @click="openSidebar" class="open-btn">
      📱 サイドバーを開く
    </button>
  </div>
</template>
js
import { ref } from 'vue';
import { useClickAway } from '@vant/use';

export default {
  setup() {
    // サイドバー要素の参照
    const sidebarRef = ref();
    // サイドバーの開閉状態
    const isSidebarOpen = ref(false);
    
    // 🎯 モバイル端末最適化:タッチ開始イベントを検知
    useClickAway(
      sidebarRef,
      () => {
        if (isSidebarOpen.value) {
          isSidebarOpen.value = false;
          console.log('📱 外部タッチを検知、サイドバーを閉じました!');
        }
      },
      { 
        eventName: 'touchstart' // 🔧 カスタムイベントタイプ
      }
    );
    
    const openSidebar = () => {
      isSidebarOpen.value = true;
      console.log('📱 サイドバーが開きました!');
    };
    
    const closeSidebar = () => {
      isSidebarOpen.value = false;
      console.log('📱 サイドバーが閉じました!');
    };

    return { 
      sidebarRef, 
      isSidebarOpen, 
      openSidebar, 
      closeSidebar 
    };
  },
};

🎨 複数要素の検知 - 複雑なシナリオにも簡単対応

複数の要素を同時に検知する必要がある場合もあります、例えば複雑なポップアップコンポーネントなど:

html
<template>
  <div class="complex-demo">
    <!-- 🎯 メインモーダル -->
    <div ref="modalRef" v-if="isModalOpen" class="modal">
      <div class="modal-content">
        <h3>🎨 複雑なモーダルの例</h3>
        <p>このモーダルには複数のインタラクティブ要素が含まれています</p>
        
        <!-- 🎯 内部のドロップダウンメニュー -->
        <div ref="dropdownRef" class="inline-dropdown">
          <button @click="toggleDropdown">
            {{ isDropdownOpen ? '📂' : '📁' }} オプションを選択
          </button>
          <div v-if="isDropdownOpen" class="dropdown-options">
            <div class="option">🎯 オプション 1</div>
            <div class="option">🎨 オプション 2</div>
            <div class="option">🚀 オプション 3</div>
          </div>
        </div>
        
        <button @click="closeModal" class="close-btn">モーダルを閉じる</button>
      </div>
    </div>
    
    <button @click="openModal" class="open-modal-btn">
      🎨 複雑なモーダルを開く
    </button>
  </div>
</template>
js
import { ref } from 'vue';
import { useClickAway } from '@vant/use';

export default {
  setup() {
    // モーダル要素の参照
    const modalRef = ref();
    // ドロップダウンメニュー要素の参照
    const dropdownRef = ref();
    // モーダルの開閉状態
    const isModalOpen = ref(false);
    // ドロップダウンメニューの開閉状態
    const isDropdownOpen = ref(false);
    
    // 🎯 モーダル外部のクリックを検知
    useClickAway(modalRef, () => {
      if (isModalOpen.value) {
        isModalOpen.value = false;
        isDropdownOpen.value = false; // 内部のドロップダウンメニューも同時に閉じる
        console.log('🎨 モーダル外部をクリック、モーダルを閉じました!');
      }
    });
    
    // 🎯 ドロップダウンメニュー外部のクリックを検知(ただしモーダル外部は除く)
    useClickAway(dropdownRef, () => {
      if (isDropdownOpen.value) {
        isDropdownOpen.value = false;
        console.log('📂 ドロップダウンメニュー外部をクリック、メニューを閉じました!');
      }
    });
    
    const openModal = () => {
      isModalOpen.value = true;
      console.log('🎨 モーダルが開きました!');
    };
    
    const closeModal = () => {
      isModalOpen.value = false;
      isDropdownOpen.value = false;
      console.log('🎨 モーダルが閉じました!');
    };
    
    const toggleDropdown = () => {
      isDropdownOpen.value = !isDropdownOpen.value;
      console.log(`📂 ドロップダウンメニューが${isDropdownOpen.value ? '開いた' : '閉じた'}!`);
    };

    return { 
      modalRef,
      dropdownRef,
      isModalOpen, 
      isDropdownOpen,
      openModal, 
      closeModal,
      toggleDropdown
    };
  },
};

📚 API リファレンス

🔧 型定義

ts
type Options = {
  eventName?: string; // 🎯 カスタムイベントタイプ
};

function useClickAway(
  target:
    | Element                                    // 🎯 単一の DOM 要素
    | Ref<Element | undefined>                   // 🎯 Vue ref でラップされた要素
    | Array<Element | Ref<Element | undefined>>, // 🎯 複数要素の配列
  listener: EventListener,                       // 🎯 コールバック関数
  options?: Options,                            // 🎯 オプション設定
): void;

📋 パラメータの説明

パラメータ説明タイプデフォルト値
target🎯 検知対象の要素
💡 単一要素、ref、または要素配列をサポート
Element | Ref<Element> | Array<Element | Ref<Element>>-
listener🎯 外部をクリックしたときのコールバック関数
💡 ここで閉じるロジックを処理する
EventListener-
options🔧 オプション設定Options下表を参照

⚙️ Options 設定

パラメータ説明タイプデフォルト値
eventName🎯 検知するイベントタイプ
💡 よく使われる:clicktouchstartmousedown
stringclick

🎯 実際の使用シナリオ

🎨 シナリオ1:ドロップダウンメニュー

js
// 🎯 様々なドロップダウンメニューコンポーネントに最適
useClickAway(menuRef, () => {
  closeMenu(); // 外部をクリックしてメニューを閉じる
});

🎨 シナリオ2:モーダルコンポーネント

js
// 🎯 モーダルにマスククリックで閉じる機能を追加
useClickAway(modalContentRef, () => {
  closeModal(); // コンテンツ領域の外部をクリックしてモーダルを閉じる
});

🎨 シナリオ3:検索候補

js
// 🎯 検索ボックスがフォーカスを失ったときに候補リストを非表示にする
useClickAway(searchContainerRef, () => {
  hideSuggestions(); // 検索領域の外部をクリックして候補を非表示にする
});

🎨 シナリオ4:モバイルサイドバー

js
// 🎯 モバイルサイドバーのタッチでの閉じる機能
useClickAway(sidebarRef, closeSidebar, {
  eventName: 'touchstart' // タッチイベントを使用
});

💡 ベストプラクティス

✅ 推奨されるアプローチ

  1. 🎯 条件判断の適切な使用

    js
    useClickAway(elementRef, () => {
      // ✅ 必要な場合のみ閉じるロジックを実行
      if (isOpen.value) {
        isOpen.value = false;
      }
    });
  2. 📱 モバイル端末の最適化

    js
    // ✅ モバイルでは touchstart イベントを使用してレスポンスを速くする
    useClickAway(elementRef, closeHandler, {
      eventName: 'touchstart'
    });
  3. 🎨 多層コンポーネントの処理

    js
    // ✅ 異なる階層の外部クリックロジックを個別に処理
    useClickAway(parentRef, closeParent);
    useClickAway(childRef, closeChild);

❌ 避けるべきアプローチ

  1. 🚫 条件判断を忘れる

    js
    // ❌ 不要な状態更新を引き起こす可能性がある
    useClickAway(elementRef, () => {
      isOpen.value = false; // false であってもトリガーされる
    });
  2. 🚫 間違ったタイミングでのバインド

    js
    // ❌ 要素がマウントされる前にバインドしないでください
    // ref に値があることを確認する必要があります

📚 関連文書

🎯 コンポジション API

🎨 UI コンポーネント

🔧 開発ガイド

💡 デザインパターン

Vant に基づく企業向けモバイルソリューション