ポップアップ: 復活!

Open UI イニシアチブの目標は、デベロッパーが優れたユーザー エクスペリエンスを実現しやすくすることです。そのために、デベロッパーが直面する、より問題の多いパターンへの取り組みを進めています。これは、プラットフォームに組み込まれたより優れた API とコンポーネントを提供することで実現できます。

そのような問題領域の一つがポップアップです。Open UI では「ポップオーバー」と呼ばれます。

ポップオーバーは長い間、賛否両論の評価を受けています。これは、ビルドとデプロイの両方の方法に起因しています。効果的なパターンを構築するのは簡単ではありませんが、ユーザーを特定のコンテンツに誘導したり、サイトのコンテンツをユーザーに認識させたりすることで、多くの価値をもたらすことができます。特に、適度な方法で使用すると効果的です。

ポップオーバーを構築する際には、主に次の 2 つの懸念事項があります。

  • 他のコンテンツの上に適切な場所に配置されるようにする方法。
  • ユーザー補助機能(キーボード操作に対応する、フォーカス可能など)にする方法。

組み込みの Popover API にはさまざまな目標がありますが、すべてに共通する目標は、デベロッパーがこのパターンを簡単に構築できるようにすることです。主な目標は次のとおりです。

  • 要素とその子孫をドキュメントの残りの部分の上に簡単に表示できるようにします。
  • アクセス性を確保します。
  • ほとんどの一般的な動作(軽い閉じる、シングルトン、スタッキングなど)に JavaScript を必要としません。

ポップアップの詳細な仕様については、OpenUI サイトをご覧ください。

ブラウザの互換性

組み込みの Popover API はどこで使用できますか?このドキュメントの作成時点では、Chrome Canary では「試験運用版のウェブ プラットフォーム機能」フラグによってサポートされています。

このフラグを有効にするには、Chrome Canary を開いて chrome://flags にアクセスしてください。次に、「試験運用版のウェブ プラットフォームの機能」フラグを有効にします。

本番環境でテストしたいデベロッパー向けに、オリジン トライアルも用意されています。

最後に、この API 用の ポリフィルが開発中です。github.com/oddbird/popup-polyfill でリポジトリを確認してください。

ポップアップのサポートを確認するには、次の手順を行います。

const supported = HTMLElement.prototype.hasOwnProperty("popover");

現在のソリューション

コンテンツを他のコンテンツよりも優先して宣伝するために、現在できることは何ですか?ブラウザでサポートされている場合は、HTML ダイアログ要素を使用できます。これを「モーダル」フォームで使用する必要があります。そのため、JavaScript が必要です。

Dialog.showModal();

アクセシビリティに関する考慮事項がいくつかあります。たとえば、バージョン 15.4 より前の Safari のユーザーに対応する場合は、a11y-dialog を使用することをおすすめします。

ポップオーバー、アラート、ツールチップ ベースのライブラリも多数公開されています。これらの多くは、同じように機能します。

  • ポップオーバーを表示するために、本文にコンテナを追加します。
  • 他の要素の上に配置されるようにスタイル設定します。
  • 要素を作成してコンテナに追加し、ポップオーバーを表示します。
  • DOM からポップオーバー要素を削除して非表示にします。

これには、追加の依存関係とデベロッパーによるより多くの判断が必要になります。また、必要な機能をすべて備えたサービスを見つけるために調査も必要です。Popover API は、ツールチップなど、さまざまなシナリオに対応することを目的としています。こうした一般的なシナリオをすべて網羅することで、デベロッパーがエクスペリエンスの構築に集中できるように、デベロッパーが別の判断を下す必要がなくなります。

最初のポップアップ

必要なのはこれだけです。

<div id="my-first-popover" popover>Popover Content!</div>
<button popovertoggletarget="my-first-popover">Toggle Popover</button>

では、ここで何が起きているのか?

  • ポップオーバー要素をコンテナなどに配置する必要はありません。デフォルトでは非表示になっています。
  • 表示するために JavaScript を記述する必要はありません。これは popovertoggletarget 属性によって処理されます。
  • これが表示されると、最上位のレイヤにプロモートされます。つまり、ビューポートの document より上にプロモートされます。z-index を管理したり、ポップオーバーが DOM のどこにあるかを気にしたりする必要はありません。クリッピング アンセスターとともに DOM の深くにネストされている可能性があります。DevTools で、現在最上位のレイヤにある要素を確認することもできます。最上位レイヤについて詳しくは、こちらの記事をご覧ください。

DevTools の最上位レイヤのサポートのデモを示す GIF

  • デフォルトでは「ライト ディスミス」が設定されています。つまり、ポップオーバーの外側をクリックする、キーボードで別の要素に移動する、Esc キーを押すなど、閉じるシグナルでポップオーバーを閉じることができます。もう一度開いて、試してみましょう。

ポップオーバーには他にどのようなメリットがありますか?例をさらに見てみましょう。ページにコンテンツがあるデモについて考えてみましょう。

このフローティング アクション ボタンは、高い z-index で位置が固定されています。

.fab {
  position: fixed;
  z-index: 99999;
}

ポップオーバーのコンテンツは DOM にネストされていますが、ポップオーバーを開くと、その固定位置要素の上に表示されます。スタイルを設定する必要はありません。

また、ポップオーバーに ::backdrop 疑似要素が追加されていることにも気づくでしょう。最上位レイヤにあるすべての要素には、スタイル設定可能な ::backdrop 疑似要素が適用されます。この例では、アルファ値を下げた背景色と背景フィルタを使用して ::backdrop のスタイルを設定し、基になるコンテンツをぼかしています。

ポップオーバーのスタイルを設定する

ポップオーバーのスタイル設定に目を向けましょう。デフォルトでは、ポップオーバーは固定位置に表示され、パディングが適用されます。また、display: none もあります。これをオーバーライドしてポップオーバーを表示することもできます。ただし、この方法では最上位レイヤに昇格しません。

[popover] { display: block; }

ポップオーバーをどのように昇格させるかにかかわらず、ポップオーバーを最上位レイヤに昇格させたら、レイアウトや配置を調整しなければならない場合があります。最上位レイヤをターゲットに設定して、次のような処理を行うことはできません。

:open {
  display: grid;
  place-items: center;
}

デフォルトでは、ポップオーバーは margin: auto を使用してビューポートの中央に配置されます。ただし、場合によっては、配置を明示的に指定する必要があります。例:

[popover] {
  top: 50%;
  left: 50%;
  translate: -50%;
}

CSS グリッドまたは Flexbox を使用してポップオーバー内のコンテンツをレイアウトする場合は、要素でラップすることをおすすめします。それ以外の場合は、ポップオーバーが最上位レイヤに入ったときに display を変更する個別のルールを宣言する必要があります。デフォルトで設定すると、display: none をオーバーライドしてデフォルトで表示されます。

[popover]:open {
 display: flex;
}

デモを試すと、ポップオーバーが開閉するようになりました。ポップオーバーを切り替えるには、:open 疑似セレクタを使用します。:open 疑似セレクタは、表示されている(つまり最上位レイヤにある)ポップオーバーと一致します。

この例では、カスタム プロパティを使用して遷移を開始します。ポップオーバーの ::backdrop にも遷移を適用できます。

[popover] {
  --hide: 1;
  transition: transform 0.2s;
  transform: translateY(calc(var(--hide) * -100vh))
            scale(calc(1 - var(--hide)));
}

[popover]::backdrop {
  transition: opacity 0.2s;
  opacity: calc(1 - var(--hide, 1));
}


[popover]:open::backdrop  {
  --hide: 0;
}

遷移とアニメーションは、モーションのメディアクエリでグループ化することをおすすめします。これにより、タイミングを維持することもできます。これは、カスタム プロパティを介して popover::backdrop の間で値を共有できないためです。

@media(prefers-reduced-motion: no-preference) {
  [popover] { transition: transform 0.2s; }
  [popover]::backdrop { transition: opacity 0.2s; }
}

ここまでは、popovertoggletarget を使用してポップオーバーを表示してきました。閉じるには「ライトで閉じる」を使用します。ただし、使用できる popovershowtarget 属性と popoverhidetarget 属性もあります。それを非表示にするボタンをポップオーバーに追加し、popovershowtarget を使用するように切り替えボタンを変更しましょう。

<div id="code-popover" popover>
  <button popoverhidetarget="code-popover">Hide Code</button>
</div>
<button popovershowtarget="code-popover">Reveal Code</button>

前述のように、Popover API は、従来のポップアップの概念を超えて、通知、メニュー、ツールチップなど、あらゆるシナリオに対応できます。

これらのシナリオによっては、異なるインタラクション パターンが必要になります。マウスオーバーなどのインタラクション。popoverhovertarget 属性の使用はテストされましたが、現在は実装されていません。

<div popoverhovertarget="hover-popover">Hover for Code</div>

要素にカーソルを合わせるとターゲットが表示される仕組みです。この動作は CSS プロパティで設定できます。これらの CSS プロパティは、ポップオーバーが反応する要素のホバーとホバー解除の時間を定義します。テスト対象のデフォルトの動作では、:hover の明示的な 0.5s の後にポップオーバーが表示されていました。その場合は、軽い閉じる操作を行うか、別のポップオーバーを開いて閉じる必要があります(詳しくは後述)。これは、ポップオーバーの非表示の時間を Infinity に設定したことが原因でした。

それまでは、JavaScript を使用してその機能をポリフィルできます。

let hoverTimer;
const HOVER_TRIGGERS = document.querySelectorAll("[popoverhovertarget]");
const tearDown = () => {
  if (hoverTimer) clearTimeout(hoverTimer);
};
HOVER_TRIGGERS.forEach((trigger) => {
  const popover = document.querySelector(
    `#${trigger.getAttribute("popoverhovertarget")}`
  );
  trigger.addEventListener("pointerenter", () => {
    hoverTimer = setTimeout(() => {
      if (!popover.matches(":open")) popover.showPopOver();
    }, 500);
    trigger.addEventListener("pointerleave", tearDown);
  });
});

明示的なホバー ウィンドウを設定する利点は、ユーザーの操作が意図的なものになることです(たとえば、ターゲットの上にポインタを渡すなど)。ユーザーが意図していない限り、ポップアップを表示することはありません。

ウィンドウを 0.5s に設定してターゲットにホバーできるデモをお試しください。


一般的なユースケースと例を確認する前に、いくつかの点を確認しておきましょう。


ポップオーバーの種類

JavaScript 以外のインタラクションの動作について説明しました。では、ポップオーバー動作全体についてはどうでしょうか。[ライトで閉じる] を希望しない場合はどうすればよいですか?ポップオーバーにシングルトン パターンを適用したい場合

Popover API では、動作が異なる 3 種類のポップオーバーを指定できます。

[popover=auto]/[popover]:

  • ネスト サポート。これは、DOM にネストされていることを意味するわけではありません。祖先ポップオーバーの定義は次のとおりです。
    • DOM の位置(子)によって関連付けられている。
    • 子要素のトリガー属性(popovertoggletargetpopovershowtarget など)によって関連付けられている。
    • anchor 属性で関連付けられている(開発中の CSS Anchoring API)。
  • ライトの閉じる操作。
  • 開くと、祖先ポップオーバー以外のポップオーバーが閉じられます。以下のデモで、祖先ポップオーバーを使用したネストの仕組みをご確認ください。popoverhidetarget/popovershowtarget インスタンスの一部を popovertoggletarget に変更すると、どのように変わるかをご覧ください。
  • ライトを 1 つ閉じるとすべて閉じられますが、グルーピングされたライトを 1 つ閉じると、その上にあるライトのみが閉じられます。

[popover=manual]:

  • 他のポップオーバーは閉じません。
  • 消灯します。
  • トリガー要素または JavaScript により明示的に却下する必要があります。

JavaScript API

ポップオーバーをより細かく制御する必要がある場合は、JavaScript を使用できます。showPopover メソッドと hidePopover メソッドの両方が取得されます。popovershow イベントと popoverhide イベントもリッスンできます。

ポップアップを表示する js popoverElement.showPopover() ポップアップを非表示にする:

popoverElement.hidePopover()

ポップオーバーの表示をリッスンします。

popoverElement.addEventListener('popovershow', doSomethingWhenPopoverShows)

ポップオーバーの表示をリッスンし、表示をキャンセルします。

popoverElement.addEventListener('popovershow',event => {
  event.preventDefault();
  console.warn(We blocked a popover from being shown);
})

ポップオーバーが非表示になったことをリッスンします。

popoverElement.addEventListener('popoverhide', doSomethingWhenPopoverHides)

ポップアップの非表示をキャンセルすることはできません。

popoverElement.addEventListener('popoverhide',event => {
  event.preventDefault();
  console.warn("You aren't allowed to cancel the hiding of a popover");
})

ポップオーバーが最上位レイヤにあるかどうかを確認します。

popoverElement.matches(':open')

これにより、あまり一般的でないシナリオで追加の電力を供給できます。たとえば、一定時間操作がないとポップオーバーを表示します。

このデモにはポップオーバーの音声があるため、音声を再生するには JavaScript が必要です。クリックするとポップオーバーが非表示になり、音声が再生されて、ポップオーバーが再び表示されます。

ユーザー補助

ポップオーバー API では、ユーザー補助機能が最優先事項です。ユーザー補助マッピングでは、必要に応じてポップオーバーをトリガー要素に関連付けます。つまり、popovertoggletarget などのトリガー属性のいずれかを使用する場合は、aria-haspopup などの aria-* 属性を宣言する必要はありません。

フォーカス管理では、オートフォーカス属性を使用して、ポップオーバー内の要素にフォーカスを移動できます。これは Dialog の場合と同じですが、フォーカスを戻す際の違いがあります。これは、軽い閉じ方によるものです。ほとんどの場合、ポップオーバーを閉じると、フォーカスは以前にフォーカスされていた要素に戻ります。ただし、ライトを閉じると、クリックされた要素にフォーカスが移動します(フォーカスを取得できる場合)。説明動画のフォーカス管理に関するセクションをご覧ください。

このデモを動作させるには、このデモの全画面バージョンを開く必要があります。

このデモでは、フォーカスされている要素に緑色の枠線が表示されます。キーボードでインターフェースをタブ移動してみてください。ポップオーバーが閉じられたときにフォーカスが返される場所に注意してください。また、Tab キーを押すとポップオーバーが閉じることも確認できます。これは仕様です。ポップオーバーにはフォーカス管理がありますが、フォーカスをトラップしません。キーボード ナビゲーションでは、フォーカスがポップオーバーから移動したときに閉じるシグナルが識別されます。

アンカー(開発中)

ポップオーバーに関しては、要素をトリガーに固定するという厄介なパターンがあります。たとえば、ツールチップがトリガーの上に表示される設定になっているにもかかわらず、ドキュメントがスクロールされた場合です。そのツールチップはビューポートで切り捨てられる可能性があります。現在、この問題に対処する JavaScript 機能(フローティング UI など)があります。ツールチップが再配置され、この問題が解消され、目的の位置順序が維持されます。

ただし、スタイルでこれを定義できるようにしたいと思っています。この問題に対処するために、Popover API とともにコンパニオン API が開発中です。CSS アンカー ポジショニング API を使用すると、要素を他の要素にテザリングできます。この API は、要素がビューポートで切り取られないように要素の位置を再調整します。

このデモでは、Anchoring API の現在の状態を使用しています。ボートの位置は、ビューポート内のアンカーの位置に応じて変化します。

このデモを機能させる CSS のスニペットは次のとおりです。JavaScript は必要ありません。

.anchor {
  --anchor-name: --anchor;
}
.anchored {
  position: absolute;
  position-fallback: --compass;
}
@position-fallback --compass {
  @try {
    bottom: anchor(--anchor top);
    left: anchor(--anchor right);
  }
  @try {
    top: anchor(--anchor bottom);
    left: anchor(--anchor right);
  }
}

仕様については、こちらをご覧ください。この API のポリフィルも用意されます。

ポップオーバーの機能と使用方法について理解できたところで、いくつかの例を見てみましょう。

通知

このデモでは、「クリップボードにコピー」通知が表示されます。

  • [popover=manual] を使用する。
  • アクション時に showPopover のポップオーバーを表示します。
  • 2000ms のタイムアウト後に、hidePopover で非表示にします。

トースト

このデモでは、最上位レイヤを使用してトースト スタイルの通知を表示します。

  • タイプ manual の 1 つのポップオーバーがコンテナとして機能します。
  • 新しい通知がポップオーバーに追加され、ポップオーバーが表示されます。
  • クリック時に Web Animations API で削除され、DOM から削除されます。
  • 表示するトーストがない場合は、ポップオーバーは非表示になります。

ネストされたメニュー

このデモでは、ネストされたナビゲーション メニューの動作を示します。

  • ネストされたポップオーバーを許可するため、[popover=auto] を使用してください。
  • キーボードで移動するには、各プルダウンの最初のリンクで autofocus を使用します。
  • これは CSS Anchoring API の理想的な候補です。このデモでは、少量の JavaScript を使用して、カスタム プロパティを使用して位置を更新します。
const ANCHOR = (anchor, anchored) => () => {
  const { top, bottom, left, right } = anchor.getBoundingClientRect();
  anchored.style.setProperty("--top", top);
  anchored.style.setProperty("--right", right);
  anchored.style.setProperty("--bottom", bottom);
  anchored.style.setProperty("--left", left);
};

PRODUCTS_MENU.addEventListener("popovershow", ANCHOR(PRODUCT_TARGET, PRODUCTS_MENU));

このデモでは autofocus を使用するため、キーボード ナビゲーションのために「全画面表示」で開く必要があります。

メディア ポップオーバー

このデモでは、メディアをポップアップ表示する方法を示します。

  • ライトの閉鎖に [popover=auto] を使用します。
  • JavaScript は動画の play イベントをリッスンし、動画をポップアップ表示します。
  • ポップオーバーの popoverhide イベントによって動画が一時停止します。

Wiki スタイルのポップオーバー

このデモでは、メディアを含むインライン コンテンツ ツールチップを作成する方法を示します。

  • [popover=auto] を使用します。1 つを表示すると、祖先ではない他の家系は非表示になります。
  • JavaScript で pointerenter に表示されます。
  • CSS Anchoring API に最適な候補です。

このデモでは、ポップオーバーを使用してナビゲーション ドロワーを作成します。

  • ライトの閉鎖に [popover=auto] を使用します。
  • autofocus を使用して最初のナビゲーション アイテムにフォーカスを設定します。

バックドロップの管理

このデモは、1 つの ::backdrop のみを表示する複数のポップオーバーの背景を管理する方法を示しています。

  • JavaScript を使用して、表示されているポップオーバーのリストを維持します。
  • 最上位レイヤの最下層のポップオーバーにクラス名を適用します。

カスタム カーソルのポップオーバー

このデモでは、popover を使用して canvas を最上位レイヤに昇格させ、カスタム カーソルを表示する方法を示します。

  • showPopover[popover=manual] を使用して、canvas をトップレイヤに昇格させます。
  • 他のポップオーバーが開いている場合は、canvas ポップオーバーを非表示にしてから表示し直して、上部に表示されるようにします。

アクションシートのポップオーバー

このデモでは、ポップオーバーをアクションシートとして使用する方法を示します。

  • display をオーバーライドして、ポップオーバーをデフォルトで表示します。
  • ポップオーバー トリガーでアクションシートが開きます。
  • ポップオーバーが表示されると、最上位レイヤに昇格し、ビューに移動されます。
  • 軽い閉じる操作で元に戻すことができます。

キーボードでポップオーバーを有効にする

このデモでは、コマンドパレット スタイルの UI にポップオーバーを使用する方法を示します。

  • cmd+j キーを押してポップオーバーを表示します。
  • inputautofocus でフォーカスされます。
  • コンボボックスは、メイン入力の下に配置された 2 番目の popover です。
  • 軽い閉じる操作では、プルダウンが表示されていない場合はパレットが閉じられます。
  • Anchoring API のもう一つの候補は

タイマー付きポップオーバー

このデモでは、4 秒後に非アクティブなポップオーバーを表示します。ユーザーに関する機密情報を保持するアプリでよく使用される UI パターン。ログアウト モーダルを表示します。

  • JavaScript を使用して、非アクティブな状態が一定時間続くとポップオーバーを表示します。
  • ポップオーバーの表示時にタイマーをリセットします。

スクリーンセーバー

前のデモと同様に、サイトにちょっとした遊び心を加えて、スクリーンセーバーを追加することもできます。

  • JavaScript を使用して、非アクティブな状態が一定時間続くとポップオーバーを表示します。
  • ライトを消してタイマーを非表示にしてリセットします。

キャレットの移動

このデモでは、入力カーソルにポップオーバーを表示する方法を示しています。

  • 選択、キーイベント、特殊文字入力に基づいてポップオーバーを表示します。
  • JavaScript を使用して、スコープ設定されたカスタム プロパティでポップオーバーの位置を更新します。
  • このパターンでは、表示されるコンテンツとユーザー補助について慎重に検討する必要があります。
  • タグ付けできるテキスト編集 UI やアプリでよく見られます。

フローティング アクション ボタンのメニュー

このデモでは、ポップオーバーを使用して JavaScript なしでフローティング アクション ボタン メニューを実装する方法を示します。

  • showPopover メソッドを使用して、manual タイプのポップオーバーを昇格させます。これがメインボタンです。
  • メニューも、メインボタンのターゲットとなっているポップオーバーです。
  • popovertoggletarget でメニューが開きます。
  • autofocus を使用して、番組の最初のメニュー項目にフォーカスを移動します。
  • ライトを閉じるとメニューが閉じます。
  • アイコンのツイストには :has() を使用します。:has() について詳しくは、こちらの記事をご覧ください。

これで作業は完了です。

これは、Open UI の取り組みの一環として今後リリースされるポップオーバーの概要です。適切に使用すれば、ウェブ プラットフォームに素晴らしい機能として追加されるでしょう。

Open UI もご確認ください。ポップオーバーの説明は、API の進化に合わせて最新の状態に保たれています。すべてのデモのコレクションもご覧ください。

お問い合わせいただきありがとうございます。


写真提供: Madison OrenUnsplash