`<wa-zoomable-frame>`にテーマ同期機能を追加するオプト・イン属性
<wa-zoomable-frame>にwith-theme-sync属性が追加され、ホストページのライト/ダークモードやテーマクラスをiframe内に自動的に反映できるようになりました。デフォルトでは同期が無効なオプト・イン設計により、既存の利用箇所への影響を排除しています。
背景
これまで<wa-zoomable-frame>はiframeのコンテンツをホストページのテーマと独立して表示しており、Web Awesomeのスタイルを使ったコンテンツをiframe内で描画する場合にテーマの不一致が生じていました。ホストページでwa-darkクラスやwa-theme-*のようなテーマセレクタクラスが切り替わっても、iframe側のドキュメントには反映されない状態でした。
ドキュメントのサンプルURLもhttps://webawesome.com/から/examples/themes/showcaseというローカルのテーマショーケースページに変更されています。これは、テーマ同期の動作確認に適したページを例示することで、新機能の効果をドキュメント上でも示す意図があると読み取れます。
技術的な変更
with-theme-sync属性の追加
zoomable-frame.tsにwithThemeSyncプロパティが追加され、with-theme-sync属性としてHTML上で指定できるようになりました。属性の有無で以下の2つの仕組みが連動して起動・停止します。
-
MutationObserver:document.documentElementのclassリストの変化を監視し、変化のたびにsyncTheme()を呼び出す -
ColorSchemeController: CSSトランジションを利用してテーマ変更を検出するReactive Controller
withThemeSyncが変化した際の処理はupdated()ライフサイクル内に実装されています。
if (changedProperties.has('withThemeSync')) {
if (this.withThemeSync) {
this.themeObserver.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });
this.syncTheme(); // Apply immediately when toggled on
} else {
this.themeObserver.disconnect();
}
}
with-theme-syncがオンになった瞬間にsyncTheme()を即時呼び出すことで、属性追加時点のテーマ状態をiframe側に即座に適用します。
ColorSchemeControllerの新設
src/internal/color-scheme-controller.tsとして新たなReactive Controllerが追加されました。このControllerはCSSカスタムプロパティ--wa-color-surface-defaultをcolorとして参照する非表示のdiv要素をコンポーネントに追加し、テーマ変更時に発生するCSSトランジションのtransitionendイベントを利用してコールバックを呼び出します。
this.hiddenElement = document.createElement('div');
this.hiddenElement.setAttribute('aria-hidden', 'true');
Object.assign(this.hiddenElement.style, {
position: 'absolute',
width: '0',
height: '0',
overflow: 'hidden',
pointerEvents: 'none',
opacity: '0',
color: 'var(--wa-color-surface-default, transparent)',
transition: 'color 0.001ms',
});
このアプローチは、テーマ変更をクラス名の変化ではなくCSSカスタムプロパティの値変化として検出する点が特徴です。MutationObserverだけではカバーできない、CSSカスタムプロパティレベルのテーマ変化に対応するために使われています。
テスト
zoomable-frame.test.tsにテーマ同期に関するテストスイートが追加されました。iframeのsrcdocが実際にロードされるまで待機するユーティリティ関数waitForIframe()を定義し、以下のケースを検証しています。
-
with-theme-syncなし(デフォルト)では、ホストのクラスがiframeに伝播しないこと -
with-theme-syncありでは、ロード時点でホストのクラスがiframeに同期されること - デフォルト状態では、ホストのクラスが後から変わってもiframeに反映されないこと
function waitForIframe(el: WaZoomableFrame) {
return waitUntil(() => el.contentDocument?.URL === 'about:srcdoc' && el.contentDocument.readyState === 'complete');
}
iframeがabout:blankからabout:srcdocに遷移したことを確認することで、srcdocコンテンツが実際にロードされたことを厳密に検証しています。
設計判断
同期機能をデフォルト無効のオプト・インとして設計した点が重要な判断です。外部URLを表示する用途では、ホストのクラスをiframeに注入することは不要なだけでなく、クロスオリジンのiframeへのアクセスはブラウザのセキュリティポリシーによりブロックされます。デフォルト無効にすることで、既存の利用箇所を壊さずに機能を追加できています。
テーマ変更の検出に2つの仕組みを組み合わせている点も注目されます。MutationObserverはclassリストの変化(wa-darkやwa-theme-*の付け外し)を捉え、ColorSchemeControllerはCSSカスタムプロパティの値変化をトランジションで検出します。前者だけではCSSカスタムプロパティのみが変わるケースを取りこぼす可能性があるため、両者を補完的に使う構成になっています。
まとめ
with-theme-sync属性の追加により、<wa-zoomable-frame>はWeb Awesomeのテーマ体系に統合されたプレビューコンポーネントとして機能するようになりました。CSSトランジションを活用したテーマ変更検出という手法は、ColorSchemeControllerとして汎用的なReactive Controllerに切り出されており、他のコンポーネントへの応用も容易な設計です。