開発の経緯
こんにちは!普段ウェブブラウジングをしている際、次のようなストレスを感じたことはありませんか?
- 画面の横幅からページの一部がはみ出てしまい、横スクロールが発生する
- レスポンシブ対応していない古いサイトや固定幅デザインのサイトで、右側が途切れてしまう
- ブラウザのウィンドウを画面の端に寄せて半分で表示している(スプリット表示)と、表示が崩れて文字が読みづらい
手動で Ctrl + - や Ctrl + + を押してズーム倍率を変更すれば収まりますが、ウィンドウサイズを変更するたびに調整するのは非常に面倒です。
そこで今回、「ウィンドウの横幅に合わせて自動的にページのズーム率を調整し、横スクロールを完全に排除する」 というChrome拡張機能**「Fit Browser - 横スクロール自動フィット」**を開発・公開しました!
この記事では、本拡張機能を開発するに至った経緯と、実際にどのようなロジック・コードで動作しているのかを詳しく解説します。
1. 開発の経緯・モチベーション
課題:画面分割(2画面並列)でのブラウジング体験の悪さ
ノートPCでの作業時や、ウルトラワイドモニターで複数ウィンドウを並べて作業する際、ブラウザを画面半分(幅700px〜900px程度)に縮めて表示することがよくあります。
しかし、世の中のウェブサイトには**「幅1000px以上の固定幅でデザインされているサイト」**がまだまだ存在します。また、レスポンシブデザインが適用されるブレイクポイントの狭間で、どうしても横スクロールバーが出てしまうケースもあります。
横スクロールが発生すると、以下の問題が起こります:
- テキストを読むのに左右にスクロールが必要になり、極めて読みづらい
- トラックパッドやマウスでの誤操作で意図せず左右に滑ってしまう
- 拡大・縮小を手動で調整しても、別のタブを開いたりウィンドウ幅を変えると再調整が必要
解決策:「横幅の比率を計算してズーム率を自動適用する」
「ページ全体の実際の幅(scrollWidth)」と「現在のウィンドウの可視幅(clientWidth)」を比較し、ぴったり収まる倍率を計算して自動でズームをかければ解決するのではないか?と考えました。
シンプルですが、常に画面に最適化されるため非常にストレスフリーな体験が得られます。
2. 拡張機能の全体構造 (Manifest V3)
本拡張機能は、Chromeの最新仕様である Manifest V3 に準拠して作成しています。
構成ファイルは以下の通りシンプルです:
fit-browser/
├── manifest.json # 拡張機能の設定ファイル
├── content.js # ページ側で幅の計算とズーム処理を行うスクリプト
└── icons/ # アイコン画像群
├── icon16.png
├── icon48.png
└── icon128.png
3. 実装とコード解説
それでは、核心となる実装部分を解説します。
① manifest.json の設定
{
"manifest_version": 3,
"name": "Fit Browser - 横スクロール自動フィット",
"version": "1.0.0",
"description": "ページの横幅がウィンドウに収まるよう自動でズームを調整し、横スクロールをなくします。",
"permissions": [
"activeTab"
],
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content.js"],
"run_at": "document_idle"
}
],
"icons": {
"16": "icons/icon16.png",
"48": "icons/icon48.png",
"128": "icons/icon128.png"
}
}
-
<all_urls>で全ページを対象にし、ページ読み込み完了時(document_idle)にcontent.jsを注入します。
② コアロジック:content.js
横スクロールをなくすためのズーム率算出ロジックです。
/**
* Fit Browser - 横スクロール自動フィット
* Content Script
*/
(function () {
'use strict';
// 連続したウィンドウリサイズ時のオーバーヘッドを防ぐデバウンス関数
function debounce(func, wait) {
let timeout;
return function (...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), wait);
};
}
// 自動ズームの調整メイン処理
function autoFitZoom() {
// 1. 一旦ズームを標準(100% / 1.0)に戻して本来のコンテンツ幅を測定する
document.body.style.zoom = '1.0';
// 2. ビューポート(画面の表示幅)とドキュメントの実際の幅を取得
const viewportWidth = document.documentElement.clientWidth;
const scrollWidth = Math.max(
document.documentElement.scrollWidth,
document.body.scrollWidth
);
// 3. コンテンツ幅が画面幅を超えている場合(横スクロールが発生している場合)
if (scrollWidth > viewportWidth && viewportWidth > 0) {
// 適切なズーム比率を計算 (余裕を持たせるため0.99倍のクッションを配置)
let targetScale = (viewportWidth / scrollWidth) * 0.99;
// 視認性を保つため、最小ズーム率を 30% (0.3) に制限
targetScale = Math.max(targetScale, 0.3);
// CSS zoom プロパティでスケール調整
document.body.style.zoom = targetScale.toFixed(2);
} else {
// 横スクロールがない場合は等倍(1.0)を維持
document.body.style.zoom = '1.0';
}
}
// ページ初期化時・読み込み完了時に実行
window.addEventListener('load', autoFitZoom);
// ウィンドウサイズ変更時に自動再計算(デバウンス処理付き)
window.addEventListener('resize', debounce(autoFitZoom, 200));
// 動的コンテンツ読み込み(DOM変更)にも対応するオブザーバー
const observer = new MutationObserver(debounce(autoFitZoom, 500));
if (document.body) {
observer.observe(document.body, {
childList: true,
subtree: true
});
}
})();
4. 技術的な工夫・ハマりどころ
① CSS zoom プロパティの選定理由
ウェブページの拡大縮小には transform: scale() を使う方法もありますが、transform だとレイアウトの占有領域自体は元のサイズで残ってしまい、スクロール領域の再計算がうまく行われません。
一方、zoom プロパティ(非標準ではあるものの、Chrome/Edge/SafariなどのWebkit系ブラウザで広くサポートされている)を使用すると、レイアウトツリー全体のリフロー(再計算)が起き、スクロールバーの出現自体をスマートに抑制できるため、今回は zoom を採用しました。
② 無限ループ・パフォーマンス低下の防止 (Debounce & Target Threshold)
ウィンドウサイズのリサイズイベント(resize)や、DOMの動的変化(MutationObserver)は高頻度で発火します。毎回ズームの計算とスタイル適用を行うとブラウザが重くなるため、デバウンス処理(debounce) を挟み、200ms〜500msの静止後に一括実行するように工夫しています。
③ 一度ズームをリセット(1.0)してから測定する
ズームがかかった状態のまま scrollWidth や clientWidth を取得すると、計算誤差が蓄積してズーム倍率が徐々に小さくなり続けてしまう問題(縮小のループ)が発生します。そのため、計算直前に必ず zoom = '1.0' に戻して本来の寸法を取得するようにしています。
5. まとめとストアリンク
ちょっとした不便を解消するために作成した拡張機能ですが、ウルトラワイドモニター利用者や、画面分割で作業するユーザーにとっては非常に快適なブラウジング環境を作ることができました。
現在、Google Chrome Web Storeにて公開中ですので、同じ悩みをお持ちの方はぜひ試してみてください!
- Chrome Web Store ページ: Fit Browser - 横スクロール自動フィット
ご意見・ご要望や「ここをもっとこう改善できるのでは」といったリクエスト等があれば、コメント欄やフィードバックでぜひお知らせください!
