はじめに
私たちを取り巻くWeb技術は、もはや社会的なインフラとしてめまぐるしく進化しています。HTMLやCSSはもちろんのこと、JavaScriptやライブラリ、フレームワークなど、それぞれがニーズにキャッチアップする形で、機能強化を繰り返しています。その中でも、Web技術の中核に位置するにもかかわらず、意外と見過ごされがちなのがHTMLの進化です。本連載は、このHTMLと関連するJavaScript APIにフォーカスして、その新機能を手軽に試していただこうというものです。理解も利用もたやすいHTMLなので、ライトな気持ちで「こんなことができるようになったのか」を感じていただきます。
[NOTE]サンプルについて
本記事の配布サンプルは、以下のURLから入手できます。新機能を試していくので、ブラウザは最新である方がよいでしょう。本記事のサンプルは、執筆時点で最新のGoogle Chromeで動作することを確認しています。
https://github.com/wateryinhare62/mynavi_html
連載第3回の目的
この回では、HTMLから簡単に利用できるインタラクションであるダイアログを紹介します。ダイアログとは、PCやスマホアプリでおなじみの、ユーザーに対して情報を伝えたり、何らかの操作を促すためのコンポーネントです。本記事では、dialog要素によるダイアログのシンプルな実現方法の他、JavaScriptとの連携、スタイルの指定など見た目を調整したりする例を紹介していきます。
ダイアログとは
ダイアログは、アラートの表示、情報表示のためのサブウィンドウ、ユーザーに入力を促すためのダイアログボックスなど、インタラクションの向上のために用いられるコンポーネントです。ダイアログの登場する以前は、JavaScriptによって疑似的にウィンドウを作成し、そこにフォームなどのUI部品を配置して、ダイアログを実現していました。ダイアログの登場により、最低限のコードで同様のことができるようになり、WebアプリのUX(ユーザ体験)が大幅に向上しました。
ダイアログは、HTMLのdialog要素を使って実装します。ダイアログには2種類あり、ダイアログが開いていても他の操作が可能なモードレス(モードなし、非モーダル)、ダイアログが開いている間は他の操作ができないモーダル(モードあり)があります。情報提供には主にモードレスなダイアログが、ユーザの入力を促すならモーダルなダイアログが、それぞれ用いられることが多いようです。
ダイアログはブラウザ間の互換性としてはBaseline Widely availableとなっており(意味については第2回を参照)、現時点でブラウザのサポートや互換性を気にすることなく広く使える機能となっています。
モードレスなダイアログ
最初に、最も基本的な例として、モードレスなダイアログをHTMLだけで開く例を紹介します。モードレスなダイアログは、dialog要素にopen属性を付与して作成します(リスト1)。
リスト1:modeless_dialog.html
<p><label>好きな麺類を教えてください:<br>
<select>
<option value="default">選択してください</option>
<option>ラーメン</option>
<option>そば</option>
<option>うどん</option>
<option>その他</option>
</select>
</label></p>
<dialog open>
<p>モードレスなダイアログ</p>
<form method="dialog">
<button>閉じる</button>
</form>
</dialog>
HTMLをブラウザに読み込ませると、図1上のように既定のスタイルである黒い太枠で囲まれたダイアログが表示されます。モードレスであるので、ダイアログが開いたままでも図1下のように選択リストをクリックして選択が可能です。[閉じる]ボタンのクリックで、ダイアログは閉じます。
ダイアログでは、ユーザに何らかの操作を促すために、通常はdialog要素の内部にform要素を置き、入力コントロールやボタンを配置します。ここではbutton要素を配置しましたが、form要素のmethod="dialog"属性により、ボタンのクリックでダイアログを閉じることができます(button要素にformmethod="dialog"属性を付与しても同様)。この属性がないと、フォームはaction属性で指定される遷移先に送信されてしまい、ダイアログとしての結果が得られません。
ここでは、open属性によるモードレスなダイアログの例を紹介しましたが、この方法ですと最初から開いているダイアログしか使えませんし、いったん閉じたら二度と開くことはできません。任意のタイミングでダイアログを開いたりモーダルなダイアログを使うには、JavaScriptによるAPI呼び出しが必要になります。
モードレスなダイアログをJavaScriptで開く
モードレスなダイアログをJavaScriptから開くには、dialog要素に対してshowメソッドを呼び出します。リスト2は、JavaScriptファイルを読み込んでダイアログを開くHTMLです。
リスト2:modeless_dialog_show.html
…選択リストはリスト1と同じなので省略…
<dialog>
<p>モードレスなダイアログ</p>
<form method="dialog">
<button>閉じる</button>
</form>
</dialog>
<p><button id="showButton">ダイアログを表示する</button></p> (1)
<script src="modeless_dialog_show.js"> </script> (2)
リスト2においては、(1)でダイアログ表示のためのボタンを設置、showメソッドを記述したJavaScriptファイルを(2)で読み込んでいます。この構造は、以降のサンプルでも同様なので覚えておいてください。リスト3は、showメソッドを記述したJavaScriptです。
リスト3:modeless_dialog_show.js
const dialogElement = document.querySelector("dialog");
const showButton = document.querySelector("dialog + button");
showButton.addEventListener("click", () => { (1)
dialogElement.show(); (2)
});
リスト3においては、(1)でダイアログ表示ボタンにclickイベントハンドラを設定し、処理内容として(2)showメソッドをdialog要素に対して呼び出しています。
HTMLをブラウザに読み込ませると、最初はダイアログがありませんが、ボタンクリックでダイアログが表示されます。[閉じる]ボタンのクリックでダイアログが閉じ、ダイアログが開いている間も選択リストの操作が可能なのはリスト1と同様です。
モーダルなダイアログ
ここからは、モーダルなダイアログを紹介していきます。モーダルなダイアログでは、dialog要素に対してshowModalメソッドの呼び出しでダイアログを表示します(リスト4、リスト5)。リスト4の(1)のように、ダイアログが開いた時点でユーザーの操作対象としたい要素にautofocus属性を指定することが推奨されています。
リスト4:modal_dialog.html
…選択リストはリスト1と同じなので省略…
<dialog>
<p>モーダルダイアログの背後にあるものは操作できません!</p>
<form method="dialog">
<button autofocus>閉じる</button> (1)
</form>
</dialog>
<button>ダイアログを表示する</button>
<script src="modal_dialog.js"></script>
リスト5:modal_dialog.js
const dialogElement = document.querySelector("dialog");
const showButton = document.querySelector("dialog + button");
showButton.addEventListener("click", () => {
dialogElement.showModal(); (1)
});
リスト5の(1)においてshowModalメソッドを呼び出すほかは、モードレスなダイアログと変わりません。
ページを表示させてボタンをクリックすると、モーダルなダイアログが開きます。表示されているように、背景にある選択リストは操作できません。既定で背景が暗くなり、ダイアログがモーダルであることが示されます(図3)。
ダイアログ表示時の背景を変更する
モーダルなダイアログでは背景が若干暗くなり、ダイアログがモーダルであることが示されますが、CSSの::backdrop疑似要素を使って背景をカスタマイズできます(リスト6)。HTMLとJavaScriptは、HTMLにおいてCSSファイルの読み込みを行っているほかはこれまでとほぼ同様なので、配布サンプルのdialog_backdrop.htmlファイルとdialog_backdrop.jsファイルを参照してください。
リスト6:dialog_backdrop.css
::backdrop {
opacity: 0.75;
background-color: tomato;
}
この例では、背景色を明るい赤色(tomato)として、透過率を指定するopacityプロパティを指定して背景が少し透けて見えるようにしています(図4)。他のスタイル、例えばlinear-gradientプロパティといったグラデーションのプロパティで、背景を自在に変化させることもできます。
値を返すダイアログ
dialog要素のreturnValue属性を使うと、ダイアログの操作結果を受け取ることができます。このとき、ダイアログを閉じる操作にcloseメソッドを使い、引数に操作結果となる値を与えます。これがreturnValue属性の値となるので、ダイアログを開いた側がこの属性を参照することで、ダイアログの操作結果として受け取ることができます(リスト7、リスト8)。
リスト7:return_dialog.html
<dialog>
<form>
<p><label>好きな麺類を教えてください:
<select>
<option value="default">選択してください</option>
<option>ラーメン</option>
<option>そば</option>
<option>うどん</option>
<option>その他</option>
</select>
</label></p>
<div>
<button id="confirm" value="default">送信</button>
<button value="cancel" formmethod="dialog">キャンセル</button>
</div>
</form>
</dialog>
<p><button id="showButton">麺類選択ダイアログを表示する</button></p>
<output>選択結果はここに表示されます!</output>
<script src="return_dialog.js"></script>
リスト8:return_dialog.js
const dialogElement = document.querySelector("dialog");
const showButton = document.querySelector("#showButton");
const outputElement = document.querySelector("output");
const selectElement = dialogElement.querySelector("select");
const confirmButton = dialogElement.querySelector("#confirm");
showButton.addEventListener("click", () => {
dialogElement.showModal();
});
confirmButton.addEventListener("click", (event) => { (1)
event.preventDefault();
dialogElement.close(selectElement.value);
});
dialogElement.addEventListener("close", (e) => { (2)
outputElement.value = dialogElement.returnValue === "default"?
"戻り値はありません" :
`好きな麺類は ${dialogElement.returnValue} ですね!`;
});
(1)は、ダイアログを開くボタンクリック時のイベントハンドラです。preventDefaultメソッドで既定の動作を無効化し、closeメソッドをダイアログに対して呼び出します。このとき、引数に選択リストの値(value属性)を指定していることに注目です。
(2)は、ダイアログが閉じる際に発生するcloseイベントのイベントハンドラです。ここでは、ダイアログのreturnValue属性から値を取得し、その内容によってoutput要素にメッセージを設定しています。defaultは選択リストの既定値であるので、それによって選択が実際に行われたかを判断しています。
ページを表示させてダイアログを開き、選択リストから麺類を選択して[送信]ボタンをクリックすると、図5のように選択した結果を表示できます。
必須項目のあるダイアログ
required属性を指定することで、未入力の要素がある場合にダイアログを閉じることができないようにすることができます。未入力をなくして続行するか、formnovalidate属性をボタンに指定して無検証で閉じるようにするか、closeメソッドを呼び出して強制的に閉じるなどの方法があります。
リスト9:validated_dialog.html
<dialog>
<form method="dialog">
<p><label>
好きな麺類は?
<input type="text" required />
</label></p>
<div>
<input type="submit" id="normal-close" value="閉じる(検証あり)" />
<input type="submit" id="novalidate-close" value="閉じる(検証なし)" formnovalidate />
<input type="submit" id="js-close" value="閉じる(closeメソッド)" />
</div>
</form>
</dialog>
<button id="show">ダイアログを表示</button>
<script src="validated_dialog.js"></script>
リスト10:validated_dialog.js
const dialogElement = document.querySelector("dialog");
const showButton = document.querySelector("dialog + button");
const closeButton = document.querySelector("#js-close");
showButton.addEventListener("click", () => {
dialogElement.showModal();
});
closeButton.addEventListener("click", (e) => {
e.preventDefault();
dialogElement.close();
});
アニメーションするダイアログ
最後に、アニメーションするダイアログを、キーフレームというアニメーションの手法で紹介します。キーフレームアニメーションとは、キーフレーム(中間地点)のスタイルを指定して他の部分はブラウザに埋めてもらうタイプのアニメーションです。ここでは、CSSのみを示します。HTML、JavaScriptについてはこれまでのサンプルとほぼ同様なので、配布サンプルのkeyframe_dialog.htmlファイル、keyframe_dialog.jsファイルを参照してください。
/* (1)ダイアログが閉じるときのアニメーション */
@keyframes fade-out {
0% {
opacity: 1.0;
transform: scale(1, 1);
display: block;
}
100% {
opacity: 0;
transform: scale(0, 0);
display: none;
}
}
dialog {
animation: fade-out 1.0s ease-out;
}
/* (2)ダイアログが開くときのアニメーション */
@keyframes fade-in {
0% {
opacity: 0;
transform: scale(0, 0);
display: none;
}
100% {
opacity: 1.0;
transform: scale(1, 1);
display: block;
}
}
dialog[open] {
animation: fade-in 1.0s ease-out;
}
/* (3)ダイアログが開くときの背景アニメーション */
@keyframes backdrop-fade-in {
0% {
background-color: rgba(0, 0, 0, 0.0);
}
100% {
background-color: rgba(0, 0, 0, 0.25);
}
}
dialog[open]::backdrop {
animation: backdrop-fade-in 1.0s ease-out forwards;
}
アニメーションの指定は、(1)ダイアログが閉じるとき、(2)ダイアログが開くとき、(3)ダイアログが開くときの背景の3つに分かれています。それぞれ、@keyframesアットルールによるキーフレームの指定、animationプロパティによる詳細の指定に分かれます。
キーフレームとは前述の通り中間地点の指定で、@keyframesアットルールに名前を指定して記述します。ここでは、0%(始点)と100%(終点)のみを指定し、opacityプロパティによる透明度の指定、transformプロパティによる変形関数の指定(ここではscale関数)、displayプロパティによる表示の有無の指定を行っています。
animationプロパティには、使用するキーフレーム、遷移時間(1秒)、遷移関数(この場合はイーズアウト)を指定しています。時間を長くしたり、遷移曲線を変更することも可能です。
まとめ
ダイアログはいかがでしたでしょうか。dialog要素だけで、スタンドアロンアプリのようなダイアログが簡単に実現できることをお伝えできたのではないかと思います。次回は、select要素やinput要素を拡張するするdatalistを紹介します。
WINGSプロジェクト 山内直(著) 山田 祥寛(監修)
有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。現在も執筆メンバーを募集中。興味のある方は、どしどし応募頂きたい。著書、記事多数。
RSS
X:@WingsPro_info(公式)、@WingsPro_info/wings(メンバーリスト)<著者について>
WINGSプロジェクト所属のテクニカルライター。出版社を経てフリーランスとして独立。ライター、エディター、デベロッパー、講師業に従事。屋号は「たまデジ。」。