はじめに
私たちを取り巻くWeb技術は、もはや社会的なインフラとしてめまぐるしく進化しています。HTMLやCSSはもちろんのこと、JavaScriptやライブラリ、フレームワークなど、それぞれがニーズにキャッチアップする形で、機能強化を繰り返しています。その中でも、Web技術の中核に位置するにもかかわらず、意外と見過ごされがちなのがHTMLの進化です。本連載は、このHTMLと関連するJavaScript APIにフォーカスして、その新機能を手軽に試していただこうというものです。理解も利用もたやすいHTMLなので、ライトな気持ちで「こんなことができるようになったのか」を感じていただきます。
[NOTE]サンプルについて
本記事の配布サンプルは、以下のURLから入手できます。新機能を試していくので、ブラウザは最新である方がよいでしょう。本記事のサンプルは、執筆時点で最新のGoogle Chromeで動作することを確認しています。
https://github.com/wateryinhare62/mynavi_html
連載第5回の目的
この回では、HTMLから簡単に利用できるインタラクションであるポップオーバーを試します。シンプルな実現方法の他、スタイルの指定など見た目を調整する例、複数のポップオーバーを使う方法、トーストやツールチップへの応用例も紹介します。
ポップオーバーとは
ポップオーバー(popover)とは、その名の通りページの最前面にポップアップ表示して、情報の表示やユーザーの対応を促すためのUIです。第3回で試したダイアログに似ていますが、ポップオーバーはそこでの処理結果を返す仕組みを持っていませんし、基本的にモードレス(表示されている間に他の操作をブロックしないこと)で動作します。
ポップオーバーは、基本的な動作はHTMLで完結しているのが大きな特徴です。開いているポップオーバーを簡単な操作で閉じたり、複数のポップオーバーを排他的に開く処理も自動です。さらにJavaScript APIが用意されているので、イベントでポップオーバーを制御することも可能です。
ポップオーバーは、その性質上、トースト(処理結果などを伝えるUI)、ツールチップ(マウスポインタのホバーで関連する情報を伝えるUI)、フローティングメニュー(マウスポインタのホバーで出現したり消えたりするメニュー)などの実現に使われます。
以降、シンプルな例を通じてポップオーバーの基本的な使い方を紹介していきます。
基本的なポップオーバー
ポップオーバーの基本は、トリガーとなる要素(呼び出し/制御要素)と、ポップオーバーとなる要素を組み合わせることです。ポップオーバー要素にid属性とpopover属性を指定し、トリガー要素にはそのidをpopovertarget属性で指定します(リスト1)。
リスト1:basic.html
<button popovertarget="popover">ポップオーバーを表示</button>
<div id="popover" popover>ラーメン食べたい!</div>
トリガー要素には、button要素かtype="button"であるところのinput要素を指定できます。ポップオーバー要素には任意の要素を指定できますが、通常はブロックとしてのdiv要素が使われます。popover属性は、popover=""またはpopover="auto"としても同様です。
HTMLをブラウザに読み込ませると、図1上のようにボタンだけが表示されます。これをクリックすると図1下のようにポップオーバーが表示されます。再度ボタンをクリックすると、ポップオーバーが消えます。
既定では、ポップオーバーは外部のクリックか[Esc]キーの入力で閉じることができます。これはポップオーバーの「簡単な解除」と呼ばれています。
アクションを指定するポップオーバー
上記の例は一つのボタンでポップオーバーの表示と非表示をトグルするものでしたが、ボタンに開く/閉じるの役割を持たせることができます。これにはpopoveraction属性を使います(リスト2)。
リスト2:action.html
<button popovertarget="popover" popovertargetaction="show">ポップオーバーを開く</button>
<button popovertarget="popover" popovertargetaction="hide">ポップオーバーを閉じる</button>
<div id="popover" popover>ラーメン食べたい!</div>
ボタンに以下の属性を付けると、開く専用、閉じる専用、トグル操作のボタンとできます。既定は既出の通り、トグル操作になります。
- popovertargetaction="show":開く専用
- popovertargetaction="hide":閉じる専用
- popovertargetaction="toggle":開く/閉じるをトグル(既定)
ポップオーバーが開いているとき、開く専用ボタンのクリックでポップオーバーが閉じることはありません。逆に、ポップオーバーが閉じているとき、閉じる専用ボタンのクリックでポップオーバーが開くことはありません。
HTMLをブラウザに読み込ませると、図2上のようにボタンが2つ表示されます。左のボタンをクリックすると図2下のようにポップオーバーが表示されます。右のボタンをクリックすると、ポップオーバーが消えます。役割でないボタンは機能しないことも試してみましょう。
ポップオーバーの表示位置や見た目の指定
これまで見てきた通り、ポップオーバーは既定でビューポートの中央に表示されます。また、太枠に白地で表示されるので、見た目は地味です。これらを変更したい場合には、:popover-open疑似クラスを使って、開いているポップオーバーに独自のスタイルを定義します。ここでは、ブラウザでサポートが始まったばかりのCSS Anchor Positioningを使ってポップオーバーの位置を指定し、見た目も変更してみます。
[NOTE]CSS Anchor Positioning
CSS Anchor Positioningは、ツールチップなどにおいて特定の要素(アンカー要素)に対する相対的な位置指定を容易にするCSSの機能です。従来は、JavaScriptで動的に座標を設定したものを、スタイル定義だけで設定できます。本稿作成時点では、BaselineがLimited availabilityとなっており実験的実装とされていますが、Google Chrome 125から利用可能であるため使用しています。Firefoxでは動作しませんので注意してください。
HTMLでは、アンカー要素となるトリガー要素に、CSSから参照可能なようにid属性を付加しておきます(リスト3)。
リスト3:styled.html
<button id="anchor" popovertarget="popover">ポップオーバーを表示</button>
<div id="popover" popover>ラーメン食べたい!</div>
CSSでは、アンカー要素にアンカー名を付与し、ポップオーバー要素でそのアンカー名を使った位置指定を行います(リスト4)。
リスト4:styled.css
#anchor {
anchor-name: --anchor; (1)
}
#popover:popover-open {
position-anchor: --anchor; (2)
inset: unset; (3)
position: absolute;
top: anchor(bottom); (4)
left: anchor(center);
translate: 0 -10px;
width: 300px;
text-align: center;
border: solid 1px blue;
border-radius: 10px;
box-shadow: 5px 5px 10px gray;
}
アンカー要素にアンカー名を<dashed-ident>形式で指定し(1)、ポップオーバー要素のposition-anchorプロパティにそのアンカー名を指定する(2)のが基本です。
(3)において、insetプロパティをunsetすなわち初期値にしていることに注意してください。既定のスタイルではinset: 0;(上下左右全て0という意味で中央配置となる)となっているので、これを解除して個別のプロパティ指定(top、left、right、bottom)が適切に働くようにしています。
(4)からの3行は、anchor関数でアンカー要素の指定する位置(ここではbottomとcenter)の座標をポップオーバーのtopとleftプロパティに設定し、translateプロパティで位置を若干上に移動しています。
HTMLをブラウザに読み込ませてボタンをクリックすると、図4のようにポップオーバーがボタンの右下に寄って、シャドウの付いた青い幅広の枠で表示されます。
ダイアログと同様に、::backdrop疑似要素によりポップアップ時に背景を暗くしたり、開く/閉じる動きをアニメーション化することも可能です。このうちアニメーションの例は、JavaScriptによる制御のサンプルとともに紹介します。
複数のポップオーバー
ここまではポップオーバーを一つだけ表示させてきましたが、トリガー要素とポップオーバー要素の組み合わせを複数個、置くことができます。既定(popoverまたはpopover=""またはpopover="auto")では、あるポップオーバーが開いているときに別のポップオーバーを開くと、開いていたポップオーバーは閉じます。つまり、排他的な動作となります(リスト5)。
リスト5:exclusive.html
<button popovertarget="popover1">ポップオーバー1を表示</button>
<div id="popover1" popover="auto">ラーメン食べたい!</div>
<button popovertarget="popover2">ポップオーバー2を表示</button>
<div id="popover2" popover="auto">お寿司食べたい!</div>
既定ではポップオーバーの位置は重なってしまうので、CSSで位置をずらしてあります(前項を参照。内容は基本的に同じなので、配布サンプルを参照してください)。
HTMLをブラウザに読み込ませると、図4上のようにボタンが2つ表示されます。左のボタンをクリックすると図4中のようにポップオーバーが表示されます。さらに右のボタンをクリックすると、表示中のポップオーバーは消えて、図4下のように新たなポップオーバーが表示されます。
ポップオーバーの同時表示
同時に複数のポップオーバーを開いたままにするには、ポップオーバー要素にpopover="manual"属性を指定します(リスト6)。このポップオーバーは、簡単な解除の対象にはなりません。
<button popovertarget="popover1">ポップオーバー1を表示</button>
<div id="popover1" popover="manual">ラーメン食べたい!</div>
<button popovertarget="popover2">ポップオーバー2を表示</button>
<div id="popover2" popover="manual">お寿司食べたい!</div>
HTMLをブラウザに読み込ませると、図5上のようにボタンが2つ表示されます。左のボタンをクリックすると図5中のようにポップオーバーが表示されます。さらに右のボタンをクリックすると、図5下のように新たにポップオーバーが表示されます。
[NOTE]popover="hint"
popover="hint"属性により、ヒントとしてのポップオーバーも作成可能です。ヒントとしてのポップオーバー間は排他的な表示となりますが、それ以外のポップオーバーには関与しません。つまり、開いているポップオーバーがあっても、ヒントとしてのポップオーバーによって閉じられることはありません。そういった意味ではpopover="manual"に似ていますが、この機能を使うにはJavaScriptによる制御が必要で、簡単な解除により閉じることができる点が異なります。例は最後にアニメーションと絡めて紹介します。
入れ子になったポップオーバー
ポップオーバーを入れ子にすることで、既定の状態でも複数のポップオーバーを同時に表示することができます。入れ子にするには、DOM自体を入れ子にします(リスト6)。
リスト6:nested.html
<button popovertarget="popover1">ポップオーバーを表示</button>
<div id="popover1" popover>ラーメン食べたい!<br>
<button popovertarget="popover2">ポップオーバーを表示</button>
<div id="popover2" popover>スープは何といってもとんこつ!</div>
</div>
双方ともpopover(="auto")ですが、内側にあるポップオーバーを開いても、外側のポップオーバーは閉じられません。また、外側のポップオーバーを閉じると、内側のポップオーバーも自動的に閉じます。
JavaScriptによる制御
HTMLだけでもポップオーバーを開いたり閉じたりできるのはこれまで見てきた通りです。ポップオーバーにはJavaScript APIが用意されているので、これを使ってスクリプトからポップオーバーを制御できます。
トースト風のポップオーバー
ポップオーバーはボタンと関連付けてそのアクションで開いたり閉じたりしてきましたが、これをJavaScriptでトースト風にしてみます。最初に実行例を示します。HTMLをブラウザに読み込ませると、ただちに図7のようにポップオーバーが表示されます。4秒経過すると、ポップオーバーが消えます。
HTMLはリスト7の通りで、ボタンを置きません。
リスト7:toast.html
<h1>ツツウラウラ</h1>
<div id="popover" popover>
<p>今日はラーメン日和ですね!</p>
<img src="ramen.jpg" alt="ラーメンのイラスト" width="100">
</div>
<script src="toast.js"></script>
JavaScriptコードはリスト8の通りです。
リスト8:toast.js
const popover = document.getElementById("popover");
popover.showPopover();
setTimeout(() => {
popover.hidePopover();
}, 4000);
スクリプトの読み込みと同時にポップオーバーを開き、4秒経過後に閉じるという処理を行っています。これらを含めたAPIを以下に挙げます。
- showPopoverメソッド:ポップオーバーを開く
- hidePopoverメソッド:ポップオーバーを閉じる
- togglePopoverメソッド:ポップオーバーの開閉を切り替える
これまで見てきたポップオーバーの操作は、これらのメソッドが内部的に動いていた、といううように見ることができます。これらを使うことで、ボタンによらずイベントでポップオーバーを開いたり閉じたりすることができるわけです。
ツールチップ風のポップオーバー
最後に、ツールチップ風のポップオーバーにアニメーションを加える例を紹介します。このポップオーバーは、ヒントとしてのポップオーバーとして実装してみます。コードが長くなるので、最初に実行例を示します(図8)。
ボタンはpopover="auto"であるポップオーバーを表示させるもの、その下の3つのリンクはpopover="hint"であるポップオーバーを表示させるものです。リンクにマウスポインタをホバーさせると、その右下にツールチップ風のポップオーバーが浮かび上がります。マウスポインタを外すとゆっくりと消えます。ヒントとしてのポップオーバーは簡単な解除で閉じることができるのも確認しましょう。
HTMLはリスト9の通りです。CSSやJavaScriptのためにid属性、クラス属性を細かく指定していますが、ポイントは(2)のpopover="hint"属性です。(1)がpopover="auto"であることも押さえておいてください。
リスト9:tooltip.html
<h1>ツツウラウラ</h1>
<button id="word" popovertarget="word-popover">今日のひとこと</button>
<div id="word-popover" popover="auto">今日もラーメンを食べよう!<br> (1)
<img src="ramen.jpg" alt="ラーメンのイラスト" width="100">
</div>
<div class="hints"><a id="shoyu" href="#">しょうゆ</a></div>
<div id="popover-shoyu" class="hint-popover" popover="hint">ラーメンといえば醤油でしょう。</div> (2)
<div class="hints"><a id="shio" href="#">しお</a></div>
<div id="popover-shio" class="hint-popover" popover="hint">あっさりとした塩ラーメンもいいですね。</div>
<div class="hints"><a id="miso" href="#">みそ</a></div>
<div id="popover-miso" class="hint-popover" popover="hint">寒くなってきたらやっぱり味噌味。</div>
<script src="tooltip.js"></script>
CSSはリスト10の通りです。一般的なスタイル指定、ポップオーバーの見た目、アンカー要素の指定など既出の内容は省略しています。アニメーションするスタイルとして、透明度のopacity、大きさのscale、変形のtransformを指定し、アニメーション速度などはtransitionプロパティで指定しています。最後にある@starting-styleルールの指定は、アニメーション開始時のスタイルとして必要で、必ず:popover-openセレクタより後に配置します。
リスト10:tooltip.css
/* ボタンにアンカー名をそれぞれ設定 */
#shoyu {
anchor-name: --shoyu;
}
#popover-shoyu {
position-anchor: --shoyu;
}
…shioとmisoについても同様…
/* ヒントとしてのポップオーバーが開いているときのスタイル */
.hint-popover:popover-open {
opacity: 1; /* 透明度1.0 */
scale: 1; /* 大きさ最大 */
translate: 0 0; /* 変換なし */
}
/* ヒントとしてのポップオーバー共通のスタイル */
.hint-popover {
inset: unset; /* 説明済み */
position: absolute;
top: anchor(bottom);
left: anchor(center);
opacity: 0; /* 透明度0 */
scale: 0.8; /* やや小さく */
translate: 0 8px; / 8px下へ */
transition: all 0.7s allow-discrete; /* 0.7秒でアニメーション */
border: solid 1px black;
box-shadow: 5px 5px 10px gray;
}
/* アニメーション開始時のスタイル */
@starting-style {
.hint-popover:popover-open {
opacity: 0;
scale: 0.8;
translate: 0 8px;
}
}
JavaScriptコードは、3つのテキストに対してmouseenterイベントとmouseoutイベントのイベントハンドラを設定し、それぞれでshowPopoverメソッド、hidePopoverメソッドを呼び出すだけなので、掲載は省略します。
まとめ
ポップオーバーはいかがでしたでしょうか。簡単な情報表示ならHTMLだけで実現でき、CSSやJavaScriptと組み合わせることでの応用範囲が意外に広そうだということをお伝えできたのではないかと思います。次回は、ミニウインドウで動画を再生できるPicture-in-Picture APIを紹介します。
WINGSプロジェクト 山内直(著) 山田 祥寛(監修)
有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。現在も執筆メンバーを募集中。興味のある方は、どしどし応募頂きたい。著書、記事多数。
RSS
X:@WingsPro_info(公式)、@WingsPro_info/wings(メンバーリスト)<著者について>
WINGSプロジェクト所属のテクニカルライター。出版社を経てフリーランスとして独立。ライター、エディター、デベロッパー、講師業に従事。屋号は「たまデジ。」。