スワイプ・ドラッグで閉じられるモーダルウインドウを作る方法
スワイプ・ドラッグで閉じられるモーダルウインドウを作る方法を紹介します。このサンプルでは、モーダルウインドウを上下左右のいずれかへスワイプまたはドラッグし、一定の距離まで移動させると、その方向へモーダルが移動して閉じる仕組みをHTML・CSS・JavaScriptで実装します。移動距離が閉じるための基準に届かなかった場合は、モーダルが元の位置へ戻ります。
スマートフォンやタブレットでは指によるスワイプ、PCではマウスによるドラッグで操作できます。上下左右の全方向に対応しているため、利用者が操作しやすい方向へ動かして閉じられます。会員サイトのお知らせ、ECサイトの商品案内、管理画面の確認表示、Webアプリの補足情報など、タッチ操作とマウス操作の両方を取り入れたい画面で活用できます。
HTMLではモーダル内のタイトルや説明文を変更でき、CSSでは背景色、横幅、余白、角丸、影などのデザインを調整できます。JavaScriptでは上下左右の移動量を取得し、一定距離を超えたかを判定しています。閉じるために必要な移動距離や、元の位置へ戻る時間、閉じるときの移動時間もコード内の数値から変更できるため、用途に合わせてカスタマイズできます。
コードについて
本記事のコードはサンプルコードです。ご利用前に必ず動作確認を行ってください。
免責事項
本コードの利用により発生した損害について、当サイトは一切の責任を負いません。
デモ
スワイプ・ドラッグで閉じるモーダルウインドウ
モーダルを上下左右の好きな方向へスワイプ・ドラッグすると閉じます。
お知らせ
上下左右の好きな方向へ
スワイプ・ドラッグすると閉じます。
モーダルを上下左右の好きな方向へスワイプ・ドラッグし、一定距離まで移動させると閉じます。
コードをコピーして使おう!
<!DOCTYPE html>
<html lang="ja">
<head>
<!-- 文字コード -->
<meta charset="UTF-8">
<!-- スマートフォン対応 -->
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- ページタイトル -->
<title>スワイプ・ドラッグで閉じられるモーダルウインドウ</title>
<!-- CSSファイル -->
<link rel="stylesheet" href="style.css">
</head>
<body>
<!-- 背景エリア -->
<main class="swipe-drag-close-modal_page">
<!-- タイトル -->
<div class="swipe-drag-close-modal_page-title">
スワイプ・ドラッグで閉じるモーダルウインドウ
</div>
<!-- 説明文 -->
<p class="swipe-drag-close-modal_page-text">
モーダルを上下左右の好きな方向へスワイプ・ドラッグすると閉じます。
</p>
</main>
<!-- モーダル全体 -->
<div class="swipe-drag-close-modal_overlay">
<!-- モーダル本体 -->
<div
class="swipe-drag-close-modal_modal"
role="dialog"
aria-modal="true"
aria-labelledby="modal-title"
aria-describedby="modal-text">
<!-- タイトル -->
<div
class="swipe-drag-close-modal_modal-title"
id="modal-title">
お知らせ
</div>
<!-- 説明文 -->
<p
class="swipe-drag-close-modal_modal-text"
id="modal-text">
上下左右の好きな方向へ<br>
スワイプ・ドラッグすると閉じます。
</p>
</div>
</div>
<!-- JavaScriptファイル -->
<script src="script.js"></script>
</body>
</html>
/* すべての要素 */
*{
box-sizing:border-box; /* paddingとborderを要素の横幅・高さへ含める */
}
/* ページ全体 */
body{
margin:0; /* ブラウザ標準の外側余白を削除 */
padding:40px 20px; /* 上下40px・左右20px。値を大きくすると画面端との間隔が広がる */
background:#ffffff; /* ページ全体の背景色 */
color:#111827; /* ページ全体の基本文字色 */
font-family:sans-serif; /* ページ全体で使用するフォント */
}
/* 背景エリア */
.swipe-drag-close-modal_page{
width:100%; /* 画面幅に合わせて横幅を縮小 */
max-width:700px; /* 最大横幅。値を大きくすると全体が横に広がる */
margin:40px auto; /* 上下40pxの余白を付けて左右中央へ配置 */
padding:32px; /* 内側の余白。値を大きくすると余白が広がる */
border:1px solid #e5e7eb; /* 背景エリアを囲む枠線 */
border-radius:18px; /* 角丸。値を大きくすると丸みが強くなる */
background:#f8fafc; /* 背景エリアの背景色 */
text-align:center; /* タイトルと説明文を中央揃えにする */
}
/* 背景エリアのタイトル */
.swipe-drag-close-modal_page-title{
margin:0 0 12px; /* 下側12pxの余白。値を大きくすると説明文との間隔が広がる */
font-size:22px; /* タイトルの文字サイズ。値を大きくすると文字が大きくなる */
font-weight:700; /* タイトルを太字にする */
}
/* 背景エリアの説明文 */
.swipe-drag-close-modal_page-text{
margin:0; /* ブラウザ標準の外側余白を削除 */
color:#4b5563; /* 説明文の文字色 */
line-height:1.8; /* 説明文の行間。値を大きくすると行間が広がる */
}
/* モーダル全体 */
.swipe-drag-close-modal_overlay{
position:fixed; /* 画面全体へ固定する */
inset:0; /* 上下左右を0にして画面全体へ広げる */
z-index:9999; /* ページ内のほかの要素より前面へ表示 */
display:flex; /* モーダルを中央へ配置する */
align-items:center; /* 上下中央へ配置 */
justify-content:center; /* 左右中央へ配置 */
padding:20px; /* 画面端との間隔。値を大きくすると余白が広がる */
overflow:hidden; /* 移動したモーダルが画面外にはみ出した部分を隠す */
background:rgba(15,23,42,.55); /* 背景を半透明の暗い色にする */
}
/* 閉じたモーダル全体 */
.swipe-drag-close-modal_overlay.is-hidden{
display:none; /* モーダル全体を非表示にする */
}
/* モーダル本体 */
.swipe-drag-close-modal_modal{
width:100%; /* 画面幅に合わせて横幅を縮小 */
max-width:420px; /* 最大横幅。値を大きくするとモーダルが横に広がる */
padding:28px; /* 内側の余白。値を大きくすると内容との間隔が広がる */
border-radius:18px; /* モーダルの角丸。値を大きくすると丸みが強くなる */
background:#ffffff; /* モーダルの背景色 */
box-shadow:0 20px 40px rgba(0,0,0,.2); /* 下方向20px・ぼかし40pxの影 */
cursor:grab; /* ドラッグできることを示すカーソル */
touch-action:none; /* スワイプ中のブラウザ標準操作を無効にする */
user-select:none; /* ドラッグ中の文字選択を防ぐ */
transition:transform .25s ease;
/*
0.25秒でモーダルを元の位置または画面外へ移動する。
値を大きくすると移動が遅くなり、小さくすると速くなる。
*/
}
/* スワイプ・ドラッグ中 */
.swipe-drag-close-modal_modal.is-dragging{
cursor:grabbing; /* ドラッグ中のカーソルへ変更 */
transition:none; /* 指やマウスの動きへ遅れず追従させる */
}
/* モーダルのタイトル */
.swipe-drag-close-modal_modal-title{
margin:0 0 12px; /* 下側12pxの余白。値を大きくすると説明文との間隔が広がる */
font-size:22px; /* タイトルの文字サイズ。値を大きくすると文字が大きくなる */
font-weight:700; /* タイトルを太字にする */
}
/* モーダルの説明文 */
.swipe-drag-close-modal_modal-text{
margin:0; /* ブラウザ標準の外側余白を削除 */
color:#4b5563; /* 説明文の文字色 */
line-height:1.8; /* 説明文の行間。値を大きくすると行間が広がる */
}
/* 画面幅640px以下 */
@media (max-width:640px){
/* ページ全体 */
body{
padding:24px 16px; /* スマートフォンでは画面端の余白を小さくする */
}
/* 背景エリア */
.swipe-drag-close-modal_page{
padding:24px 18px; /* スマートフォンでは内側の余白を小さくする */
}
/* モーダル本体 */
.swipe-drag-close-modal_modal{
padding:24px 20px; /* スマートフォンでは内側の余白を小さくする */
}
}
/* モーダル全体を取得 */
const swipeDragCloseModalOverlay=document.querySelector(
".swipe-drag-close-modal_overlay"
);
/* モーダル本体を取得 */
const swipeDragCloseModal=document.querySelector(
".swipe-drag-close-modal_modal"
);
/* スワイプ・ドラッグ開始位置 */
let swipeDragCloseModalStartX=0;
let swipeDragCloseModalStartY=0;
/* スワイプ・ドラッグした移動量 */
let swipeDragCloseModalMoveX=0;
let swipeDragCloseModalMoveY=0;
/* スワイプ・ドラッグ中かどうか */
let swipeDragCloseModalIsDragging=false;
/* スワイプ・ドラッグを開始したとき */
swipeDragCloseModal.addEventListener("pointerdown",function(event){
/* スワイプ・ドラッグ中の状態にする */
swipeDragCloseModalIsDragging=true;
/* 開始位置を保存 */
swipeDragCloseModalStartX=event.clientX;
swipeDragCloseModalStartY=event.clientY;
/* 前回の移動量を初期化 */
swipeDragCloseModalMoveX=0;
swipeDragCloseModalMoveY=0;
/* ドラッグ中のクラスを追加 */
swipeDragCloseModal.classList.add("is-dragging");
/* モーダル外へ移動しても操作を継続できるようにする */
swipeDragCloseModal.setPointerCapture(event.pointerId);
});
/* スワイプ・ドラッグ中 */
swipeDragCloseModal.addEventListener("pointermove",function(event){
/* 操作中でなければ処理しない */
if(!swipeDragCloseModalIsDragging){
return;
}
/* 開始位置からの横方向と縦方向の移動量を取得 */
swipeDragCloseModalMoveX=
event.clientX-swipeDragCloseModalStartX;
swipeDragCloseModalMoveY=
event.clientY-swipeDragCloseModalStartY;
/* 指やマウスの位置に合わせてモーダルを移動 */
swipeDragCloseModal.style.transform=
`translate(${swipeDragCloseModalMoveX}px, ${swipeDragCloseModalMoveY}px)`;
});
/* スワイプ・ドラッグを終了したとき */
swipeDragCloseModal.addEventListener("pointerup",function(event){
/* 操作中でなければ処理しない */
if(!swipeDragCloseModalIsDragging){
return;
}
/* スワイプ・ドラッグを終了 */
swipeDragCloseModalIsDragging=false;
/* ドラッグ中のクラスを削除 */
swipeDragCloseModal.classList.remove("is-dragging");
/* ポインターの固定を解除 */
if(swipeDragCloseModal.hasPointerCapture(event.pointerId)){
swipeDragCloseModal.releasePointerCapture(event.pointerId);
}
/* 開始位置から現在位置までの移動距離を計算 */
const swipeDragCloseModalDistance=Math.hypot(
swipeDragCloseModalMoveX,
swipeDragCloseModalMoveY
);
/*
120はモーダルを閉じるために必要な移動距離。
値を大きくすると長く動かす必要があり、
小さくすると短い操作で閉じられる。
*/
if(swipeDragCloseModalDistance>=120){
/* モーダルを移動させる方向を計算 */
const swipeDragCloseModalDirectionX=
swipeDragCloseModalMoveX/swipeDragCloseModalDistance;
const swipeDragCloseModalDirectionY=
swipeDragCloseModalMoveY/swipeDragCloseModalDistance;
/*
1.5は画面外へ移動させる距離の倍率。
値を大きくすると、より遠くまで移動して閉じる。
*/
const swipeDragCloseModalCloseDistance=Math.max(
window.innerWidth,
window.innerHeight
)*1.5;
/* スワイプ・ドラッグした方向へ画面外まで移動 */
swipeDragCloseModal.style.transform=
`translate(
${swipeDragCloseModalDirectionX*swipeDragCloseModalCloseDistance}px,
${swipeDragCloseModalDirectionY*swipeDragCloseModalCloseDistance}px
)`;
/*
250は0.25秒。
CSSのtransition時間が終わったあとに非表示へ切り替える。
CSSの時間を変更する場合は、この値も同じ時間へ変更する。
*/
setTimeout(function(){
/* モーダル全体を非表示にする */
swipeDragCloseModalOverlay.classList.add("is-hidden");
},250);
}else{
/* 移動距離が足りない場合は中央へ戻す */
swipeDragCloseModal.style.transform="translate(0, 0)";
}
/* 移動量を初期化 */
swipeDragCloseModalMoveX=0;
swipeDragCloseModalMoveY=0;
});
/* 操作が途中で中断されたとき */
swipeDragCloseModal.addEventListener("pointercancel",function(){
/* スワイプ・ドラッグを終了 */
swipeDragCloseModalIsDragging=false;
/* 移動量を初期化 */
swipeDragCloseModalMoveX=0;
swipeDragCloseModalMoveY=0;
/* ドラッグ中のクラスを削除 */
swipeDragCloseModal.classList.remove("is-dragging");
/* モーダルを中央へ戻す */
swipeDragCloseModal.style.transform="translate(0, 0)";
});
コードをコピーする場合
使用するコードのタブを選び、「このタブをコピー」をクリックすると、現在表示中のコードをコピーできます。
サンプル一式を取得する場合
HTML・CSSなどすべてのファイルが必要な場合は、「サンプル一式をダウンロード」をクリックしてください。
ファイル構成と設置方法
このサンプルはHTML、CSS、JavaScriptの3ファイルで構成されています。HTMLではページのタイトルや説明文、モーダルウインドウの内容を作成し、CSSではモーダルの横幅、余白、背景色、角丸、影、移動アニメーションなどを設定します。JavaScriptではPointer Eventsを使用し、マウスによるドラッグとスマートフォンやタブレットでのスワイプを共通の処理で判定します。
sample/
├── index.html
├── style.css
└── script.js
各ファイルの役割
index.htmlはページ全体とモーダルウインドウの構成を作るファイルです。背景エリアのタイトルと説明文、モーダル内の「お知らせ」というタイトル、操作方法を案内する文章を変更できます。CSSファイルとJavaScriptファイルも、このファイルから読み込みます。
style.cssはページとモーダルウインドウのデザインを設定するファイルです。背景エリアとモーダルの横幅、余白、背景色、文字色、角丸、影、画面全体を覆う背景色などを変更できます。また、ドラッグ中のカーソル表示や、操作を終了したときにモーダルを元の位置または画面外へ移動するアニメーションも設定しています。
script.jsはスワイプ・ドラッグ操作を処理するファイルです。操作を開始した位置と現在位置を取得し、その差に合わせてモーダルを上下左右へ移動します。操作終了時の移動距離が指定値以上であれば、動かした方向へモーダルを移動して閉じます。移動距離が指定値未満の場合は、モーダルを中央へ戻します。
設置方法・使い方
sampleフォルダへindex.html、style.css、script.jsを配置します。3つのファイルを同じ階層へ保存したあと、sample/index.htmlをブラウザで開いてください。ページを開くと画面中央へモーダルウインドウが表示されます。モーダル本体をマウスでドラッグするか、スマートフォンやタブレットでスワイプしてください。上下左右のいずれかへ一定距離以上動かして指やマウスを離すと、その方向へ移動して閉じます。移動距離が足りない場合は中央へ戻ります。
カスタマイズできる内容
index.htmlでは、背景エリアのタイトルと説明文、モーダル内のタイトルと案内文を変更できます。お知らせ、キャンペーン案内、操作説明、確認メッセージなど、表示したい内容に合わせて文章を編集できます。
style.cssでは、ページ全体とモーダルの背景色、文字色、横幅、余白、角丸、影を変更できます。.swipe-drag-close-modal_overlayのbackgroundを変更すると画面全体を覆う背景色と透明度を調整でき、.swipe-drag-close-modal_modalのmax-widthやpaddingを変更するとモーダルの大きさを調整できます。
script.jsでは、モーダルを閉じるために必要な移動距離と、画面外へ移動させる距離を変更できます。短いスワイプで閉じたい場合や、誤操作を防ぐために長く動かした場合だけ閉じたい場合は、移動距離の数値を調整します。
数値調整
style.css
max-width:700px;
背景エリアの最大横幅です。値を大きくすると横に広がり、小さくするとコンパクトになります。
padding:32px;
背景エリアの内側余白です。値を大きくすると内容と枠線の間隔が広がり、小さくすると狭くなります。
border-radius:18px;
背景エリアとモーダル本体の角丸です。値を大きくすると丸みが強くなり、0にすると四角になります。
padding:20px;
モーダル全体と画面端の間隔です。値を大きくすると画面端から離れ、小さくすると画面端へ近づきます。
max-width:420px;
モーダル本体の最大横幅です。値を大きくするとモーダルが横に広がり、小さくすると細くなります。
padding:28px;
モーダル本体の内側余白です。値を大きくすると文章と外枠の間隔が広がり、小さくすると詰まった表示になります。
box-shadow:0 20px 40px rgba(0,0,0,.2);
モーダル本体の影です。20pxは下方向への位置、40pxはぼかしの大きさです。値を大きくすると影が大きく広がり、小さくすると控えめになります。
transition:transform .25s ease;
モーダルを元の位置または画面外へ移動する時間です。0.25秒で移動します。値を大きくするとゆっくり動き、小さくすると速く動きます。
@media (max-width:640px)
スマートフォン向けのスタイルへ切り替える画面幅です。640px以下になると、ページ全体と各要素の余白が小さくなります。
script.js
swipeDragCloseModalDistance>=120
モーダルを閉じるために必要な移動距離です。120を大きくすると長くスワイプ・ドラッグする必要があり、小さくすると短い操作で閉じられます。
*1.5
モーダルを画面外へ移動させる距離の倍率です。値を大きくすると、スワイプ・ドラッグした方向へさらに遠くまで移動します。
setTimeout(...,250)
モーダル全体を非表示へ切り替えるまでの待機時間です。250は0.25秒を表し、style.cssのtransition:transform .25s ease;と同じ時間です。アニメーション時間を変更する場合は、両方を同じ時間へ変更します。
レスポンシブ対応
このサンプルはレスポンシブ対応です。style.cssのwidth:100%とmax-widthにより、背景エリアとモーダル本体が画面幅に合わせて縮小されます。モーダル全体にはpadding:20pxを設定しているため、画面幅が狭い場合でも画面端との余白を確保できます。また、640px以下ではbody、背景エリア、モーダル本体のpaddingを小さくしているため、スマートフォン・タブレット・PCで利用できます。
コメント