アスペクト比の約分と行の自然順ソート — サーバなしのブラウザ完結ツールをGCDとIntl.Collatorで作る

「幅と高さからアスペクト比を出す」「行のリストを重複削除して自然順に並べる」。
どちらも地味だが、日常的に何度も必要になる処理だ。

こういう小さなユーティリティは、サーバを立てずブラウザのJavaScriptだけで完結させるのが一番早い。入力がサーバへ飛ばないのでプライバシー面でも安心で、静的ホスティングに置くだけで動く。

今回、フリートの ハシトシステム に2つのツールを追加した。その実装の核だけを、手元で動く形で取り出して解説する。

1. アスペクト比は「最大公約数で割る」だけ

1920x1080 が 16:9 になるのは、両方を最大公約数(GCD)で割っているからだ。GCD はユークリッドの互除法で数行で書ける。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function gcd(a, b) {
a = Math.abs(a); b = Math.abs(b);
while (b) { const t = b; b = a % b; a = t; }
return a;
}

function ratio(w, h) {
const g = gcd(w, h);
return g === 0 ? [0, 0] : [w / g, h / g];
}

console.log(ratio(1920, 1080)); // [16, 9]
console.log(ratio(1024, 768)); // [4, 3]
console.log(ratio(2560, 1080)); // [64, 27] (いわゆる 21:9 の実体)

ポイントは 2560x1080 のようなウルトラワイドで、これは正確には 21:9 ではなく 64:27。約分して初めて分かる。

小数の寸法にも耐えるようにする

入力が 1280.5 のように小数になることもある。そのときは一定桁までスケールして整数化してから約分する。

1
2
3
4
5
6
7
function toIntPair(w, h) {
const scale = (Number.isInteger(w) && Number.isInteger(h)) ? 1 : 1000;
const iw = Math.round(w * scale);
const ih = Math.round(h * scale);
const g = gcd(iw, ih);
return g === 0 ? [0, 0] : [iw / g, ih / g];
}

逆に「16:9 を保ったまま幅1280から高さを出す」場合は、比率を掛けるだけだ。

1
2
const r = 16 / 9;
Math.round(1280 / r); // 720

整数で割り切れないときは四捨五入した近似値になる、という注意書きをUIに添えておくと親切だ。

2. 自然順ソートは Intl.Collator 一発

file10file2 より後ろに並べたい、というのが自然順(natural sort)だ。素朴な文字列比較では file10 < file2(”1” < “2” だから)になってしまう。

自前で数字部分を切り出して比較するコードを書きがちだが、標準の Intl.Collatornumeric: true を渡せば一行で済む。

1
2
3
4
5
6
7
const collator = new Intl.Collator(undefined, {
numeric: true, // 数字を数値として比較
sensitivity: 'accent' // 大文字小文字を区別しない
});

["file10", "file2", "file1"].sort((a, b) => collator.compare(a, b));
// => ["file1", "file2", "file10"]

sensitivity'variant' にすれば大文字小文字を区別できる。ロケールを渡せば各言語の並び順にも従う。ブラウザ組み込みなので依存も増えない。

重複削除は Set ではなく「キーを作る」

大文字小文字を無視して重複を消したいときは、素の Set では足りない(Appleapple が別物になる)。比較キーを作って判定する。

1
2
3
4
5
6
7
8
9
10
11
12
function dedupe(lines, caseSensitive = false) {
const seen = Object.create(null);
const out = [];
for (const line of lines) {
const key = caseSensitive ? line : line.toLowerCase();
if (!(key in seen)) { seen[key] = true; out.push(line); }
}
return out;
}

dedupe(["banana", "apple", "Apple", "banana"]);
// => ["banana", "apple"] (大文字小文字を無視・元の表記は保持)

元の表記(apple)を保ちつつ、重複判定だけ小文字化する、という点が地味に効く。

実際に試せる

上のコードはそのまま Node でも DevTools のコンソールでも動く。組み上がったものは、それぞれ次で触れる。

どちらも入力はブラウザから外に出ない。サーバレスで済む処理は、GCD と Intl.Collator のように、標準の道具を組み合わせるだけで十分に作れる。ライブラリを足す前に、まず組み込みで足りないかを確かめる価値はある。


アスペクト比の約分と行の自然順ソート — サーバなしのブラウザ完結ツールをGCDとIntl.Collatorで作る
https://blog.hashito.biz/2026/07/22/client-side-aspect-ratio-gcd-natural-sort-collator/
著者
hashito
作成日
2026年7月22日
著作権