404ページに <portal> 要素を導入

最近、自分のTLで 404 ページのことが話題になっていますが、それとは関係なくこのサイトの 404 ページを改良し、<portal> 要素(wicg.github.io) で親階層ページのイメージを挿入するようにしてみました。

どんな感じかは https://w0s.jp/404 を参照[1]

Chrome Canary で 404 ページを表示した様子

なお、 <portal> 要素は2019年9月5日現在、正式に対応したブラウザはありません。 Chrome Canary 78 で chrome://flags/#enable-portals を有効にした状態で使えるようになります。

  • <portal> 要素を導入したのは単純に自分の技術的興味からであり、 404 ページに <portal> を導入することがユーザビリティ的に有効であるとか、そういう統計や確証があるわけではありません。

<portal> 要素の簡単な使い方として、例えば「<portal> の表示部分をクリックしたら遷移する」という挙動にする場合、以下のようにします。

<portal src="https://example.com/"></portal>
<script>
document.querySelector('portal').addEventListener('click', (ev) => {
  ev.target.activate();
});
</script>

以上、終わり。と言いたいところですが、せっかくなので以下の2点を実装してみました。

  • アクセシビリティ上の問題を改善
  • ページ遷移時のアニメーションを実装(サムネイルが徐々に拡大していき、全画面表示になるイメージ)

現状、 Chrome Canary での <portal> 要素はまだ不安定な状況のようで、アクセシビリティ上の問題をいくつか抱えています。Steve Faulkner 氏が問題点のまとめとアクセシブルな実装サンプル(codepen.io) を公開されているので要約します。

  • キーボードでアクセスできない → tabindex="0" を付与したうえで Tab キー操作すると、なぜか <portal> 自体ではなく、その中(src属性で指定したコンテンツ)のフォーカス可能要素にフォーカスが移る
  • アクセシビリティツリーで有用なセマンティクスが公開されない → role="link" が適切ではないかとのこと
  • <portal> のページを開く(activate() で遷移する)と、ブラウザの戻るボタンが無効になり、ユーザーは元のページに戻ることができない

2020年1月10日追記戻るボタンが無効になるバグは Chrome 80 で解消されていました(80.0.3987.42 beta で確認)。

2020年1月21日追記キーボードでアクセスできないバグは Chrome 81 canary で解消されていました(81.0.4034.0 canary で確認)。

とくに「戻るボタン」が使えない事象は致命的なもので、これはブラウザ側の対応や仕様のアップデートを待ちたいと思いますが、それ以外の2点は下記のように <div> 要素で囲んで tabindex と WAI-ARIA の属性を設定することで改善できます。

<div tabindex="0" role="link" aria-label="遷移先のページタイトル">
  <portal src="https://example.com/"></portal>
</div>

2020年1月21日追記前述のように Chrome 81.0.4034.0 canary では <portal> にフォーカスがされるように改善されており、ラッパー要素に tabindex="0" を設定すると2重にフォーカスが当たってしまうことになるので、早くもこのコードはデメリットのあるバッドノウハウになりつつあります。今のところ、同じバージョン81でも dev チャンネルの 81.0.4029.3 では tabindex="0" が必要です。

ただ、このようなテクニックは将来的には(ブラウザ側が対応して)不要になるかもしれないので、カスタム要素を使ってHTML側はシンプルにしてみました。

アニメーションもカスタム要素内で定義しています。徐々に拡大する動きは CSS の transition で行い、transitionend イベントを検知して <portal>activate() することでアニメーション終了後に遷移するようにしています。

TODO

§

  • 前述のとおり、ブラウザの戻るボタンが使えない件はブラウザ側で対応されることを期待して放置。The PortalActivateEvent interface(wicg.github.io) とか使って頑張れば対応可能だったりする?
  • prefers-reduced-motion: reduce 時はアニメーションをやめた方が良いか。その場合、代替の何かを設定すべき? なにもわからない。

脚注

  • 1.

    リンクしたいところですが、404ページにおいてリファラーが同じドメインならサイト内でリンク切れが発生していると判定して通知が来るようにしているので、下手に <a href> でリンクを設定することはできないのです。こういうときreferrerpolicy属性(WHATWG) が有用なのですが、このブログにその属性を設定する機能はないので……。 ↩ 戻る