ダイアログ コンポーネントを作成する

<dialog> 要素を使用して、色適応性、応答性、アクセスしやすいミニモーダルとメガモーダルを作成する方法の基本的な概要。

この投稿では、色適応 <dialog> 要素を使用すると、応答性が高くアクセスしやすいミニモーダルとメガモーダルを作成できます。 デモをお試しください ソースをご覧ください。

<ph type="x-smartling-placeholder">
</ph> ライトモードとダークモードでのメガとミニのダイアログのデモ。

動画で視聴したい場合は、この投稿の YouTube バージョンをご利用ください。

概要

<dialog> 要素は、ページ内のコンテキスト情報やアクションに適しています。どのような場合に ユーザー エクスペリエンスは、複数ページではなく同一ページのアクションから得られる 理由としては、フォームが小さい、または 確認またはキャンセルします。

最近、<dialog> 要素はブラウザで安定版になりました。

対応ブラウザ

  • Chrome: 37。 <ph type="x-smartling-placeholder">
  • Edge: 79。 <ph type="x-smartling-placeholder">
  • Firefox: 98。 <ph type="x-smartling-placeholder">
  • Safari: 15.4。 <ph type="x-smartling-placeholder">

ソース

要素にはいくつか欠けていたことがわかったので、こちらのGUI で 課題: デベロッパー エクスペリエンスを追加する 期待するアイテム: 追加のイベント、ライトを閉じる、カスタム アニメーション、ミニ 比較できます。

マークアップ

<dialog> 要素の基本は控えめです。要素は 自動的に非表示になり、コンテンツをオーバーレイするスタイルが組み込まれています。

<dialog>
  …
</dialog>

このベースラインを改善できます。

従来、ダイアログ要素はモーダルと多く共有しており、多くの場合、 互換性があります。ここでは、ダイアログ要素を自由に使用して、 小さなダイアログ ポップアップ(ミニ)と全画面ダイアログ(メガ)の両方があります。名前 メガとミニの 2 種類のダイアログがあり、どちらのダイアログも若干異なるユースケースに合わせて調整されています。 タイプを指定できるように modal-mode 属性を追加しました。

<dialog id="MegaDialog" modal-mode="mega"></dialog>
<dialog id="MiniDialog" modal-mode="mini"></dialog>

ライトモードとダークモードのミニダイアログとメガ ダイアログの両方のスクリーンショット。

必ずしもそうとは限らないが、一般的にはダイアログの要素が インタラクションの情報を提供します。ダイアログの要素内のフォームは できます。 ダイアログのコンテンツをフォーム要素でラップして、 JavaScript はユーザーが入力したデータにアクセスできます。さらに内部のボタンは method="dialog" を使用したフォームでは、JavaScript を使用せずにダイアログを閉じて、 分析できます

<dialog id="MegaDialog" modal-mode="mega">
  <form method="dialog">
    …
    <button value="cancel">Cancel</button>
    <button value="confirm">Confirm</button>
  </form>
</dialog>

メガ ダイアログ

メガ ダイアログには、フォーム内に次の 3 つの要素があります。 <header> <article>, および <footer>。 これらはセマンティック コンテナとして機能し、画像スタイル ターゲットとしても機能します。 ダイアログのプレゼンテーションです。ヘッダーにはモーダルのタイトルがあり、 ] ボタンを離します。この記事では、フォームの入力と情報について説明します。フッターには <menu> / 操作ボタン。

<dialog id="MegaDialog" modal-mode="mega">
  <form method="dialog">
    <header>
      <h3>Dialog title</h3>
      <button onclick="this.closest('dialog').close('close')"></button>
    </header>
    <article>...</article>
    <footer>
      <menu>
        <button autofocus type="reset" onclick="this.closest('dialog').close('cancel')">Cancel</button>
        <button type="submit" value="confirm">Confirm</button>
      </menu>
    </footer>
  </form>
</dialog>

最初のメニューボタンには autofocus onclick インライン イベント ハンドラ。autofocus 属性は、 メッセージにフォーカスしたいので、 確認ボタンではなくキャンセルボタンですこれにより 意図的なものであり、偶発的ではなく

ミニダイアログ

ミニダイアログはメガダイアログとよく似ており、 <header> 要素。サイズを小さくしてインライン化できます。

<dialog id="MiniDialog" modal-mode="mini">
  <form method="dialog">
    <article>
      <p>Are you sure you want to remove this user?</p>
    </article>
    <footer>
      <menu>
        <button autofocus type="reset" onclick="this.closest('dialog').close('cancel')">Cancel</button>
        <button type="submit" value="confirm">Confirm</button>
      </menu>
    </footer>
  </form>
</dialog>

ダイアログ要素は、メッセージを表示する完全なビューポート要素に、 データを収集できるようにします。これらの基本原則により、 ユーザーの興味を引く効果的な操作が 可能になります

ユーザー補助

ダイアログ要素には優れたユーザー補助機能が組み込まれています。これらを追加する代わりに、 多くの機能がすでにあります

フォーカスを復元しています

「サイド ナビゲーションの作成」で手動で行ったように コンポーネントを使用する場合、 適切なオープニングとクローズで、関連するオープニングとクローズに焦点を合わせる できます。そのサイド ナビゲーションが開くと、閉じるボタンにフォーカスが移動します。Google 閉じるボタンが押されると、開いたボタンにフォーカスが復元されます。

ダイアログ要素では、これは組み込みのデフォルトの動作です。

残念ながら、ダイアログの出入りをアニメーション化したい場合、この機能は 失われます。JavaScript セクションで復元します。 説明します。

トラップの焦点

ダイアログ要素は inert ドキュメント上で自動的に作成されます。inert より前は、フォーカスの監視に JavaScript が使用されていました 要素はなくなった時点で、それをインターセプトして元に戻します。

対応ブラウザ

  • Chrome: 102。 <ph type="x-smartling-placeholder">
  • Edge: 102。 <ph type="x-smartling-placeholder">
  • Firefox: 112。 <ph type="x-smartling-placeholder">
  • Safari: 15.5。 <ph type="x-smartling-placeholder">

ソース

inert以降、ドキュメントの任意の部分を「固定」できます責任を持って ターゲットをフォーカスしなくなったり、マウスで操作したりしなくなったりします。トラップする代わりに ドキュメントの唯一のインタラクティブな部分に案内されます。

要素を開いてオートフォーカスする

デフォルトでは、ダイアログ要素は最初のフォーカス可能な要素にフォーカスを割り当てます。 作成します。これがユーザーのデフォルトにする要素として 最適でない場合は autofocus 属性を使用します。先ほど説明したように 確認ボタンではなくキャンセルボタンに配置してくださいこれにより 確認が意図的なものであり、偶発的ではない。

Esc キーで閉じる

この要素を簡単に閉じることが重要です。 幸いなことに、ダイアログ要素は Esc キーを自動的に処理するため、 オーケストレーションの負担から解放されます

スタイル

ダイアログ要素のスタイル設定には簡単な方法とハードパスがあります。Google Workspace の このパスを実現するには、ダイアログの表示プロパティを変更せず、 説明します。一生懸命にカスタム アニメーションを ダイアログの開閉、display プロパティの引き継ぎなど。

開いた小道具を使用したスタイル設定

アダプティブ カラーと全体的なデザインの一貫性を高めるために、 CSS 変数ライブラリ Open Props を読み込みました。イン 無料の変数のほかに、変数を normalize(正規化)などの buttons です。どちらのボタンも オプションのインポートとして提供されます。これらのインポートにより、インフラストラクチャを ダイアログとデモで 多くのスタイルを必要とせず よいでしょう

<dialog> 要素のスタイル設定

表示プロパティを所有する

ダイアログ要素のデフォルトの表示と非表示の動作で、表示を切り替える プロパティを block から none に変更します。そのためアニメーションは使用できません 出入りできますインとアウトの両方をアニメーション化します。最初のステップは 自分で設定する display プロパティ:

dialog {
  display: grid;
}

たとえば 上記の CSS スニペットの上には、 適切なユーザーエクスペリエンスが 得られるようにする必要がありますまず、ダイアログのデフォルトの状態は 閉じています。この状態を視覚的に表現することで、ダイアログが 次のスタイルのインタラクションを受信:

dialog:not([open]) {
  pointer-events: none;
  opacity: 0;
}

ダイアログが表示されなくなって、開いていないときは操作できなくなりました。それ以降 ダイアログの inert 属性を管理する JavaScript を追加して、 キーボードやスクリーン リーダーを使用するユーザーも、非表示のダイアログにはアクセスできません。

ダイアログにアダプティブ カラーテーマを設定する

ライトモードとダークモードを示すメガ ダイアログで、サーフェスの色が示されています。

一方、color-scheme ではブラウザが提供する ライトとダークのシステム設定に合わせたアダプティブ カラーテーマ それだけではありませんOpen Prop では、いくつかのサーフェス 自動的に調整される色 ライトとダークのシステム設定(color-scheme を使用する場合と同様)。これらの デザインのレイヤを作成するのに最適です 視覚的にサポートします。背景色は var(--surface-1)、このレイヤの上に配置するには、var(--surface-2) を使用します。

dialog {
  
  background: var(--surface-2);
  color: var(--text-1);
}

@media (prefers-color-scheme: dark) {
  dialog {
    border-block-start: var(--border-size-1) solid var(--surface-3);
  }
}

ヘッダーなどの子要素については、後ほどアダプティブ カラーが追加される予定です 設定します。私はダイアログ要素では追加で考慮しますが 魅力的で優れたデザインのダイアログを 作成します

レスポンシブ ダイアログのサイズ設定

ダイアログでは、デフォルトでサイズをそのコンテンツに委任します。 ありがとうございます。ここでの目標は、これらの Pod に max-inline-size 読み取り可能なサイズ(--size-content-3 = 60ch)またはビューポートの幅の 90% に変更します。この モバイル デバイスでダイアログが端から端まで表示されることがなく、 見づらいかもしれません。次に、 max-block-size ダイアログがページの高さを超えないようにします。つまり ダイアログのスクロール可能領域がどこにあるかを指定する必要があります(長い場合)。 使用します。

dialog {
  
  max-inline-size: min(90vw, var(--size-content-3));
  max-block-size: min(80vh, 100%);
  max-block-size: min(80dvb, 100%);
  overflow: hidden;
}

max-block-size が 2 回あることに注目してください。1 つ目は 80vh を使用します。これは物理的な 使用します。私が本当に求めているのは、ダイアログを 海外のユーザーを対象としているため、論理的な新しい 安定化したときの 2 番目の宣言で、dvb 単位をサポートしました。

メガ ダイアログの配置

ダイアログ要素の配置をわかりやすくするために、この 2 つの要素を 2 つの部分があります。全画面表示の背景とダイアログ コンテナです。背景は すべての部分を覆い隠すようにシェード効果を持たせ、このダイアログが コンテンツにアクセスできません。ダイアログ コンテナは、 この背景の中央に重ねて、コンテンツに必要な形状にできます。

次のスタイルでは、ダイアログ要素がウィンドウに固定され、それぞれの位置に引き伸ばされます。 margin: auto を使用してコンテンツを中央揃えします。

dialog {
  
  margin: auto;
  padding: 0;
  position: fixed;
  inset: 0;
  z-index: var(--layer-important);
}
モバイル メガ ダイアログのスタイル

小さいビューポートでは、この全画面のメガモーダルのスタイルが若干異なります。私は 下余白を 0 に設定して、ダイアログのコンテンツを下部に表示します。 作成します。いくつかのスタイルを調整して、ダイアログを アクションシートの近くに置いておきます

@media (max-width: 768px) {
  dialog[modal-mode="mega"] {
    margin-block-end: 0;
    border-end-end-radius: 0;
    border-end-start-radius: 0;
  }
}

余白に重ねて表示されている devtools のスクリーンショット 
  パソコンとモバイルの両方のメガ ダイアログを開いてください。

ミニ ダイアログの配置

デスクトップ パソコンなどの大きなビューポートを使用する場合、ミニダイアログの位置は 呼び出すことができます。そのためには、JavaScript が必要です。こちらの 使用する手法 こちら 残念ながら、この記事の範囲を超えていると思います。JavaScript がなければ、 ミニダイアログがメガダイアログと同様に画面の中央に表示されます。

目立たせる

最後に、ダイアログに彩りを加え、柔らかい表面が遠くにあるように見えるようにします。 。滑らかにするには、ダイアログの角を丸くします。 奥行きは、Open Props の入念に作られたシャドウで表現する props:

dialog {
  
  border-radius: var(--radius-3);
  box-shadow: var(--shadow-6);
}

背景疑似要素のカスタマイズ

背景は軽く操作し、ぼかし効果を追加するのは backdrop-filter 次のメガダイアログに移動します。

対応ブラウザ

  • Chrome: 76。 <ph type="x-smartling-placeholder">
  • Edge: 79。 <ph type="x-smartling-placeholder">
  • Firefox: 103。 <ph type="x-smartling-placeholder">
  • Safari: 18。 <ph type="x-smartling-placeholder">

ソース

dialog[modal-mode="mega"]::backdrop {
  backdrop-filter: blur(25px);
}

また、ブラウザを backdrop-filter に移行することに決めました。 これにより、将来的に背景要素を移行できるようになります。

dialog::backdrop {
  transition: backdrop-filter .5s ease;
}

カラフルなアバターの背景をぼかしたメガダイアログのスクリーンショット。

その他のスタイル設定

このセクションを「エクストラ」と呼んでいます会話要素と関係が強いためです。 ダイアログ要素全般よりも

スクロールの包含

ダイアログが表示されたときもユーザーはページをスクロールできますが、 これを望まないものとして:

通常、 overscroll-behavior 私の通常のソリューションかもしれませんが、 仕様、 スクロール ポートではありません。つまり、 スクローラーを使用するため 防止対策はありませんJavaScript を使用して 新しいイベント(「クローズ」など)も[オープン] を選択し、 ドキュメントに対して overflow: hidden。または、:has() が安定するまで待機できます。 すべてのブラウザ:

対応ブラウザ

  • Chrome: 105。 <ph type="x-smartling-placeholder">
  • Edge: 105。 <ph type="x-smartling-placeholder">
  • Firefox: 121。 <ph type="x-smartling-placeholder">
  • Safari: 15.4。 <ph type="x-smartling-placeholder">

ソース

html:has(dialog[open][modal-mode="mega"]) {
  overflow: hidden;
}

これで、メガ ダイアログが開いているとき、html ドキュメントに overflow: hidden が表示されるようになりました。

<form> レイアウト

やり取りを収集するための非常に重要な要素であるだけでなく、 配置するため、これを使用してヘッダー、フッター、 使用します。このレイアウトでは、article の子を 表示することもできます。これを実現するには grid-template-rows。 記事要素に 1fr が指定されており、フォーム自体の最大値が同じである ダイアログ要素として高さを指定します。固定の高さと固定の行サイズを設定すると、 は、記事要素がオーバーフローした場合に制限してスクロールできるようにします。

dialog > form {
  display: grid;
  grid-template-rows: auto 1fr auto;
  align-items: start;
  max-block-size: 80vh;
  max-block-size: 80dvb;
}

グリッド レイアウト情報を行にオーバーレイしている DevTools のスクリーンショット。

ダイアログ <header> のスタイルを設定する

この要素の役割は、ダイアログの内容とオファーのタイトルを提供することです。 閉じるボタンを見つけやすくします。サーフェスの色も指定されて 記事コンテンツの背景を作りますこうした要件から コンテナ、端から間隔を空けて垂直方向に整列されたアイテム、 パディングとギャップを設定して、タイトルと閉じるボタンに余裕を持たせます。

dialog > form > header {
  display: flex;
  gap: var(--size-3);
  justify-content: space-between;
  align-items: flex-start;
  background: var(--surface-2);
  padding-block: var(--size-3);
  padding-inline: var(--size-5);
}

@media (prefers-color-scheme: dark) {
  dialog > form > header {
    background: var(--surface-1);
  }
}

ダイアログのヘッダーに Flexbox のレイアウト情報を重ねて表示している Chrome DevTools のスクリーンショット。

ヘッダーの閉じるボタンのスタイルを設定する

このデモでは [Open Props] ボタンを使用しているため、閉じるボタンはカスタマイズされています。 アイコン中心の丸いボタンにします

dialog > form > header > button {
  border-radius: var(--radius-round);
  padding: .75ch;
  aspect-ratio: 1;
  flex-shrink: 0;
  place-items: center;
  stroke: currentColor;
  stroke-width: 3px;
}

ヘッダーの閉じるボタンのサイズとパディングの情報をオーバーレイしている Chrome DevTools のスクリーンショット。

ダイアログ <article> のスタイルを設定する

このダイアログでは、記事の要素には特別な役割があります。つまり、 縦に長いダイアログがある場合に スクロールされるようにします

これを実現するために、親フォーム要素では、 それ自体が、記事の要素に制約を与えます。 身長が高すぎます。スクロールバーが必要なときにのみ表示されるように、overflow-y: auto を設定します。 overscroll-behavior: contain を使用してその中にスクロールを表示し、 カスタムのプレゼンテーションスタイルです

dialog > form > article {
  overflow-y: auto; 
  max-block-size: 100%; /* safari */
  overscroll-behavior-y: contain;
  display: grid;
  justify-items: flex-start;
  gap: var(--size-3);
  box-shadow: var(--shadow-2);
  z-index: var(--layer-1);
  padding-inline: var(--size-5);
  padding-block: var(--size-3);
}

@media (prefers-color-scheme: light) {
  dialog > form > article {
    background: var(--surface-1);
  }
}

フッターの役割は、操作ボタンのメニューを含めることです。Flexbox の用途 コンテンツをフッターのインライン軸の最後に配置し、 ボタンにゆとりを持たせます。

dialog > form > footer {
  background: var(--surface-2);
  display: flex;
  flex-wrap: wrap;
  gap: var(--size-3);
  justify-content: space-between;
  align-items: flex-start;
  padding-inline: var(--size-5);
  padding-block: var(--size-3);
}

@media (prefers-color-scheme: dark) {
  dialog > form > footer {
    background: var(--surface-1);
  }
}

Flexbox のレイアウト情報がフッター要素に重なっている Chrome DevTools のスクリーンショット。

menu 要素を使用して、ダイアログの操作ボタンを格納します。ラッピングを使用する ボタン間にスペースを設けるための gap を使用したフレックスボックス レイアウト。メニュー要素 <ul> などのパディングがある。スタイルは不要なため、削除します。

dialog > form > footer > menu {
  display: flex;
  flex-wrap: wrap;
  gap: var(--size-3);
  padding-inline-start: 0;
}

dialog > form > footer > menu:only-child {
  margin-inline-start: auto;
}

Flexbox の情報がフッターのメニュー要素に重なっている Chrome DevTools のスクリーンショット。

アニメーション

ダイアログ要素は多くの場合、ウィンドウに出入りするため、アニメーション化されます。 ユーザーが会話をスタートして離脱するのを助ける動きを示すと、ユーザーが 方向性を理解できません

通常、ダイアログの要素はアニメーションインのみ可能で、アウトできません。その理由は、 ブラウザにより、要素の display プロパティが切り替わります。先ほど説明した display を grid に設定し、none には設定しない。これにより アニメーション表示します。

オープン プロップには多くのキーフレームが付属 アニメーションを自動で作成できるため、 可視化できるようにします。これがアニメーションの目標とレイヤの 次のようなアプローチを採用しました。

  1. 縮小モーションはデフォルトの遷移で、単純な不透明度のフェードインとフェードアウトです。
  2. モーションに問題がなければ、スライドとスケールのアニメーションが追加されます。
  3. メガ ダイアログのレスポンシブ モバイル レイアウトが、スライドアウトするように調整されています。

安全で有意義なデフォルト移行

Open Props にはフェードインとフェードアウト用のキーフレームが用意されているが、こちらの方が好ましい キーフレーム アニメーションのような、 アップグレードの可能性も低くなります。先ほど、ダイアログの表示スタイルを 不透明度。[open] 属性に応じて 1 または 0 をオーケストレートします。宛先 0% から 100% の間で変動すると、ブラウザには、 したいイージング:

dialog {
  transition: opacity .5s var(--ease-3);
}

遷移にモーションを追加する

ユーザーが動きに反応している場合、メガ ダイアログとミニ ダイアログの両方がスライドするようにします。 出口としてスケールアウトしますこれを実現するには prefers-reduced-motion メディアクエリといくつかの Open Prop:

@media (prefers-reduced-motion: no-preference) {
  dialog {
    animation: var(--animation-scale-down) forwards;
    animation-timing-function: var(--ease-squish-3);
  }

  dialog[open] {
    animation: var(--animation-slide-in-up) forwards;
  }
}
<ph type="x-smartling-placeholder">
</ph>

終了アニメーションをモバイル用に調整する

スタイル セクションの前半で、メガ ダイアログのスタイルをモバイルに対応させました。 小さな紙を滑らせたようなアクション シートのように、 画面下部から上に持ち上げられ 下部に固定されます体重計 終了アニメーションはこの新しいデザインに合わないので いくつかのメディアクエリといくつかの OpenProp があります。

@media (prefers-reduced-motion: no-preference) and @media (max-width: 768px) {
  dialog[modal-mode="mega"] {
    animation: var(--animation-slide-out-down) forwards;
    animation-timing-function: var(--ease-squish-2);
  }
}

JavaScript

JavaScript では以下のような多くの要素を追加できます。

// dialog.js
export default async function (dialog) {
  // add light dismiss
  // add closing and closed events
  // add opening and opened events
  // add removed event
  // removing loading attribute
}

こうした機能の追加は、閉じるボタンを軽く閉じるという望み( 背景)、アニメーション、その他のイベントにより、 必要があります。

ライト拒否を追加しています

このタスクは簡単で、 表示されます。ダイアログでのクリックを監視してインタラクションを実現する イベントを活用して 気泡 クリックされたものを評価し close() それが最上位の要素である場合:

export default async function (dialog) {
  dialog.addEventListener('click', lightDismiss)
}

const lightDismiss = ({target:dialog}) => {
  if (dialog.nodeName === 'DIALOG')
    dialog.close('dismiss')
}

dialog.close('dismiss') に注目してください。イベントが呼び出され、文字列が提供されます。 この文字列を他の JavaScript で取得して、 ダイアログを閉じました。また、電話のたびに、締めくくりの文字列も提示しています。 関数をボタンで指定して、コンテキストをアプリケーションに提供します。 制御します。

終了イベントと終了イベントの追加

ダイアログ要素には終了イベントがあり、 ダイアログ close() 関数が呼び出されます。この要素はアニメーション化されているので アニメーションの前後にイベントがあり、 ダイアログ フォームをリセットできます。ここでは、スペースの追加や 閉じたダイアログの inert 属性。このデモでは、これらを使用して変更します。 ユーザーが新しい画像を送信した場合は、アバターのリスト。

そのためには、closingclosed という 2 つの新しいイベントを作成します。その後 ダイアログで組み込みの閉じるイベントをリッスンします。ここでダイアログを inert を実行し、closing イベントをディスパッチします。次のタスクは、 アニメーションと遷移がダイアログでの実行を終了し、 closed イベント。

const dialogClosingEvent = new Event('closing')
const dialogClosedEvent  = new Event('closed')

export default async function (dialog) {
  
  dialog.addEventListener('close', dialogClose)
}

const dialogClose = async ({target:dialog}) => {
  dialog.setAttribute('inert', '')
  dialog.dispatchEvent(dialogClosingEvent)

  await animationsComplete(dialog)

  dialog.dispatchEvent(dialogClosedEvent)
}

const animationsComplete = element =>
  Promise.allSettled(
    element.getAnimations().map(animation => 
      animation.finished))

animationsComplete 関数。これは、トーストを作成する component は、 アニメーションの完了と遷移の約束です。このため、dialogClose async であり、 function; その後、 await Promise が返されて、自信を持って終了イベントに進みます。

オープン イベントとオープン イベントを追加する

これらのイベントは、組み込みのダイアログ要素では追加されないため、追加は簡単ではありません。 「close」と同様に「open」イベントを生成します。使用しているデバイス: MutationObserver ダイアログの属性の変化に関する分析情報を提供します。このオブザーバーで 「open」属性の変更をモニタリングし、カスタム イベントを管理します 必要があります。

「Closed」イベントと「Closed」イベントの開始方法と同様に、2 つの新しいイベントを作成します。 openingopened と呼ばれます。以前にダイアログのリッスンを行った場所を閉じる 今回は作成されたミューテーション オブザーバーを使用して、 属性です。


const dialogOpeningEvent = new Event('opening')
const dialogOpenedEvent  = new Event('opened')

export default async function (dialog) {
  
  dialogAttrObserver.observe(dialog, { 
    attributes: true,
  })
}

const dialogAttrObserver = new MutationObserver((mutations, observer) => {
  mutations.forEach(async mutation => {
    if (mutation.attributeName === 'open') {
      const dialog = mutation.target

      const isOpen = dialog.hasAttribute('open')
      if (!isOpen) return

      dialog.removeAttribute('inert')

      // set focus
      const focusTarget = dialog.querySelector('[autofocus]')
      focusTarget
        ? focusTarget.focus()
        : dialog.querySelector('button').focus()

      dialog.dispatchEvent(dialogOpeningEvent)
      await animationsComplete(dialog)
      dialog.dispatchEvent(dialogOpenedEvent)
    }
  })
})

ミューテーション オブザーバーのコールバック関数は、ダイアログが 属性が変更され、変更のリストが配列として提供されます。反復処理 属性が変更され、attributeName が開いていることを確認します。次に、 要素に属性があるかどうか: これは、ダイアログが するようになりました。すでに開いている場合は、inert 属性を削除し、フォーカスを設定します リクエストする要素または autofocus またはダイアログで最初に見つかった button 要素を返します。最後に、 開始イベントをすぐにディスパッチし、アニメーションを待つ 終了してから、開かれたイベントをディスパッチします。

削除したイベントの追加

シングルページ アプリケーションでは、ルートに基づいてダイアログが追加または削除されることが多く 状態をモニタリングできます。イベントのクリーンアップや 削除したときのデータです。

これは、別のミューテーション オブザーバーで実現できます。今回は、 ダイアログ要素の属性を監視すると、body 要素の子を ダイアログ要素が削除されないか確認します。


const dialogRemovedEvent = new Event('removed')

export default async function (dialog) {
  
  dialogDeleteObserver.observe(document.body, {
    attributes: false,
    subtree: false,
    childList: true,
  })
}

const dialogDeleteObserver = new MutationObserver((mutations, observer) => {
  mutations.forEach(mutation => {
    mutation.removedNodes.forEach(removedNode => {
      if (removedNode.nodeName === 'DIALOG') {
        removedNode.removeEventListener('click', lightDismiss)
        removedNode.removeEventListener('close', dialogClose)
        removedNode.dispatchEvent(dialogRemovedEvent)
      }
    })
  })
})

子が追加または削除されるたびにミューテーション オブザーバー コールバックが呼び出される コピーされます。モニタリング対象のミューテーションは 次がある removedNodes: nodeName/ クリックします。ダイアログが削除された場合、クリックと閉じるイベントは メモリが解放され、カスタム削除済みイベントがディスパッチされます。

読み込み属性の削除

ダイアログ アニメーションが追加されたときに終了アニメーションが再生されないようにするには またはページの読み込み時に、読み込み属性がダイアログに追加されます。「 次のスクリプトは、ダイアログのアニメーションが終了するまで待ってから、 作成します。これでダイアログを自由にアニメーション化でき 注意をそらすアニメーションを効果的に隠すことができます

export default async function (dialog) {
  
  await animationsComplete(dialog)
  dialog.removeAttribute('loading')
}

ページの読み込み時にキーフレーム アニメーションが妨げられない問題の詳細 こちらをご覧ください。

すべて一緒に

各セクションについて説明したので、次は dialog.js の全文です 個別:

// custom events to be added to <dialog>
const dialogClosingEvent = new Event('closing')
const dialogClosedEvent  = new Event('closed')
const dialogOpeningEvent = new Event('opening')
const dialogOpenedEvent  = new Event('opened')
const dialogRemovedEvent = new Event('removed')

// track opening
const dialogAttrObserver = new MutationObserver((mutations, observer) => {
  mutations.forEach(async mutation => {
    if (mutation.attributeName === 'open') {
      const dialog = mutation.target

      const isOpen = dialog.hasAttribute('open')
      if (!isOpen) return

      dialog.removeAttribute('inert')

      // set focus
      const focusTarget = dialog.querySelector('[autofocus]')
      focusTarget
        ? focusTarget.focus()
        : dialog.querySelector('button').focus()

      dialog.dispatchEvent(dialogOpeningEvent)
      await animationsComplete(dialog)
      dialog.dispatchEvent(dialogOpenedEvent)
    }
  })
})

// track deletion
const dialogDeleteObserver = new MutationObserver((mutations, observer) => {
  mutations.forEach(mutation => {
    mutation.removedNodes.forEach(removedNode => {
      if (removedNode.nodeName === 'DIALOG') {
        removedNode.removeEventListener('click', lightDismiss)
        removedNode.removeEventListener('close', dialogClose)
        removedNode.dispatchEvent(dialogRemovedEvent)
      }
    })
  })
})

// wait for all dialog animations to complete their promises
const animationsComplete = element =>
  Promise.allSettled(
    element.getAnimations().map(animation => 
      animation.finished))

// click outside the dialog handler
const lightDismiss = ({target:dialog}) => {
  if (dialog.nodeName === 'DIALOG')
    dialog.close('dismiss')
}

const dialogClose = async ({target:dialog}) => {
  dialog.setAttribute('inert', '')
  dialog.dispatchEvent(dialogClosingEvent)

  await animationsComplete(dialog)

  dialog.dispatchEvent(dialogClosedEvent)
}

// page load dialogs setup
export default async function (dialog) {
  dialog.addEventListener('click', lightDismiss)
  dialog.addEventListener('close', dialogClose)

  dialogAttrObserver.observe(dialog, { 
    attributes: true,
  })

  dialogDeleteObserver.observe(document.body, {
    attributes: false,
    subtree: false,
    childList: true,
  })

  // remove loading attribute
  // prevent page load @keyframes playing
  await animationsComplete(dialog)
  dialog.removeAttribute('loading')
}

dialog.js モジュールの使用

モジュールからエクスポートされた関数は、呼び出されてダイアログが渡されることが想定されています。 新しいイベントと機能を追加する要素です。

import GuiDialog from './dialog.js'

const MegaDialog = document.querySelector('#MegaDialog')
const MiniDialog = document.querySelector('#MiniDialog')

GuiDialog(MegaDialog)
GuiDialog(MiniDialog)

2 つのダイアログがアップグレードされ、軽い閉じるアニメーションとアニメーションが追加されます。 修正、処理するイベントを読み込んでいます。

新しいカスタム イベントをリッスンする

アップグレードされた各ダイアログ要素で、次のような 5 つの新しいイベントをリッスンできるようになりました。

MegaDialog.addEventListener('closing', dialogClosing)
MegaDialog.addEventListener('closed', dialogClosed)

MegaDialog.addEventListener('opening', dialogOpening)
MegaDialog.addEventListener('opened', dialogOpened)

MegaDialog.addEventListener('removed', dialogRemoved)

これらのイベントの処理例を 2 つ紹介します。

const dialogOpening = ({target:dialog}) => {
  console.log('Dialog opening', dialog)
}

const dialogClosed = ({target:dialog}) => {
  console.log('Dialog closed', dialog)
  console.info('Dialog user action:', dialog.returnValue)

  if (dialog.returnValue === 'confirm') {
    // do stuff with the form values
    const dialogFormData = new FormData(dialog.querySelector('form'))
    console.info('Dialog form data', Object.fromEntries(dialogFormData.entries()))

    // then reset the form
    dialog.querySelector('form')?.reset()
  }
}

ダイアログ要素を使って作成したデモでは、この closed イベントを使用し、 フォームのデータをコピーして、新しいアバター要素をリストに追加します。適切な時期は 終了アニメーションを完了した時点で、一部のスクリプトが 新しいアバターに移動します。新しいイベントのおかげで ユーザーエクスペリエンスの スムーズになります

dialog.returnValue: これには、 ダイアログの close() イベントが呼び出されます。dialogClosed イベントでは、 ダイアログが閉じられた、キャンセルされた、確認されたことを知らせる。確認された場合、 スクリプトはフォームの値を取得して、フォームをリセットします。このリセットは ダイアログが再度表示されたら空白になり、新しい送信の準備が整いました。

まとめ

どのようにやり方をしたかわかったので、どのように感じますか? ‽ 🙂?

アプローチを多様化して、ウェブで構築するすべての方法を学びましょう。

デモを作成し、ツイートしてリンクを送ってください 下の [コミュニティリミックス]セクションに アクセスしてください

コミュニティ リミックス

リソース