JavaScript なしでインタラクションを追加する Invokers
Invokers は JavaScript なしでインタラクションを追加するための提案です。`<button>` 要素に `invoketarget` 属性を指定することで、値として指定した id を持つ `<dialog>` などの要素の開閉状態を切り替えることが可能となります。
invoketarget
、interesttarget
属性は 2023 年 10 月 22 日現在実装されていません。
<button>
要素に invoketarget
属性を指定することにより、JavaScript を削減し、より宣言的な方法で UI にインタラクションを追加できます。下記の例では invoketarget
属性に <dialog>
の id を指定することで、<button>
要素をクリックしたときに <dialog>
要素を開くことができます。
<button invoketarget="dialog">Open Dialog</button>
<dialog id="dialog">Content</dialog>
interesttarget
属性は、インタラクティブな要素に対してホバー、フォーカスをした際にツールチップを表示する提案です。インタラクティブな要素は <button>
に限らず、以下の要素が対象となります。
<a>
<area>
<input>
<select>
<textarea>
<button>
<summary>
<button interesttarget="my-popover">Open Popover</button>
<div id="my-popover" popover="hint">Hello world</div>
背景
はるか昔のことですが、人々は DOM にインタラクションを追加するためにインラインの JavaScript を使用していました。
<button onclick="openDialog()">Open Dialog</button>
しかし、インラインの JavaScript はセキュリティ上の懸念や保守性の問題により次第に支持されなくなりました。この問題を解決するために、イベントリスナーを登録する addEventListener
メソッドが使われるようになります。
<button id="button">Open Dialog</button>
<script>
document.getElementById("button").addEventListener("click", function () {
// ...
});
</script>
イベントリスナーを登録する方法はセキュリティ上の問題を解決するとともに、「関心の分離」の原則に従うことができます。しかし、イベントリスナーを登録コードは定型的なコードが増え、しばしば冗長になりがちです。また、イベントリスナーのコードだけ見ても一見どの要素に対してどのようなインタラクションが追加されているのかがわかりにくいという「手続き型のコード」となってしまうことがあります。
近年の React, Vue, Svelte などのフレームワークでは、イベントリスナーを登録する代わりに、onClick
などのイベントハンドラーをプロパティとして指定することで、より宣言的な方法でインタラクションを追加することができます。
const App = () => {
const [isOpen, setIsOpen] = useState(false);
return (
<>
<button onClick={() => setIsOpen(true)}>Open Dialog</button>
{isOpen && <Dialog />}
</>
);
};
このように宣言的な方法でインタラクションを追加するコードを記述することにより、開発者体験の向上や、コードの保守性の向上が期待できることに我々開発者は気がついたのです。
ポップオーバー API は、ポップオーバー要素としたい DOM に対して popover
属性を、ポップオーバーの表示をトリガーとしたい DOM に対して popovertarget
属性を追加することで、ポップオーバーを表示できます。これには追加の JavaScript は必要ありません。
このように、宣言的な方法で DOM にインタラクションを追加する HTML は標準として既に受け入れられています。invoketarget
属性はこの考え方を拡張したものとなります。ポップオーバー API は現在ポップオーバーのみに焦点を当てていますが、invoketarget
属性はポップオーバー以外の以下の要素にも適用させることが目的です。
<dialog>
<details>
<video>
<input type="file">
また現在のポップオーバー API では、popovertarget
をクリックした時のみしかポップオーバー要素を表示できません。現実のアプリケーションでは、インタラクティブな要素をホバー(またはデバイスに応じたインタラクション)した際に表示されるツールチップも存在します。このような実装を行うには、追加の JavaScript が必要でした。ポップオーバー API を拡張してさらに使いやすい方法にすることも検討されています。
コード例
ポップオーバー
invoketarget
属性はポップオーバー API における popovertarget
と同じように動作します。つまり、invoketarget
を指定した要素をクリックした際に、invoketarget
の値と同じ id を持つ popover
の表示・非表示を切り替えます。
<button invoketarget="mypopover">ポップオーバーの切り替え</button>
<div id="mypopover" popover>ポップオーバーコンテンツ</div>
invoketarget
を指定した際のデフォルトの挙動はポップオーバーの開閉状態のトグルです。invokeaction
属性を指定することで、開くか閉じるかを指定できます。
<button invoketarget="mypopover" invokeaction="showPopover">
ポップオーバーを開く
</button>
<button invoketarget="mypopover" invokeaction="hidePopover">
ポップオーバーを閉じる
</button>
<div id="mypopover" popover>ポップオーバーコンテンツ</div>
interesttarget
属性を指定することで、ツールチップを表示できます。interesttarget
を指定したようにホバーすることで、interesttarget
の値と同じ id を持つ popover
の表示・非表示を切り替えます。
<input type="text" interesttarget="mypopover"></input>
<div id="mypopover" popover="hint">入力のヒント</div>
ダイアログ
invoketarget
属性の id に <dialog>
要素を指定することで、<button>
要素をクリックした際に <dialog>
要素の開閉状態を切り替えることができます。
デフォルトではダイアログの開閉状態をトグルします・invokeaction
属性を指定することで閉じるか開くかを指定できます。
<button invoketarget="my-dialog">ダイアログを開く</button>
<dialog id="my-dialog">
<p>コンテンツ</p>
<div>
<!-- invokeaction="cancel" はダイアログが `open` な場合に閉じる -->
<button
invoketarget="my-dialog"
invokeaction="cancel"
>
キャンセル
</button>
<!-- invokeaction="close" はダイアログが `open` な場合に閉じ、`value` 属性を返す -->
<button
invoketarget="my-dialog"
invokeaction="close"
value="ok"
>
OK
</button>
</dialog>
ディスクロージャー(details)
invoketarget
属性の id に <details>
要素を指定することで、<button>
要素をクリックした際に <details>
要素の開閉状態を切り替えることができます。
<button invoketarget="my-details">ヒントを開く</button>
<details id="my-details">
<summary>ヒント</summary>
<p>ヒントコンテンツ</p>
</details>
デフォルトでは <details>
の開閉状態をトグルします。invokeaction
属性を指定することで、開くか閉じるかを指定できます。
<button invoketarget="my-details" invokeaction="open">ヒントを開く</button>
<button invoketarget="my-details" invokeaction="close">ヒントを閉じる</button>
<details id="my-details">
<summary>ヒント</summary>
<p>ヒントコンテンツ</p>
</details>
ファイル選択
input[type="file"]
要素のカスタマイズは現実のアプリケーションで広く行われています。ファイル選択のカスタマイズは実装方法の誤りによりアクセシビリティを損なうおそれがありました。invoketarget
属性を使用することで、ファイル選択ダイアログの表示をより簡単に実現できます。
<button invoketarget="my-file">ファイルを選択</button>
<input type="file" id="my-file" />
ビデオ
<video>
や <audio>
要素には多くのインタラクションが存在し、その多くはカスタマイズされています(Youtube の動画を想像してください)。invoketarget
属性を使用することで、ビデオの再生・一時停止や、ミュートなどのカスタマイズをより簡単に実現できます。
invokeaction
を指定しない場合には、ビデオの再生状態のトグルを行います。invokeaction
を指定することで、再生・一時停止やミュートなどを指定できます。
<button invoketarget="my-video" invokeaction="play">再生</button>
<button invoketarget="my-video" invokeaction="pause">一時停止</button>
<button invoketarget="my-video" invokeaction="mute">ミュート</button>
<video id="my-video" src="video.mp4"></video>
JavaScript によるカスタム要素のインタラクション
invoketarget
により指定された任意の要素に対して、JavaScript によるインタラクションを追加することもできます。invoketarget
により指定された要素はイベントハンドラで invoke
イベントを購読します。
<button invoketarget="my-custom-element">要素の色を変更</button>
<div id="my-custom-element">...</div>
<script>
document
.getElementById("my-custom-element")
.addEventListener("invoke", function (e) {
// invokeaction が指定されていない場合には、"auto"になる
if (e.action === "auto" || e.action === "red") {
// 要素の色をトグルする
if (e.style.color === "black") {
this.style.color = "red";
} else {
this.style.color = "black";
}
} else if (e.action === "blue") {
if (e.style.color === "black") {
this.style.color = "blue";
} else {
this.style.color = "black";
}
}
});
</script>
interesttarget
によるインタラクションの追加の場合には、interest
と loseinterest
イベントを購読します。
<button interesttarget="my-custom-element">要素の色を変更</button>
<div id="my-custom-element">...</div>
<script>
document
.getElementById("my-custom-element")
.addEventListener("interest", function (e) {
this.style.color = "red";
});
document
.getElementById("my-custom-element")
.addEventListener("loseinterest", function (e) {
this.style.color = "black";
});
</script>
アクセシビリティ
invoketarget
属性を指定した場合、暗黙的に aria-controls
または aria-details
(現時点では未確定)が追加されます。これにより、支援技術に対してインタラクションをトリガーする要素とインタラクションの対象の要素との関連性を伝えることができます。
インタラクションの対象の要素に popover
属性が指定されている、<dialog>
要素、または <details>
要素の場合にはトリガー要素に自動で aria-expanded
の値が設定されます。
まとめ
- Invokers の提案は、ポップオーバー API を拡張したもので、宣言的な方法で JavaScript を使用せずにインタラクションを追加できる
invoketarget
属性は、<button>
要素をクリックした際に、invoketarget
の値と同じ id を持つ要素の表示・非表示を切り替えることができるinteresttarget
属性は、ホバーした際に、interesttarget
の値と同じ id を持つ要素の表示・非表示を切り替えることができる