レスポンシブ対応や、画面幅に応じた表示切り替えのために、JavaScriptで画面サイズをリアルタイムに取得したい場面はよくあります。
基本は window.innerWidth / window.innerHeight と resize イベントですが、「どのプロパティを使うか」「resizeをそのまま使うと重くなる」といったつまずきポイントがあります。この記事では、用途別の使い分けと効率的な監視方法までそのまま動くコードで解説します。
window.innerWidth / window.innerHeight で取得し、変化の監視は resize イベントで行います。ただし resize は高頻度で発火するためthrottle/debounceで間引くのが必須。ブレークポイント判定だけなら matchMedia、要素単位の監視なら ResizeObserver が効率的です。画面サイズを取得する(基本)
現在のビューポート(表示領域)のサイズは window.innerWidth と window.innerHeight で取得できます。要素に表示してみましょう。
<div id="size">幅: 0px / 高さ: 0px</div>
function showSize() {
const el = document.getElementById("size");
el.textContent = `幅: ${window.innerWidth}px / 高さ: ${window.innerHeight}px`;
}
showSize(); // 初期表示(読み込み時点のサイズ)
これで読み込み時点のサイズが表示されます。あとはサイズ変更を監視すればリアルタイムになりますが、その前に「どのプロパティを使うべきか」を整理しておきます。
取得プロパティの使い分け
「画面サイズ」と一口に言っても、対象によってプロパティが異なります。主なものは次のとおりです。
| プロパティ | 取得できるサイズ | スクロールバー |
|---|---|---|
window.innerWidth / innerHeight |
ビューポート(表示領域) | 含む |
documentElement.clientWidth / clientHeight |
ビューポート(表示領域) | 除く |
window.outerWidth / outerHeight |
ブラウザウィンドウ全体(ツールバー込み) | — |
screen.width / height |
デバイスの物理画面 | — |
window.innerWidth はスクロールバーを含む幅、document.documentElement.clientWidth は含まない幅です。CSSの @media (max-width: ...) は基本的にスクロールバーを含まない幅で判定するため、innerWidth とCSSのブレークポイントが数px〜十数pxズレることがあります。CSSと厳密に揃えたいときは clientWidth を使ってください。なお、高解像度(Retina)ディスプレイの判定には window.devicePixelRatio(CSSピクセルに対する物理ピクセルの比率。Retinaなら2以上)を使います。
リサイズをリアルタイムで監視する(間引き必須)
サイズ変更の監視には resize イベントを使います。ただし resize はドラッグ中に1秒間で何十回も発火します。重い処理を直結すると一気にカクつくため、throttle(一定間隔に1回)で間引きます。
// 一定間隔に1回だけ実行する throttle
function throttle(fn, interval) {
let last = 0;
return (...args) => {
const now = Date.now();
if (now - last >= interval) {
last = now;
fn(...args);
}
};
}
// 200msに1回までサイズを更新(ドラッグ中も追従しつつ軽い)
window.addEventListener("resize", throttle(showSize, 200));
ドラッグ中も追従させたいなら throttle、「リサイズが止まってから一度だけ」でよいなら debounce が向いています。2つの違いと実装はdebounceとthrottleの違いと実装方法で詳しく解説しています。
ブレークポイント判定は matchMedia が効率的
「モバイル幅かどうか」だけを知りたいなら、resize で毎回幅を比較するより matchMedia の change イベントが効率的です。境界をまたいだ瞬間だけ発火するため、無駄な処理が走りません。
const mq = window.matchMedia("(max-width: 767px)");
function handleChange(e) {
if (e.matches) {
console.log("モバイル幅");
} else {
console.log("デスクトップ幅");
}
}
handleChange(mq); // 初期判定
mq.addEventListener("change", handleChange); // 境界をまたいだ時だけ発火
ウィンドウ幅の条件でページを切り替える具体例はウィンドウサイズの条件でページをリロードする方法も参考になります(連続リロードの罠も解説しています)。
特定要素のサイズ監視は ResizeObserver
ウィンドウ全体ではなく特定の要素のサイズ変化を監視したいときは、resize ではなく ResizeObserver を使います。サイドバーの開閉やコンテンツ量の変化など、ウィンドウサイズと無関係な変化も拾えます。
const box = document.getElementById("box");
const observer = new ResizeObserver((entries) => {
for (const entry of entries) {
const { width, height } = entry.contentRect;
console.log(`要素サイズ: ${Math.round(width)} x ${Math.round(height)}`);
}
});
observer.observe(box); // 監視開始(不要になったら observer.disconnect())
要素サイズに応じてレイアウトを揃える実践例は要素の高さを揃える方法で扱っています。
モバイルで innerHeight が変わる問題(visualViewport)
スマホでは、アドレスバーの表示・非表示で window.innerHeight が変動します。これが「100vh にすると画面からはみ出す」問題の原因です。実際に見えている領域を正確に取りたいときは window.visualViewport を使います。
// 対応ブラウザでのみ実行(?. で安全に)
window.visualViewport?.addEventListener("resize", () => {
const vv = window.visualViewport;
console.log(`表示領域の高さ: ${Math.round(vv.height)}px`);
});
よくある質問(FAQ)
window.innerWidth / window.innerHeight が現在のビューポートサイズを返し、resize イベントで変化を監視できます。resize は高頻度で発火するため、throttle/debounceで間引くのが前提です。window.innerWidth はスクロールバーを含むビューポート幅、document.documentElement.clientWidth は含まない幅(CSSの@mediaと一致しやすい)、screen.width はデバイスの物理画面幅です。通常のレスポンシブ対応にはビューポート幅を使います。window.matchMedia("(max-width: 767px)").matches で判定するのが確実です。CSSのブレークポイントと同じ条件で書けるうえ、change イベントで境界をまたいだ時だけ処理できます。User-Agent判定より正確なレスポンシブ対応になります。ResizeObserver を使います。observer.observe(要素) で監視を開始し、コールバックの entry.contentRect から幅・高さを取得できます。ウィンドウサイズと無関係な要素の変化も検知できるのが利点です。まとめ
画面サイズの取得は window.innerWidth / innerHeight が基本で、スクロールバーを除いた値が必要なら documentElement.clientWidth を使い分けます。リアルタイム監視は resize イベントですが、throttle/debounceでの間引きが必須です。
目的が「ブレークポイント判定」なら matchMedia、「特定要素の監視」なら ResizeObserver の方が効率的です。モバイルで見える領域を正確に扱いたいときは visualViewport も覚えておくと役立ちます。
