アニメーション期間0msの場合のイベント発火問題を修正
アニメーション期間が0msに設定されている場合、animationendイベントが発火せず、ダイアログのイベントハンドリングが正常に動作しない問題が修正されました。この変更により、prefers-reduced-motionなどでアニメーションを無効化する場合でも、コンポーネントが正しく動作するようになります。
背景
--show-duration をアクセシビリティ対応のために0msに設定した場合、特にWindows環境で問題が発生していました。アニメーション期間が0msの場合、ブラウザはanimationendイベントを発火させないため、animateWithClass関数内のPromiseが永久に解決されない状態になっていました。この問題により、ダイアログの表示・非表示に関連するイベントリスナーが適切に動作せず、後続の処理がブロックされる状態でした。
従来のコードには「failsafeの追加が必要」というTODOコメントがありましたが、具体的な実装がなされていませんでした。アクセシビリティ要件としてモーション削減を設定する環境が増える中、この問題への対応が求められていました。
技術的な変更
packages/webawesome/src/internal/animate.ts の animateWithClass 関数に、アニメーションが実際に存在するかをチェックする機構が追加されました。
変更前:
let onEnd = () => {
el.classList.remove(className);
resolve();
controller.abort();
};
el.addEventListener('animationend', onEnd, { once: true, signal });
el.addEventListener('animationcancel', onEnd, { once: true, signal });
// TODO add failsafe if neither of these fires
変更後:
let resolved = false;
let onEnd = () => {
if (resolved) {
return;
}
resolved = true;
el.classList.remove(className);
resolve();
controller.abort();
};
el.addEventListener('animationend', onEnd, { once: true, signal });
el.addEventListener('animationcancel', onEnd, { once: true, signal });
// if there are no animations or animation is set to 0ms, end immediately
requestAnimationFrame(() => {
if (!resolved && el.getAnimations().length === 0) {
onEnd();
}
});
変更の中核は requestAnimationFrame コールバック内での el.getAnimations() チェックです。requestAnimationFrameを使用することで、ブラウザが次のフレームを描画する前にアニメーション状態を確認できます。この時点でアニメーションが存在しない場合、即座にonEndを呼び出してPromiseを解決します。
resolvedフラグの導入により、onEndが複数回呼び出されても副作用が発生しないようガードされています。これはanimationendイベントとrequestAnimationFrameのコールバックが競合する可能性に対する防御策です。
設計判断
requestAnimationFrameによるポーリング方式 が採用されました。
アニメーション期間0msの検出方法として、CSS変数の値を直接読み取る方法やタイムアウトベースの実装も考えられましたが、getAnimations()を使用する方式が選ばれています。この API は Web Animations API の標準メソッドであり、ブラウザが実際に適用しているアニメーション状態を正確に反映します。
requestAnimationFrameの使用により、CSSクラスが適用された直後ではなく、ブラウザがスタイル計算を完了した後のタイミングでチェックが実行されます。これは、CSSクラス追加直後はまだアニメーションが評価されていない可能性があるためです。
TODOコメントの削除は、この実装がアニメーションイベントが発火しないすべてのケース(期間0ms、アニメーション未定義、display: none要素など)をカバーすることを示しています。
まとめ
本PRは、アニメーション期間0msのエッジケースに対する堅牢性を向上させた変更です。getAnimations()による実行時チェックとresolvedフラグによる冪等性の保証により、アクセシビリティ設定に関わらずコンポーネントが一貫して動作するようになりました。Windows環境での実地テストも完了しており、prefers-reduced-motion設定下でも安定した動作が期待できます。