📚
HTMLのdialogタグを使ってみた
dialogのユーザーエージェントスタイル
これはChromeの例。
dialog {
display: none;
position: absolute;
inset-inline-start: 0px;
inset-inline-end: 0px;
width: fit-content;
height: fit-content;
background-color: canvas;
color: canvastext;
margin: auto;
border-width: initial;
border-style: solid;
border-color: initial;
border-image: initial;
padding: 1em;
}
特に気を付ける必要があるのは、position:absolute
とmargin:auto
。
スタイルがうまく当たらない、JSで思ったように操作できない(後述)ときはデフォルトのスタイルを疑おう(戒め)。
dialogは2種類ある
showModal()
で表示したときと、show()
やopen属性
で表示したときで要素の扱いが変わるらしい。
show()やopenで表示した場合
特に特別な仕様はない。
showModal()で表示した場合
-
Escapeキー
でモーダルを閉じることができる。 -
::backdrop
というCSS疑似要素が使える。 -
最上位レイヤー(top layer)
に表示される。 - 3によって、最後に開かれたモーダル以外は操作不能になる。
最上位レイヤーについてはcolissで。
JavaScriptで操作したい
個人的に困ったのが、ESCで閉じられたときにイベントを発火する方法。
モーダルが閉じるときには方法にかかわらずclose
イベントが発行されるため、モーダルを開いたときに「closeイベントが発火したときの処理」を書いておくことでボタンを押されたときと同一の処理ができる。
モーダルを開く
function openModal(target) {
let _element = document.getElementById(target);
_element.showModal();// モーダルを表示
// モーダルのid名で処理を分岐する
if (target === "dialog-help") {
_element.addEventListener("close", () => {
// モーダルが閉じられたときの処理
});
}
}
モーダルを閉じる
// ボタンが押されたときにモーダルを閉じる
function closeModal(e) {
e.target.offsetParent.close();
}
ドラッグしてみる
参考記事
実践
ドラッグするために下記のようなdialogを用意した。
当然、青色の部分を掴んでドラッグすると移動するようにしたい。
HTML
<dialog id="dialog-help" class="dialog">
<!-- 青色の部分(dialog_header) -->
<div class="dialog_header" draggable="true">
<div class="dialog_header--icon">?</div>
<h2 class="dialog_header--ttl">ヘルプ</h2>
<button class="dialog_header--close" @click="closeModal">close</button>
</div>
<!-- ダイアログの内容 -->
<div class="dialog_content">
<h3 class="dialog_content--ttl u-mb10">U+1F4BB</h3>
<div class="dialog_content--img"><img src="./../img/logo.png" alt="" width="40" height="40" /></div>
<button v-if="!_playing.playing" class="dialog_btn -submit" autofocus="autofocus" @click="closeModal">はじめる</button>
<button v-else class="dialog_btn -submit" @click="endPlaying">やめる</button>
<button class="dialog_btn" @click="openModal('dialog-play')">あそびかた</button>
<button class="dialog_btn" @click="openModal('dialog-about')">このサイトについて</button>
</div>
</dialog>
JavaScript
function draggableModal() {
// https://qiita.com/kotazuck/items/0465250d6e026983fa50
const Modals = document.getElementsByTagName("dialog");
[...Modals].forEach((element) => {
let _target = element.getElementsByClassName("dialog_header")[0];
let _mouse_in_modal = { x: 0, y: 0 }; // モーダルのどこをつかんで移動を開始したか保存する用
function MouseDown(evt) {
// mouseのとき || touchのとき
let _pointerX = evt.clientX || evt.touches[0].clientX;
let _pointerY = evt.clientY || evt.touches[0].clientY;
_mouse_in_modal.y = element.offsetTop - _pointerY;
_mouse_in_modal.x = element.offsetLeft - _pointerX;
if (evt.dataTransfer) {
evt.dataTransfer.setDragImage(document.createElement("div"), 0, 0);
}
}
function MouseMove(evt) {
if (evt.x === 0 && evt.y === 0) return;
let _pointerX = evt.clientX || evt.touches[0].clientX;
let _pointerY = evt.clientY || evt.touches[0].clientY;
const top = _pointerY + _mouse_in_modal.y;
const left = _pointerX + _mouse_in_modal.x;
element.style.margin = `0px`; // 1.座標を画面左上基準にする
// 2.モーダルが画面外にはみ出さないように制限
element.style.top = `${Math.min(Math.max(top, 0), window.innerHeight - element.clientHeight)}px`;
element.style.left = `${Math.min(Math.max(left, 0), window.innerWidth - element.clientWidth)}px`;
}
function MouseUp() {
_mouse_in_modal = {
x: 0,
y: 0,
};
}
// 3.スマホ対応
_target.addEventListener("pointerdown", (evt) => {
// pointerEventでポインターの種類を確認
if (evt.pointerType != "touch") {
_target.addEventListener("dragstart", MouseDown);
_target.addEventListener("drag", MouseMove);
_target.addEventListener("dragend", MouseUp);
} else {
// touchのとき
_target.addEventListener("touchstart", MouseDown);
_target.addEventListener("touchmove", MouseMove);
_target.addEventListener("touchend", MouseUp);
}
});
_target.addEventListener("pointerup", (evt) => {
_target.removeEventListener("touchmove", MouseMove);
});
});
}
余談
①モーダルにmargin:auto
がかかっているとはいざ知らず、座標が合わなくて苦労した。
初期表示では画面中央に表示されてほしいから、ドラッグされたときに初めてマージンを0にすることにした。
②スマホで操作する際にPCほど精密な動作ができないだろうと思い、「モーダルが画面外にはみ出さない」というセーフティを設けてみた。
③DragEventはスマホのブラウザが対応していないため、TouchEventを使用する。
PointerEventはマウスとタッチ両方に対応していると書いてあるけど、moveイベントがうまく取得できなかったため、タイプを判別する部分のみの採用になった。
Discussion