•11 min read

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

ChatGPTUIで要素の正しいY座標を取得する方法(2026年版)
Audio Briefing
0:00 / 0:00

問題点

ChatGPT用のブラウザ拡張機能を開発していたとき、ある問題にぶつかりました。チャット内の特定のメッセージまでスクロールする必要があったのですが、私は他の開発者と同じように、まず以下のコードを試しました。

window.scrollY

しかし、結果は毎回ゼロでした。

次に、お決まりのコードを試しました。

element.getBoundingClientRect().top + window.scrollY

それでもうまくいきません。座標はめちゃくちゃで、拡張機能は使い物になりませんでした。

なぜいつも0なのか?

ChatGPTのようなモダンなSPAは、ページ自体をスクロールさせません。<body>は高さ100%に固定されています。スクロールはDOMツリー内のどこかにあるネストされたコンテナ内で発生します。そのため、window.scrollYは永遠に0のままで、計算はサイレントに失敗します。

このデバッグにかなりの時間を費やしてしまいました。しかし、本当の答えは私が思っていたよりもシンプルでした。


Advertisement

真のスクロールコンテナを見つける

ページ自体はスクロールしません。ページ内の何かがスクロールしているのです。それを見つける必要があります。

私がうまくいったアプローチは次のとおりです。まず、対象の要素を取得します。

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);

Advertisement

動的コンテンツについてはどうですか?

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がスクロールターゲットであるという仮定をやめた途端、すべてがうまくいくようになりました。

こちらもおすすめ

Share this article:

Stay Updated

Get the latest posts delivered straight to your inbox.

Free Developer Utilities

Free In-Browser Developer Tools

Clean AI CLI logs, build cron expressions, decode JWTs, and calculate chmod permissions offline.

Explore Tools
Advertisement