JavaScriptでナビゲーション以外の場所をクリックしたらナビゲーションを閉じる

JavaScriptでナビゲーション以外の場所をクリックしたらナビゲーションを閉じる

画面右上の丸いトグルボタンで、サイドナビゲーションの表示・非表示を切り替え。
ナビゲーション以外の部分をクリックすると、メニューは自動的に閉じる。
キーボードのESCキーでもメニューを閉じることができる。
classList.toggle() を使いコードを簡潔に。
contains() メソッドでクリック対象がメニュー内かを判定。

クリックでナビを閉じる機能の実装例

<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta name="author" content="X Media" />
    <title>クリック対象外の領域をクリックした時の処理</title>
    <style>
        html {
            --font-color: #7aabd6;
            --hover-color: rgba(36, 97, 151, 0.2);
            --background-color: #246197;
            font-family: 'Courier New', Courier, monospace;
            font-size: 15px;
        }

        *,
        :before,
        :after {
            box-sizing: border-box;
            margin: 0;
            padding: 0;
        }

        ::selection {
            color: var(--background-color);
            background-color: var(--font-color);
        }

        body {
            min-height: 100vh;
        }

        .toggle-btn {
            position: fixed;
            top: 10px;
            right: 10px;
            z-index: 5;
            width: 40px;
            height: 40px;
            cursor: pointer;
            transition: background-color 0.2s linear 0s;
            border-radius: 100em;
        }

        .toggle-btn:hover {
            background-color: var(--hover-color);
        }

        .toggle-btn:before,
        .toggle-btn:after {
            content: "";
            display: block;
            width: 50%;
            height: 3px;
            background-color: var(--background-color);
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -5px);
            transition: transform 0.4s cubic-bezier(.88, .22, .17, .98) 0s,
                background-color 0.4s cubic-bezier(.88, .22, .17, .98) 0s;
        }

        .toggle-btn:after {
            transform: translate(-50%, 2px);
        }

        .mobile-navigation {
            color: var(--font-color);
            width: 200px;
            height: 100%;
            position: fixed;
            top: 0;
            right: 0;
            background-color: var(--background-color);
            z-index: 4;
            padding: 100px 20px 30px;
            text-align: center;
            transform: translateX(101%);
            pointer-events: none;
            opacity: 0;
            transition: transform 0.3s cubic-bezier(.56, .15, .35, .99) 0s,
                opacity 0s linear 0.3s,
                pointer-events 0s linear 0.3s;
        }

        /* メニュー表示時のスタイル */
        .show-mobile-navigation .mobile-navigation {
            transform: translateX(0);
            pointer-events: unset;
            opacity: 1;
            transition: transform 0.5s cubic-bezier(.56, .15, .35, .99) 0s,
                opacity 0s linear 0s,
                pointer-events 0s linear 0s;
        }

        /* トグルボタンの×表示 */
        .show-mobile-navigation .toggle-btn:before,
        .show-mobile-navigation .toggle-btn:after {
            transform: translate(-50%, -50%) rotate(45deg);
            background-color: var(--font-color);
        }

        .show-mobile-navigation .toggle-btn:after {
            transform: translate(-50%, -50%) rotate(-45deg);
        }
    </style>
</head>

<body>
    <div class="toggle-btn"></div>
    <div class="mobile-navigation">ここにモバイルナビゲーションの内容が入ります。</div>

    <script>
        document.addEventListener("DOMContentLoaded", () => {
            const body = document.body;
            const toggleBtn = document.querySelector(".toggle-btn");
            const mobileNav = document.querySelector(".mobile-navigation");

            // トグルボタンがクリックされたときの処理
            toggleBtn.addEventListener("click", (event) => {
                event.stopPropagation();
                // メニューの表示・非表示を切り替える
                body.classList.toggle("show-mobile-navigation");
            });

            // ドキュメント全体がクリックされたときの処理
            document.addEventListener("click", (event) => {
                const target = event.target;
                // メニューが表示されていて、かつクリックがメニュー外かつトグルボタン外ならメニューを閉じる
                if (
                    body.classList.contains("show-mobile-navigation") &&
                    !mobileNav.contains(target) &&
                    !toggleBtn.contains(target)
                ) {
                    body.classList.remove("show-mobile-navigation");
                }
            });

            // ESCキーが押されたときの処理(メニューが表示されていれば閉じる)
            document.addEventListener("keydown", (event) => {
                if (event.key === "Escape" && body.classList.contains("show-mobile-navigation")) {
                    body.classList.remove("show-mobile-navigation");
                }
            });
        });
    </script>
</body>

</html>

コード解説

このコードは、モバイル向けのサイドナビゲーションメニューを実装しています。右上の丸いボタンをクリックすると、右側からナビゲーションがスライドして表示されます。ナビゲーション以外の領域をクリックすると自動でメニューが閉じ、ESCキーでもメニューを閉じることが可能です。

  • HTML:ボタンとナビゲーションの構造を定義。
  • CSS:ボタンやメニューのスタイル、表示・非表示時のアニメーションを制御。
  • JavaScript:ボタンのクリックでメニュー表示切替、画面外クリックでメニュー非表示、ESCキー押下でメニュー閉じる挙動を制御。

注意点・補足

  • ボタンのサイズやクリック可能範囲は適切に設定し、誤操作を防ぎましょう。
  • メニューの中身が多い場合、スクロールや表示パフォーマンスの調整が必要になることがあります。
  • classListcontains() は主要ブラウザで対応済みですが、古いブラウザでは動作確認が必要です。
  • イベントリスナーの解除処理は実装していません。動的に要素を追加・削除する場合は注意が必要です。
  • ESCキーイベントはデフォルト動作を妨げないため、特定のケースでは別のショートカットに影響する可能性があります。

Web制作現場でよくある質問(FAQ)

Q1: なぜボタンクリック時にevent.stopPropagation()を使うのですか?

A1: クリックイベントのバブリングを防ぎ、ボタンをクリックした直後にドキュメントのクリックイベントが発火してメニューがすぐ閉じるのを防止します。

Q2: contains() メソッドの役割は?

A2: クリックされた要素がメニュー内またはボタン内かどうかを判定するために使用しています。

Q3: classList.toggle()を使う利点は?

A3: メニューの表示・非表示の切替処理が簡潔になり、コードの可読性と保守性が向上します。

Q4: ESCキーで閉じる機能はなぜ重要?

A4: キーボード操作での利便性やアクセシビリティ向上のため、視覚障害者などの利用も考慮しています。


まとめ

本コードはシンプルかつ実用的なモバイル向けサイドナビゲーションの実装例です。CSSのトランジションとJavaScriptのイベント管理を組み合わせることで、スムーズで直感的な操作を実現しています。現代のDOM APIを活用し、パフォーマンスと保守性のバランスも良好です。

より高度な機能や複雑なメニュー構造が必要な場合は、この基盤に機能追加する形で拡張が可能です。

関連記事
ユーザーのブラウザに保存されているJavaScript、CSS、画像のキャッシュを削除するにはどうすればいいですか?
ブラウザのキャッシュによる古いJavaScript、CSS、画像の表示を防ぐには、URLにバージョン番号や日時パラメータを付けてリソースを更新する方法が有効です。 毎回タイムスタンプを使うとキャッシュ効果がなくなるため、手動や適切なタイミングでバージョン番号を管理するのがおすすめです。
Details
Details
2ヶ月前
ユーザーのブラウザに保存されているJavaScript、CSS、画像のキャッシュを削除するにはどうすればいいですか?
ページ遷移の一般的な方法
本記事では、Webページにおけるページ遷移(リダイレクト)の一般的な実装方法について紹介しています。JavaScript、HTMLのメタタグ、PHP、ASPを用いたリダイレクト方法をそれぞれ具体的なコード例と共に解説しています。
Details
Details
2ヶ月前
ページ遷移の一般的な方法
【SEO基礎】rel=”nofollow”とは?リンクの評価をコントロールするHTML属性を解説
SEO対策に欠かせない「rel="nofollow"」の意味と使い方を解説。広告やユーザー投稿に適切なrel属性を設定し、検索エンジンとの正しい関係を築きましょう。
qqplus
qqplus
1ヶ月前
【SEO基礎】rel=”nofollow”とは?リンクの評価をコントロールするHTML属性を解説
SCSSの @for ループ(ループ文)
SCSSの @for ループ
無敵の小さな蚊
無敵の小さな蚊
2ヶ月前
SCSSの @for ループ(ループ文)
inputのプレースホルダー(placeholder)の色を変更する
プレースホルダーの色は::placeholder疑似要素で変更可能 IEやEdgeなどの古いブラウザ向けに:-ms-input-placeholder、::-ms-input-placeholderを併用 Firefoxでは透明度の調整のためopacity指定が推奨される
Details
Details
2ヶ月前
inputのプレースホルダー(placeholder)の色を変更する
!important なしでも大丈夫!CSS @layer でスタイル管理をもっと簡単に
CSS の !important は便利ですが、乱用すると保守が難しくなります。そこで注目されているのが新機能 @layer。この記事では、@layer を使って優先度を整理する方法を実装例とともに紹介します。
Details
Details
8日前
!important なしでも大丈夫!CSS @layer でスタイル管理をもっと簡単に
画像の遅延読み込みはこう変わる!2025年最新版・Lazy LoadとIntersectionObserverの最適解
2025年最新の画像遅延読み込み(Lazy Load)完全ガイド。ネイティブloading="lazy"とIntersectionObserverの使い分け・実装例・SEOやLCPへの最新対応ポイントまで徹底解説。パフォーマンスと検索順位アップのための必読ノウハウ!
aki0o0
aki0o0
2ヶ月前
画像の遅延読み込みはこう変わる!2025年最新版・Lazy LoadとIntersectionObserverの最適解
CSSで等間隔配置を実現:1行に複数または複数行に配置
CSSでは、特にレスポンシブデザインにおいて、複数の要素を等間隔に配置する必要があります。列数や間隔を動的に制御することで、柔軟なレイアウトが実現可能です。この記事では、FlexboxとGridの2つの方法を使って、1行に複数のアイテムを等間隔に配置する方法を紹介します。コード内の変数(列数や間隔)を調整することで、1行に表示するアイテム数やその間隔を簡単に管理でき、レイアウト調整が容易になり、開発効率が大幅に向上します。
無敵の小さな蚊
無敵の小さな蚊
1ヶ月前
CSSで等間隔配置を実現:1行に複数または複数行に配置
フッターのJavaScriptは画像読み込みに影響する?ブラウザの読み込み順と最適化の基本
はじめに Webサイトの表示速度や操作感は、単にコンテンツの量や見た目だけでなく、ブラウザがリソースをどのように読み込むかによっても大きく左右されます。特にJavaScriptや画像の読み込み順序、そしてそれらがどのように相互作用しているか […]
解構人
解構人
1ヶ月前
フッターのJavaScriptは画像読み込みに影響する?ブラウザの読み込み順と最適化の基本
相対パスと絶対パスの使い方
相対パスは、モジュールやディレクトリ内のローカルな参照に適しており、柔軟性があります。一方、絶対パスはウェブサイト全体で共通のリソース(CSS、画像、CDNなど)を参照する際に便利です。両者を理解して使い分けることで、パスのエラーや読み込み不具合を防げます。
Details
Details
2ヶ月前
相対パスと絶対パスの使い方
【CSSテクニック】:not()セレクタで階層を跨いだ除外を実現
:not()疑似クラスはCSSでの除外指定に便利ですが、親要素や階層関係に対しては思わぬ制約があります。本記事では、WordPressの投稿構造を例に、インラインコードとコードブロックを正確に区別するためのセレクタ設計を解説します。
無敵の小さな蚊
無敵の小さな蚊
1ヶ月前
【CSSテクニック】:not()セレクタで階層を跨いだ除外を実現
IT業界の初心者が知っておくべきの用語(2025版)
新しいテクノロジーについて話すとき、時々自分がみんなのペースについていけないと感じることはありませんか?あるいは、その業界にいるのに、自分はまだ十分ではないと感じ、多くの専門用語を理解しなければならないこともあるでしょう。
Details
Details
1ヶ月前
IT業界の初心者が知っておくべきの用語(2025版)
jQuery Sortableがスマホなどのタッチスクリーンで使えない場合の対処法
モバイル端末でのクリックイベントとの競合を避けるため、タップ操作には click より touchstart を使うことを推奨。 jQuery UI のバージョンによっては、内部構造が異なる場合があるため、導入時はテストを行うこと。
Details
Details
2ヶ月前
jQuery Sortableがスマホなどのタッチスクリーンで使えない場合の対処法
和暦から西暦変換ツール
このツールは、日本の元号(和暦)と西暦を相互に変換できるシンプルな変換ツールです。「昭和64年は何年?」「2025年は令和何年?」といった場面で、すぐに答えがわかります。 履歴書の作成、行政手続き、年齢計算、歴史的資料の読み解きなどにご活用 […]
aki0o0
aki0o0
1ヶ月前
和暦から西暦変換ツール
SEOは聞いたことありますよね?ではGEOってご存知ですか?|生成エンジン最適化(GEO)の重要性や必要なことをまとめました!
SEOは知ってるけどGEOは初耳?AI時代に欠かせない「生成エンジン最適化(GEO)」の意味・必要性・対策ポイントを初心者向けにやさしく解説!
WASABI
WASABI
28日前
SEOは聞いたことありますよね?ではGEOってご存知ですか?|生成エンジン最適化(GEO)の重要性や必要なことをまとめました!