scrollWidth / scrollHeight とは何か
scrollWidth と scrollHeight は、「要素のコンテンツが本来必要としている大きさ」を返すプロパティです。visible(見えている)領域の制限に関係なく、コンテンツ全体の幅・高さを整数ピクセルで教えてくれます。ここが重要です:clientWidth/clientHeight(内側で“使える”領域)よりも大きくなることがあり、overflow で隠れている部分も含んだ“本来サイズ”だと理解してください。
何が含まれ、何が含まれないか(ボックスモデルの要点)
内訳のポイント
scrollWidth/scrollHeight は content+padding を含みます。border と margin は含みません。ここが重要です:clientWidth/Height と同じく“内側領域”基準ですが、“見えているかどうか”に関係なく、コンテンツが必要とする全量を返します。だから「収まっているか」「スクロールが必要か」を判定するのに最適です。
box-sizing の影響
box-sizing(content-box / border-box)の違いに関わらず、scrollWidth/Height は“コンテンツ+paddingの必要サイズ”として返ります。ここが重要です:CSS の指定方法に左右されず、収まり判定やスクロール量計算に一貫して使えます。
基本の使い方(収まり判定と必要スクロール量の算出)
横に収まるかを判定(省略記号や折り返しの分岐)
<div id="title" style="max-width:240px; padding:8px; border:1px solid #ccc; white-space:nowrap; overflow:hidden; text-overflow:ellipsis"></div>
<script>
title.textContent = "とても長いタイトルが入ることを想定しています";
const fits = title.scrollWidth <= title.clientWidth; // 必要幅 <= 内側幅
console.log("収まる?", fits);
</script>
HTMLここが重要です:scrollWidth(実際に必要な幅)と clientWidth(使える内側幅)の比較だけで、収まるかどうかを確実に判定できます。offsetWidth(外側寸法)では枠やスクロールバーが混ざって誤差が出ます。
縦に収まるかを判定(折りたたみの開閉基準)
<div id="box" style="height:120px; padding:8px; border:1px solid #ccc; overflow:auto">
<!-- 長文を想定 -->
</div>
<script>
const needsScroll = box.scrollHeight > box.clientHeight; // 必要高さ > 内側高さ
console.log("スクロール必要?", needsScroll);
</script>
HTMLここが重要です:scrollHeight はコンテンツの必要高さ、clientHeight は見えている内側高さ。これで“スクロールが必要か”がシンプルに判断できます。
スクロール量・進捗の計算(scrollLeft/Top と組み合わせる)
横スクロールの進捗(カルーセルなど)
<div id="row" style="width:260px; overflow:auto; white-space:nowrap; border:1px solid #ccc">
<span>カード1</span><span>カード2</span><span>カード3</span><span>カード4</span>
</div>
<script>
function getHProgress(el) {
const max = Math.max(el.scrollWidth - el.clientWidth, 1);
return Math.min(el.scrollLeft / max, 1); // 0〜1
}
row.addEventListener("scroll", () => {
console.log("横進捗:", (getHProgress(row) * 100).toFixed(1), "%");
}, { passive: true });
</script>
HTMLここが重要です:割合は「現在のスクロール位置 ÷ 到達可能な最大量」。見えている幅(clientWidth)を差し引くのを忘れると 100% に届かない計算ミスになります。
縦スクロールの進捗(パネル内)
<div id="panel" style="height:180px; overflow:auto; border:1px solid #ccc">
<div style="height:800px"></div>
</div>
<script>
function getVProgress(el) {
const max = Math.max(el.scrollHeight - el.clientHeight, 1);
return Math.min(el.scrollTop / max, 1);
}
panel.addEventListener("scroll", () => {
console.log("縦進捗:", (getVProgress(panel) * 100).toFixed(1), "%");
}, { passive: true });
</script>
HTMLここが重要です:scrollTop は“今どれだけスクロールしたか”。scrollHeight−clientHeight が“スクロール可能な総量”。この三つで進捗が安定して求まります。
実践パターン(自動サイズ、折りたたみアニメ、横スクロールナビ)
テキストエリアの自動伸長(必要高さに合わせる)
<textarea id="ta" style="box-sizing:border-box; width:280px; padding:8px; border:1px solid #ccc; overflow:hidden"></textarea>
<script>
function autosize(el) {
el.style.height = "auto"; // いったんリセット
el.style.height = el.scrollHeight + "px"; // 必要高さに合わせる
}
ta.addEventListener("input", () => autosize(ta));
</script>
HTMLここが重要です:scrollHeight は“内容に必要な高さ”。高さをそれに合わせれば、折り返し量に応じて自然に伸びる入力欄を作れます。
折りたたみパネルを自然な高さで開く
<div id="panel" style="overflow:hidden; transition: height .25s ease; padding:8px; border:1px solid #ddd"></div>
<script>
function openPanel(html) {
panel.innerHTML = html;
const h = panel.scrollHeight; // 必要高さ(padding込み、枠除外)
panel.style.height = "0px";
requestAnimationFrame(() => panel.style.height = h + "px");
}
openPanel("テキスト<br>テキスト<br>テキスト");
</script>
HTMLここが重要です:scrollHeight はコンテンツ量次第で変わる“自然高さ”。それをアニメの終点にすると、見た目が破綻しません。外側寸法で開きたいなら offsetHeight を使います。
横スクロールの「次へ」ボタンを正しく無効化
<div id="row" style="width:260px; overflow:auto; white-space:nowrap; border:1px solid #ccc">
<span>カード1</span><span>カード2</span><span>カード3</span><span>カード4</span>
</div>
<button id="next">次へ</button>
<script>
function canScrollRight(el) {
return el.scrollLeft < el.scrollWidth - el.clientWidth;
}
function sync() { next.disabled = !canScrollRight(row); }
row.addEventListener("scroll", sync, { passive: true });
next.addEventListener("click", () => { row.scrollLeft += 120; sync(); });
sync();
</script>
HTMLここが重要です:scrollWidth−clientWidth が“右端座標”。scrollLeft がそれ以上なら“もう進めない”。この判定でボタン状態を同期します。
getBoundingClientRect との使い分け(画面座標が欲しいとき)
画面(ビューポート)基準が必要なら rect
<script>
const r = row.getBoundingClientRect();
console.log(r.left, r.width); // 画面に対する位置とサイズ(小数)
</script>
HTMLここが重要です:rect は“画面に対しての見え方”。scrollWidth/Height は“要素内の必要サイズ”。ポップアップ位置合わせや交差判定は rect、収まり判定・必要量計算は scroll を使います。
よくある落とし穴と回避策
display: none だと 0 になる
非表示(display: none)の要素はレイアウト計算されないため、scrollWidth/Height は 0 を返します。ここが重要です:事前計測が必要なら visibility: hidden(見えないがレイアウトに参加)にしてから測定します。
“枠やバー込み”と勘違いする
scrollWidth/Height は border を含みません。内部スクロールバーの厚みも含みません。ここが重要です:外側の箱寸法が欲しいなら offsetWidth/Height、内側の可視領域なら clientWidth/Height。役割を分けるとズレが消えます。
100% にならない進捗計算
最大量の計算で“見えている領域”を引き忘れると、最後まで行っても 100% になりません。ここが重要です:必ず scrollWidth−clientWidth(縦なら scrollHeight−clientHeight)を使います。
小数が欲しいのに整数しか出ない
scrollWidth/Height は整数です。ズームや精密な交差判定が必要なら getBoundingClientRect の小数値を使います。ここが重要です:精度と目的に応じて値の種類を選びます。
まとめ
scrollWidth/scrollHeight は「コンテンツが本来必要としている内側サイズ(content+padding)」を返すプロパティで、収まり判定やスクロール必要量、進捗計算に最適です。横は scrollWidth と clientWidth、縦は scrollHeight と clientHeight のペアで“必要量−見えている量”を正しく扱い、scrollLeft/Top と組み合わせて進捗や端判定を行います。画面座標や精密な幅・高さが欲しい場面では getBoundingClientRect、外側寸法が欲しいなら offsetWidth/Height を使い分ける。表示状態・ボックスモデル・整数値という性質を理解すれば、初心者でもズレのないスクロール連動 UI と自然なアニメーションを堅実に実装できます。
