HTMLとCSSで、マウスを乗せたときにポップオーバーを開く方法
ボタンやリンクに interestfor 属性を付け、ポップオーバーの id を指定すると、マウスを乗せたときにポップオーバーが開き、離すと閉じます。キーボードでフォーカスしたときも開き、Esc キーで閉じます。開くまでの待ち時間は CSS の interest-delay で変えられ、JavaScript は要りません。ボタンの近くに短い説明を出すツールチップを作りたいときに使えます。
検証環境は Ubuntu 24.04、Node.js 22.23.4、Playwright 1.63.0、Chromium 153 です。ヘッドレスの Chromium でページを開き、Playwright でマウスとキーボードを操作して、interest・loseinterest・toggle イベントが起きた時刻を記録しました。Firefox と Safari、スマートフォンの実機(長押しで開く動き)、スクリーンリーダーでの読み上げ、<area> と SVG の <a> は試していません。各ブラウザーの対応バージョンは、互換性データ(@mdn/browser-compat-data 8.1.8)の値です。
ボタンに interestfor、出したい要素に popover="hint" を付ける
もとになる書き方は次のとおりです。ボタンの interestfor に、出したい要素の id を書きます。出したい要素には popover="hint" を付けます。
<p>
<button interestfor="tip">ヘルプ</button>
下書きの保存について
</p>
<div id="tip" popover="hint">保存せずに閉じると、入力した内容は消えます</div>
popover 属性を付けた要素は、開くまで表示されません。MDN の関心インボーカーの解説によると、interestfor を付けた要素に、マウスを乗せる・フォーカスする・長押しするなどして「関心を示す」と、ブラウザーがポップオーバーを開きます。
検証環境で、マウスをボタンに乗せてから開くまでを3回測ると、502〜505ミリ秒でした。マウスを外へ出してから閉じるまでは、252〜254ミリ秒でした。ボタンに乗せて300ミリ秒で離したときは、ポップオーバーは開かず、イベントも起きませんでした。マウスがボタンの上を通り過ぎただけでは開きません。

キーボードでも同じように動きました。Tab キーでボタンにフォーカスすると503ミリ秒で開き、次のボタンへ Tab で移ると252ミリ秒で閉じました。開いているときに Esc キーを押すと、フォーカスはボタンに残ったまま、ポップオーバーだけが閉じました。マウスで開いたときも Esc で閉じ、そのあとマウスを乗せたまま1秒待っても開き直しませんでした。
position-area でボタンのすぐ下に出す
上の HTML だけでは、ポップオーバーはボタンから離れた画面の中央に出ます。検証環境の 800×600 の画面では、ポップオーバーの左上の角が 373, 283 の位置に出ました。
ボタンの下に出すには、ポップオーバーに position-area を指定します。
#tip {
position-area: bottom;
margin: 4px;
}
MDN のポップオーバー API の解説によると、ポップオーバーとそれを開く要素を結びつけると、開く要素が暗黙のアンカーになります。そのため、anchor-name を書かなくても position-area だけで位置が決まります。interestfor で結びつけた場合も同じで、検証環境では、下端が 64px のボタンに対して、ポップオーバーは上端 68px の位置に出ました。
ポップオーバーの上へマウスを移しても閉じない
ボタンからポップオーバーの上へマウスを移すと、ポップオーバーは開いたままでした。ポップオーバーの上に1.5秒置いても閉じず、loseinterest イベントも起きませんでした。ポップオーバーの外へ出すと、252ミリ秒で閉じました。
ボタンとポップオーバーの間にすき間があるときは、すき間を通る時間しだいです。40px のすき間を150ミリ秒で通ったときは、開いたままでした。すき間で400ミリ秒止まってから移ったときは、着く前に閉じました。ボタンとポップオーバーは、あまり離さずに置きます。
待ち時間は interest-delay で変える
開くまでと閉じるまでの待ち時間は、ボタンなど interestfor を付けた側に interest-delay を指定して変えます。値を1つ書くと開くときと閉じるときの両方に、2つ書くと1つ目が開くまで、2つ目が閉じるまでの時間になります。片方だけ変えるときは interest-delay-start と interest-delay-end を使います。
button[interestfor] {
interest-delay: 1s 2s;
}
検証環境で測った結果は次のとおりです。
| 指定 | 開くまで | 閉じるまで |
|---|---|---|
指定なし(normal) |
502〜505ms | 252〜254ms |
interest-delay: 0s |
17ms | 2ms |
interest-delay: 0.1s |
103ms | 102ms |
interest-delay-start: 200ms |
218ms | 251ms |
interest-delay: 1s 2s |
1002ms | 2002ms |
初期値は normal で、getComputedStyle() で読んでも normal が返りました。MDN では、normal の待ち時間はブラウザーごとに違うことがあるとされています。上の約0.5秒と約0.25秒は、Chromium 153 での値です。負の値は書けず、CSS.supports('interest-delay-start', '-1s') は false でした。
ボタンが並んでいるところで待ち時間を長くすると、隣のボタンへ移るたびに待たされます。MDN の例にある、:has() と :interest-source を組み合わせた指定を足すと、どれか1つが開いている間だけ、ほかのボタンの待ち時間をなくせます。次の 1s 0.25s は、検証で使った値です。
button {
interest-delay: 1s 0.25s;
}
p:has(button:interest-source) button {
interest-delay-start: 0s;
}
検証環境で、<p> の中に並べたボタン1とボタン2に、それぞれ別のポップオーバーを結びつけました。ボタン1に乗せて開いてからボタン2へ移ると、:has() の指定がないときは、ボタン2のポップオーバーが開くまで1004ミリ秒かかりました。:has() の指定があるときは3ミリ秒で開き、ボタン1のポップオーバーも同時に閉じました。
開いている間だけ見た目を変える
:interest-source は interestfor を付けた要素に、:interest-target は interestfor で指定されたポップオーバーに、関心が示されている間だけ当てはまります。
button:interest-source {
background: #d85878;
}
#tip:interest-target {
border-style: dashed;
}
この2つは、マウスを乗せた瞬間ではなく、待ち時間が過ぎてポップオーバーが開いたときに当てはまりました。乗せて200ミリ秒の時点ではどちらも当てはまらず、ボタンの背景も初期の色のままでした。離してから100ミリ秒の、まだ閉じる前の時点では当てはまったままで、閉じると外れました。
popover="hint" にすると、開いているメニューを閉じない
popover 属性の値は、ほかのポップオーバーと一緒に使うときに効いてきます。値を書かない popover は auto と同じ扱いです。MDN のポップオーバー API の解説では、hint のポップオーバーを開いても auto のポップオーバーは閉じない、と説明されています。
検証環境で、クリックで開くメニュー(popover)を開いたまま、隣の「ヘルプ」ボタンにマウスを乗せました。ヒントが popover="hint" のときはメニューとヒントの両方が表示され、popover(auto)のときはヒントが開くと同時にメニューが閉じました。

同じボタンに、クリックで開く commandfor と interestfor の両方を付けることもできます。
<button interestfor="tip" commandfor="box" command="toggle-popover">設定</button>
<p id="tip" popover="hint">ヒント</p>
<div id="box" popover>設定の箱</div>
検証環境では、マウスを乗せてヒントが開いたあとにボタンをクリックすると、loseinterest が起きてヒントが閉じ、箱が開きました。そこからマウスをいったん離して乗せ直すと、ヒントが hint のときは箱を開いたままヒントが開きました。auto のときは、ヒントが開くと箱が閉じました。interestfor だけを付けたボタンやリンクでは、クリックしてもヒントは開いたままで、リンクはふつうにリンク先へ移りました。
JavaScript で中身を変える
ポップオーバーが開くときと閉じるときには、ポップオーバーの側で interest と loseinterest イベントが起きます(MDN の interest イベントの説明(英語))。イベントの source に、マウスを乗せたボタンやリンクが入ります。
const tip = document.getElementById("tip");
for (const link of document.querySelectorAll("a")) {
link.interestForElement = tip;
}
tip.addEventListener("interest", (e) => {
tip.textContent = `${e.source.textContent} の説明`;
});
interestForElement は、JavaScript から interestfor の相手を決めるプロパティです。検証環境では、2つのリンクに同じポップオーバーを設定すると、どちらに乗せても開きました。中身は「リンク1 の説明」「リンク2 の説明」と、乗せたリンクに合わせて変わりました。設定したあとの interestfor 属性は、空の文字列になりました。
interest イベントで preventDefault() を呼ぶと、ポップオーバーは開きませんでした。イベントの cancelable は true でした。
使える要素と、対応しているブラウザー
MDN では、interestfor を付けられるのは <a>、<button>、<area>、SVG の <a> とされています。検証環境では、<button> と href のある <a> では開きました。tabindex を付けた <span> と、href のない <a> では開きませんでした。
ヒントが開いている間に、Chromium のアクセシビリティツリーを Chrome DevTools Protocol(Accessibility.getFullAXTree)で取り出すと、「ヘルプ」ボタンの説明(description)にヒントの文が入っていました。
互換性データでは、各ブラウザーが次のバージョンから対応しています。
| ブラウザー | interestfor、interest-delay など | popover="hint" |
|---|---|---|
| Chrome、Edge | 142 | 151(133〜150 は古い仕様) |
| Android の Chrome | 142 | 151(133〜150 は古い仕様) |
| Firefox | 未対応 | 153(149〜152 は古い仕様) |
| Safari | 未対応 | Technology Preview |
| iOS の Safari | 未対応 | 未対応 |
MDN の例では、対応していないブラウザーでも、interestfor を付けたリンクはふつうのリンクとして動くとされています。ヒントでしか見られない情報は置かず、ページの本文やリンク先でも同じことが分かるようにしておきます。
対応しているかどうかは、MDN の例と同じく interestForElement があるかで調べられます。検証環境では true が返りました。
const supported = Object.hasOwn(
HTMLButtonElement.prototype,
"interestForElement",
);





