こんにちは、かつコーチです。
前回は非同期処理でハマりやすい罠を解説しました。
今回のテーマは「ブラウザ互換性」です。
自分のパソコンでは完璧に動いていたはずのコードが、別のブラウザや古い環境ではエラーになる、あるいは見た目が崩れる。
こうした経験は、実務でコードを書くようになると誰もが一度はぶつかる壁です。
今回は、なぜ互換性の問題が起きるのか、どう調べて、どう対処すればいいのかを整理していきます。
なぜブラウザによって挙動が変わるのか
ブラウザの実装エンジンの違い
ブラウザはそれぞれ異なるレンダリングエンジン(HTML・CSS・JavaScriptを解釈して画面に表示する仕組み)を使っています。
- Chrome・Edge:Blink
- Safari:WebKit
- Firefox:Gecko
新しいJavaScriptの仕様(ECMAScriptの新機能)が策定されても、各ブラウザがそれを実装するタイミングにはズレがあります。
そのため「Chromeでは動くのにSafariでは動かない」という状況が生まれてしまうのです。
バージョンによる違いもある
同じブラウザでも、ユーザーが使っているバージョンが古ければ新しい機能に対応していないことがあります。
会社支給のパソコンでブラウザの自動更新が止まっている、といったケースは実務でも意外と多く、想定より広い範囲のバージョンを意識する必要があります。
対応の第一歩:Can I useで対応状況を調べる
使いたい機能が使えるか事前に確認する
新しい構文やAPIを使う前に、Can I use(caniuse.com)というサイトで対応ブラウザを確認するのが基本の流れです。
例えばArray.prototype.at()のような比較的新しいメソッドを使う場合、Can I useで検索すると、どのブラウザのどのバージョンから対応しているかが一覧で表示されます。
「うちのサービスは古いSafariのユーザーが一定数いる」といった事情がある場合は、その機能を使うかどうかをここで判断します。
開発者ツールで実際に確認する
コードを書いた後は、複数のブラウザで実際に動作確認するのが確実です。
Chromeのデベロッパーツールには「デバイスツールバー」で他のブラウザを模倣する機能もありますが、レンダリングエンジン自体は変わらないため、可能であれば実機・実ブラウザでの確認も併用しましょう。
つまずきポイント:Safariだけ動かなかった経験
かつコーチが実際にハマった話
私が実際に痛い目を見たのが、Array.prototype.at()をSafariの古いバージョンで使ってしまったケースです。
配列の末尾要素を取得する処理を書いていて、Chromeでの動作確認だけで「よし、完成」と本番公開してしまいました。
❌ Before:新しい構文をそのまま使ってしまう
const prices = [1000, 1500, 2000];
const lastPrice = prices.at(-1);
console.log(lastPrice); // Chromeでは2000と表示される
at(-1)は「配列の末尾から1番目」を簡潔に取得できる便利なメソッドですが、対応が比較的新しいため、古いバージョンのSafariでは次のようなエラーが出てしまいました。
Uncaught TypeError: prices.at is not a function
公開後にお客様から「スマホで商品の値段が表示されない」という問い合わせが来て、慌てて調査したところ、原因はこの.at()が使えない古いiOSのSafariだったという顛末です。
✅ After:対応範囲の広い書き方に置き換える
const prices = [1000, 1500, 2000];
const lastPrice = prices[prices.length - 1];
console.log(lastPrice); // すべてのブラウザで2000と表示される
array[array.length - 1]という昔からある書き方に変更することで、対応ブラウザを気にする必要がなくなりました。
この一件以来、私は新しい構文を使う前に必ずCan I useで確認し、対応範囲が狭ければ従来の書き方を選ぶ、という判断を徹底するようになりました。
互換性を高めるための代表的な対処法
対処法1:ポリフィルを使う
ポリフィルとは、ブラウザが対応していない機能を、別のコードで補って使えるようにする仕組みです。
// Array.prototype.at() のポリフィル例
if (!Array.prototype.at) {
Array.prototype.at = function (index) {
const len = this.length;
const relativeIndex = index >= 0 ? index : len + index;
return this[relativeIndex];
};
}
「その機能が存在するかどうか」をifでチェックし、存在しない場合だけ独自に実装を追加するという考え方です。
実務では自分でポリフィルを1から書くよりも、core-jsのような既存のポリフィルライブラリを利用するのが一般的です。
対処法2:Babelでトランスパイルする
Babelは、新しい構文で書いたコードを、古いブラウザでも動く構文に変換してくれるツールです。
// 変換前(アロー関数)
const add = (a, b) => a + b;
// Babelによる変換後(イメージ)
var add = function (a, b) {
return a + b;
};
アロー関数やconst・letのような比較的新しい構文も、Babelを通すことで古いブラウザ向けのコードに自動変換できます。
新しい機能そのものが存在しない場合(Array.at()のようなAPI)はBabelだけでは解決できないため、その場合はポリフィルと組み合わせて使うのが一般的です。
対処法3:機能検出で分岐する
ライブラリを使わず、自前で対応状況を確認して処理を分岐させる方法もあります。
if ('IntersectionObserver' in window) {
// 対応ブラウザ向けの処理
const observer = new IntersectionObserver(() => {
console.log('要素が画面内に入りました');
});
} else {
// 非対応ブラウザ向けの代替処理
console.log('IntersectionObserver未対応のため、代替処理を実行します');
}
in演算子で「そのオブジェクトや機能が存在するか」を確認してから処理を分けることで、機能が使えないブラウザでもエラーにならずに済みます。
ブラウザの種類そのもの(User Agent)で判定する方法は、偽装や将来的な変更に弱いため、できる限り「機能が存在するかどうか」で判定する方が安全です。
どこまで対応すべきかの判断基準
対応ブラウザの範囲を最初に決めておく
すべてのブラウザ・すべてのバージョンに完璧に対応しようとすると、開発コストが際限なく膨らんでしまいます。
実務では、サービスのアクセス解析データ(Google Analyticsなど)をもとに「利用者の98%が使っているブラウザ・バージョンまでを正式サポート範囲とする」といった基準をチームであらかじめ決めておくのが一般的です。
package.jsonのbrowserslistを活用する
Node.jsのプロジェクトでは、package.jsonに対応ブラウザの範囲を書いておくと、Babelなどのツールがその設定を自動的に読み取って変換してくれます。
{
"browserslist": [
"> 0.5%",
"last 2 versions",
"not dead"
]
}
この設定は「シェア0.5%以上、各ブラウザの直近2バージョン、開発が終了していないブラウザ」を対象にする、という意味になります。
一度この基準を決めておけば、新しい構文を使うたびに手動でCan I useを確認する手間も減らせます。
まとめ
この記事のポイント
- ブラウザごとにレンダリングエンジンが異なり、新しい機能への対応時期にもズレがある
- 新しい構文・APIを使う前にCan I useで対応状況を確認する
- ポリフィル・Babel・機能検出を組み合わせて古いブラウザにも対応できる
- ブラウザの種類ではなく「機能が存在するか」で分岐させる方が安全
- 対応ブラウザの範囲をアクセス解析などから最初に決めておくと開発コストを抑えられる
次に読むべき記事
ブラウザ互換性の考え方を押さえたら、いよいよJavaScript基礎シリーズの最終回、デバッグ手法のまとめに進みます。
→ 次の記事:JavaScriptのデバッグ手法まとめ