ChatGPTUIで要素の正しいY座標を取得する方法(2026年版)

Table of Contents
問題点
ChatGPT用のブラウザ拡張機能を開発していたとき、ある問題にぶつかりました。チャット内の特定のメッセージまでスクロールする必要があったのですが、私は他の開発者と同じように、まず以下のコードを試しました。
window.scrollY
しかし、結果は毎回ゼロでした。
次に、お決まりのコードを試しました。
element.getBoundingClientRect().top + window.scrollY
それでもうまくいきません。座標はめちゃくちゃで、拡張機能は使い物になりませんでした。
なぜいつも0なのか?
ChatGPTのようなモダンなSPAは、ページ自体をスクロールさせません。<body>は高さ100%に固定されています。スクロールはDOMツリー内のどこかにあるネストされたコンテナ内で発生します。そのため、window.scrollYは永遠に0のままで、計算はサイレントに失敗します。
このデバッグにかなりの時間を費やしてしまいました。しかし、本当の答えは私が思っていたよりもシンプルでした。
真のスクロールコンテナを見つける
ページ自体はスクロールしません。ページ内の何かがスクロールしているのです。それを見つける必要があります。
私がうまくいったアプローチは次のとおりです。まず、対象の要素を取得します。
const el = document.querySelector("div.whitespace-pre-wrap");
次に、実際にスクロールを処理している親要素を見つけるまで、DOMツリーを遡ります。
function findScrollParent(element) {
let parent = element.parentElement;
while (parent) {
const style = getComputedStyle(parent);
if (/(auto|scroll)/.test(style.overflowY)) return parent;
parent = parent.parentElement;
}
return document.documentElement;
}
const container = findScrollParent(el);
この関数は、各親要素の計算されたoverflowYスタイルをチェックします。autoまたはscrollを持つ最初の要素がスクロールコンテナです。ChatGPTでは、通常、.overflow-y-autoのようなクラスを持つ<div>がこれに該当します。
コンテナが見つかれば、実際のY座標の計算は簡単です。
const y = el.getBoundingClientRect().top - container.getBoundingClientRect().top + container.scrollTop;
要素の位置からコンテナのオフセットを減算し、コンテナがスクロールした距離を足し戻します。これにより、要素のコンテナのトップに対する相対位置が得られます。これこそが、そもそもあなたが求めていたものです。
視覚的にテストする
計算したY座標に赤い線を描画して、完全に一致するか確認してください。
const marker = document.createElement("div");
marker.style.cssText = `position: absolute; left: 0; right: 0; background: red; height: 2px; top: ${y}px`;
document.body.appendChild(marker);
線がターゲット要素の真上に表示されれば、正しく計算できています。
なぜSPAはこのようなことをするのか?
これは偶然ではありません。ReactのようなフレームワークやTailwindのようなライブラリは、フルハイトレイアウトを推奨しています。パターンはシンプルです。ルート要素がビューポート全体を占め、各セクションが独自のオーバーフローを管理します。
考えてみてください。ページ全体がスクロールする場合、ナビゲーションバー、サイドバー、モーダルはスクロールと競合します。下にスクロールするとサイドバーは消えてしまいます。固定ヘッダーは、その場に留まるために追加のCSSハックが必要になります。スクロールコンテナをネストすることで、UIの各セクションが独立性を保ちます。
ChatGPTはこれを極端に推し進めています。サイドバーは独立してスクロールします。チャットペインは独立してスクロールします。設定パネルも独立してスクロールします。これは合理的な設計上の選択ですが、ウェブの初期から存在していた前提を打ち破るものです。
これはChatGPTに限った話ではない
GitHub、Discord、Linear、Notion、そしてほとんどすべてのReactベースのSPAで、同じ問題に遭遇するでしょう。このパターンを知っていれば、どこでもそれを見かけるようになるはずです。
これが実際に必要になる場面
私はブラウザ拡張機能を開発しているときにこれに遭遇しました。しかし、SPAで正確なY座標が必要になる他のケースもあります。
PlaywrightやCypressのようなUI自動化ツールは、要素を操作する前に要素までスクロールする必要があることがよくあります。スクロール検出にwindow.scrollYを使用している場合、同じように失敗します。
カスタムツールチップやポップオーバーの配置は、間違ったコンテナに対して位置を計算すると壊れます。ドロップダウンが本来あるべき場所から2000ピクセルも上にレンダリングされているのを見たことがあります。
現在表示されているセクションをハイライトするためのスクロールスパイロジック。実際に何が見えているかを知るには、真のコンテナが必要です。
フルページコンテンツをキャプチャするスクリーンショットツール。ツールがネストされたスクロールコンテナについて知らない場合、ビューポートに収まる部分のスクリーンショットしか得られません。
この問題に直面しているかどうかをすばやく確認する方法は次のとおりです。
// If this returns 0, you've got a nested scroll container
console.log("scrollY:", window.scrollY, "body height:", document.body.scrollHeight);
動的コンテンツについてはどうですか?
ChatGPTはストリーミングアプリです。ページを見ている間に新しいメッセージが表示されます。要素が追加、削除、移動されます。
特定の要素の位置を時間の経過とともに追跡している場合、一度計算して値をキャッシュしないでください。スクロール時とDOM変更時に再計算してください。スクロールイベントリスナーと組み合わせたMutationObserverは、両方のケースをカバーします。
const observer = new MutationObserver(() => {
const y = el.getBoundingClientRect().top - container.getBoundingClientRect().top + container.scrollTop;
// do something with the updated y
});
observer.observe(container, { childList: true, subtree: true });
レイアウトがシフトする場合、コンテナ上のResizeObserverも役立ちます。私は、両方のオブザーバーをスクロールハンドラーのスロットルと組み合わせることで、遭遇したすべてのエッジケースをカバーできることを見出しました。
これはあなたが思っている以上に重要です。ChatGPTは、上にスクロールするとメッセージを遅延ロードします。追跡している要素の上に新しいコンテンツがロードされると、その位置が変わります。オブザーバーがないと、古い座標を指すことになります。
パフォーマンスに関する警告
MutationObserverは頻繁に発火します。コールバック内で位置を測定している場合は、スロットルをかけてください。そうしないと、UIのスクロールパフォーマンスが低下し、スクロールの途切れほどデモを台無しにするものはありません。
要するに
覚えておくべき3つのこと:
window.scrollYは、document自体がスクロールする場合にのみ機能します。SPAでは、通常そうではありません。- DOMを遡り、
overflowYをチェックして真のコンテナを見つけます。 getBoundingClientRect().top - container.getBoundingClientRect().top + container.scrollTopで位置を計算します。
それだけです。魔法はありません。フレームワークのトリックもありません。モダンなSPAでDOMが実際にどのように機能するかを理解するだけです。windowがスクロールターゲットであるという仮定をやめた途端、すべてがうまくいくようになりました。
こちらもおすすめ
Free In-Browser Developer Tools
Clean AI CLI logs, build cron expressions, decode JWTs, and calculate chmod permissions offline.
Related Articles

Next.jsにおけるsuppressHydrationWarning: 安全な利用法とデバッグの完全ガイド
Next.jsのsuppressHydrationWarningについて、安全な利用法とデバッグ方法を実証済みの本番環境での例を交えて網羅的に解説する包括的なガイドです。
Read more
PlaywrightによるE2Eテスト習得2026年版
Playwrightのauto-waiting、browser context isolation、network interception、auth storage、CI parallelizationを活用し、E2Eテストを習得するためのガイドです。
Read more
エンタープライズアプリケーションのためのTypeScript高度パターン
branded type、条件付き応答型、テンプレートリテラルルーティング、satisfies演算子など、エンタープライズ向けTypeScriptの高度なパターンを習得しましょう。
Read more