ループ中の carousel が非表示タブ内で誤表示するバグを修正
リード文
<wa-carousel> の loop 属性が有効な状態で、非アクティブなタブパネル内に初期化された場合に、ロード直後に誤ったスライドが一瞬表示されるバグを解消しました。これにより、タブ切り替え時の視覚的ブリックがなくなり、期待通りのスライドが初回表示されます。
背景
このバグは、非表示コンテナ内でループ carousel が初期化された際に発生していました。loop が有効なときは、先頭と末尾にクローンスライドが配置され、内部で自動的にスクロールして正しいスライドへ移動します。非表示状態(例: 非アクティブな <wa-tab-panel>)では要素の幅・高さが 0 になるため、スクロールが実行されず最後のクローンスライドが表示されることが報告されました#1163。
結果として、ページネーションのドットは最初がアクティブでも、コンテンツは最後のスライドが一瞬見えてしまうという不整合が起きました。ユーザーは「スライドがフラッシュした」ように感じ、操作感が損なわれました。この問題はテストランナーでは再現できなかったため、手動テストでのみ確認されていました。
技術的な変更
CSS 側で表示制御を導入し、誤表示を防止しました。packages/webawesome/src/components/carousel/carousel.styles.ts に以下のスタイルを追加し、.slides と .pagination にフェードトランジションを設定しました。
.slides,
.pagination {
transition: opacity var(--wa-transition-fast) ease;
}
.slides-awaiting-position,
.pagination-awaiting-position {
opacity: 0;
transition: none;
}
コンポーネントロジックに状態フラグと可視化タイミングを組み込みました。WaCarousel クラスに @state() awaitingInitialPosition = false; を追加し、firstUpdated() で非表示で開始したか判定してフラグを立てます。
// 追加された状態プロパティ
@state() awaitingInitialPosition = false;
ResizeObserver のコールバックで位置決定と表示切替を実装しました。コンテナが可視化された瞬間に goToSlide(this.activeSlide, 'auto') で正しい位置にスクロールし、二重 requestAnimationFrame 後に awaitingInitialPosition を false に戻すことで、スライドとページネーションがフェードインします。
const startedHidden = this.loop && !this.scrollContainer?.clientWidth && !this.scrollContainer?.clientHeight;
if (startedHidden) {
this.awaitingInitialPosition = true;
}
...
this.resizeObserver = new ResizeObserver(() => {
if (this.scrollContainer?.clientWidth || this.scrollContainer?.clientHeight) {
this.goToSlide(this.activeSlide, 'auto');
this.synchronizeSlides();
this.resizeObserver?.disconnect();
this.resizeObserver = undefined;
if (this.awaitingInitialPosition) {
requestAnimationFrame(() => {
requestAnimationFrame(() => {
this.awaitingInitialPosition = false;
});
});
}
}
});
結果として、非表示タブ内で loop 付き carousel が表示されるときにスライドがフラッシュしなくなります。CSS の透明度切替と JavaScript の状態管理が組み合わさり、ユーザーに見えるまでに正しい位置へ自動スクロールが完了します。
設計判断
表示制御は CSS に委譲し、ロジックは最小限に留めました。awaitingInitialPosition というシンプルなブールフラグだけで状態遷移を管理し、余計なレイアウト計算やフラグの増殖を防いでいます。この選択は、既存のコンポーネント構造への侵入度を低く保つという設計意図に沿っています。
可視化のタイミングは ResizeObserver と requestAnimationFrame で正確に捕捉しました。要素が実際に描画可能になるまで待機し、二重フレームで状態を解除することで、ブラウザのペイントサイクルと同期させています。これにより、フラッシュが残る余地を排除しつつ、追加テストを書かずに実装できました。
トレードオフとして、テスト自動化が未実装である点が残ります。バグは手動で再現できるが CI 上では検証できないため、将来的に視覚的回帰テストを導入する余地があります。それ以外は、機能追加や他コンポーネントへの影響を最小化できた点が評価できます。
まとめ
この PR は、loop が有効な <wa-carousel> が非表示タブ内で初期化された際に起きる「誤スライドフラッシュ」バグを、CSS の透明度制御とコンポーネント内部の状態管理で解消しました。最小限の侵入でユーザー体験を改善し、既存 API との互換性も維持しています。今後は同様の可視性依存不具合を防ぐテストパターンを追加すると、品質保証がさらに向上します。