ツールバーのオーバーフロー検出を `clientWidth` ベースに刷新
scrollWidth と独自計算に依存していたツールバーのオーバーフロー判定を、clientWidth / offsetWidth を使ったシンプルな実装に置き換えました。シングルパス設計は維持され、強制レイアウト(Forced Layout)の発生を防ぐ設計方針が引き継がれています。
背景
ツールバーのオーバーフロー検出は、表示領域に収まらないボタンを「オーバーフローメニュー」へ移動する仕組みの核心です。変更前の実装では、#isToolbarOverflowing() による事前チェックと #toolbarOverflowAmount() による溢れ幅の計算という2段階のアプローチが取られていました。これらは scrollWidth や独自の幅積算に依存していたため、コードパスが複雑化していました。
PR の説明によれば、clientWidth と scrollWidth の比較でオーバーフローを判定し、各ボタンの幅取得には offsetWidth を直接使う方式に切り替えることで、この複雑さを解消しています。なお、ボタン移動のたびに幅を再計算すると強制レイアウトが発生するため、シングルパスで処理する設計は意図的に維持されています。
技術的な変更
#compactMenu() メソッドの責務が分割され、オーバーフロー幅の取得とボタン回収が独立したメソッドに切り出されました。
変更前:
#compactMenu() {
if (!this.#isToolbarOverflowing()) return
this.#overflow.style.display = "block"
const overflowAmount = this.#toolbarOverflowAmount() + 1
const buttons = this.#overflowButtons
const overflowButtons = []
let recoveredWidth = 0
while (recoveredWidth < overflowAmount) {
const button = buttons.pop()
if (!button) break
// ...
}
}
変更後:
#compactMenu() {
this.#showOverflowMenu()
const overflowWidth = this.#getOverflowWidth()
if (overflowWidth > 0) {
this.#reclaimWidth(overflowWidth)
}
}
#showOverflowMenu() {
this.#overflowMenuButton.style.display = "block"
}
#reclaimWidth(overflowWidth) {
const gap = this.#getToolbarGap()
const buttons = this.#overflowableButtons
const overflowButtons = []
let recoveredWidth = 0
while (recoveredWidth < overflowWidth && buttons.length) {
const button = buttons.pop()
if (!button) break
// ...
}
}
変更前は #isToolbarOverflowing() で事前チェックを行い、true の場合のみ処理を続行していました。変更後は常に #showOverflowMenu() でオーバーフローメニューボタンを表示した上で #getOverflowWidth() で溢れ幅を取得し、overflowWidth > 0 の場合のみ #reclaimWidth() を呼び出す構造になっています。
DOM要素の参照も整理されており、this.#overflow が this.#overflowMenuButton に、this.#overflowMenu が this.#overflowMenuDropdown に改名されています。また、ボタンのコレクションも #overflowButtons から #overflowableButtons へとリネームされ、意味がより明確になっています。
while ループの終了条件にも改善が加えられており、変更前の buttons.length チェックなしの実装から、recoveredWidth < overflowWidth && buttons.length という二重条件に変わっています。これにより、ボタンが枯渇した場合の pop() の失敗を if (!button) break に頼らずとも、ループ条件で安全に制御できるようになっています。
設計判断
シングルパス設計の維持が本PRで最も重要な設計判断です。PR説明に「forced layouts を防ぐためシングルパス設計を維持した」と明示されており、ボタン移動のたびに幅を再計算しないことが意図的なトレードオフとして選択されています。
オーバーフロー判定の方式として clientWidth vs scrollWidth の比較が採用された点も注目に値します。scrollWidth はスクロール可能なコンテンツ全体の幅を返すのに対し、clientWidth はパディングを含む表示領域の幅を返します。この2つを比較することで、コンテナに収まりきらないコンテンツの有無をシンプルに判定できます。また、各ボタンの幅取得に offsetWidth を使用することで、マージン・ボーダーを含む実際のレイアウト上の幅を正確に取得しています。
メソッドの細分化(#compactMenu → #showOverflowMenu + #getOverflowWidth + #reclaimWidth)は、単一責任の原則に沿った整理であり、コードの削減(52行削除、30行追加)とともに、各ステップの意図をより明確にしています。
まとめ
ブラウザ標準の clientWidth / offsetWidth を活用することで、独自の幅計算ロジックを排除し、コードを大幅に簡素化しました。DOM 要素名の整理と合わせて可読性が向上しており、強制レイアウトを回避するシングルパス設計という核心的な制約を守りながら、実装の複雑さを下げることに成功しています。