概要
左下に表示されるフローティングメニューのリンクを、管理画面から自由に設定できる機能を実装した。実装自体はほぼ完成したが、管理ページのレイアウトがどうしても左寄りになる問題が最後まで残り、原因の特定に手間取った。最終的にはブラウザ拡張機能が html 要素と body 要素に外部から CSS を注入していたことが判明した。
実装内容
Astro ページの自動検出
追加できるページの候補を手動でリストアップするのは管理コストが高い。そこで Vite の import.meta.glob を利用し、ビルド時に src/pages/ 以下の .astro ファイルを自動列挙する方式を採用した。
const pageModules = import.meta.glob('/src/pages/**/*.astro');
取得したファイルパスを URL に変換し、パスの構造からラベルを自動推測して表示する。ラベルは追加前に編集できるようにしてある。
データ保存
新しいテーブルやマイグレーションは不要。既存のキーバリュー型の設定テーブルに JSON 形式で保存することで、スキーマ変更なしに実装できた。
タッチ・マウス両対応のドラッグ&ドロップ

HTML5 の Drag API はモバイルで動作しない。そのため、mousedown / mousemove / mouseup と touchstart / touchmove / touchend を個別に実装した。
ドラッグ開始の誤検知を防ぐため、マウスボタンを押した時点ではなく、5px 以上移動した時点でドラッグを開始するしきい値を設けている。これにより、単純なクリックでゴースト要素が出現する問題も解消した。
// 5px のしきい値を超えたときだけドラッグ開始
const dx = e.clientX - startX;
const dy = e.clientY - startY;
if (dx * dx + dy * dy < 25) return;
Astro のスコープ CSS と動的生成要素
Astro の <style> タグは自動的にスコープ処理され、テンプレート内の要素には data-astro-cid-xxxx 属性が付与される。しかし JavaScript の createElement() で生成した要素にはこの属性が付かないため、スコープ CSS が届かない。
ドラッグ中に表示するゴースト要素や、並べ替えリストの各行など、JavaScript で動的に生成する要素のスタイルには <style is:global> を使用した。
中央配置が効かない問題
管理ページのコンテナを画面中央に配置しようとして、margin: 0 auto や display: flex; justify-content: center など複数の方法を試したが、いずれも左寄りのままだった。
当初は Astro のスコープ処理が原因と疑ったが、同じ構造の他の管理ページでは問題が起きていなかった。インラインスタイルで margin-left: auto !important を直接記述しても変わらない。
解決の糸口
Chrome DevTools の Computed タブで html 要素のスタイルを確認したところ、width: 2560px という固定値が当たっていた。
html {
width: 2560px; /* ← 意図しない固定値 */
overflow-x: hidden;
}
body {
max-width: 1200px; /* ← ブラウザ拡張が追加 */
font-family: sans-serif;
background-color: #f9f9f9;
color: #4a3f35;
}
body に当たっていた background-color: #f9f9f9 と color: #4a3f35 は、こちらで書いた CSS とは別の値だった。ブラウザ拡張機能が html と body に外部スタイルを注入していたことがわかった。
html が 2560px(スクリーンの物理ピクセル幅と一致)に固定されているため、ビューポートの外に大きくはみ出している。overflow-x: hidden でその横スクロールが隠されているため、一見正常に見えた。body が max-width: 1200px で制限され、左端から配置されることで、コンテナを中央寄せしても「分割されたように見える」状態になっていた。
修正方法
is:global の CSS で !important を使い、拡張機能によるスタイルを上書きした。
html {
width: 100% !important;
max-width: 100% !important;
overflow-x: auto !important;
}
body {
display: block !important;
width: 100% !important;
max-width: none !important;
}
この修正でレイアウトが正しく中央配置された。
学び
- CSS が効かないとき、外部注入を疑う: インラインスタイルや
!importantでも効かない場合、ブラウザ拡張が DOM に手を加えている可能性がある。DevTools の Computed タブで、自分が書いていない CSS プロパティが当たっていないか確認する。 html要素の幅は盲点になりやすい: body や container のスタイルだけ見ていると、html 要素が原因の問題に気づきにくい。レイアウト崩れの調査は html から順に見ていく。import.meta.globはページ自動検出に有効: Cloudflare Workers のランタイムにはファイルシステムアクセスがないため、ページ一覧の取得はビルド時に完結する Vite の機能を使う必要がある。