スマートフォンを縦から横に持ち替えたときに、レイアウトを組み直したい、ゲームの画面の大きさを変えたい、ということがあります。かつては orientationchange イベントと window.orientation プロパティを使うのが定番でしたが、これらは今では非推奨です。ここでは、昔の方法で何が困ったのかと、今の書き方を説明します。

昔の方法: orientationchange と window.orientation

iPhone の Safari が始めた方法で、Android のブラウザーにも広まりました。

// 昔の書き方 (今は非推奨)
window.addEventListener("orientationchange", () => {
  // 0: 縦向き、90 と -90: 横向き、180: 上下逆さま
  console.log(window.orientation);
});

この方法には、端末やブラウザーによる違いが多くありました。

  • orientationchange と resize のどちらが先に起きるかが、端末によって違った
  • orientationchange が起きた時点で、window.innerWidth などの画面の大きさが、まだ向きを変える前の値のままのことがあった
  • window.orientation の 0 が縦向きとは限らなかった。もともと横長の画面のタブレットでは、横向きが 0 になる機種があった

そのため、イベントのあとに setTimeout() で少し待ってから画面の大きさを読む、といった回避策が使われていました。待つ時間は機種ごとに試して決めるしかなく、確実な方法ではありませんでした。

window.orientation と orientationchange は、今も一部のブラウザーで動きますが、非推奨とされています。新しく書くコードでは使いません。

今の方法 1: screen.orientation

画面の向きを知るための標準の API が、Screen Orientation API です。screen.orientation の type に向きの種類が、angle に回転の角度が入ります。向きが変わると、change イベントが起きます。

function showOrientation() {
  const { type, angle } = screen.orientation;
  // type: "portrait-primary"、"landscape-primary" など
  // angle: 0、90、180、270 のどれか
  console.log(type, angle);
}

if (screen.orientation) {
  showOrientation();
  screen.orientation.addEventListener("change", showOrientation);
}

window.orientation では横向きが 90 と -90 でしたが、angle は 0〜359 の範囲の値 (0、90、180、270) になります。古いブラウザーには screen.orientation が無いことがあるので、上の例のように有るかどうかを確かめてから使います。

なお、screen.orientation.lock() で向きを固定する機能もありますが、全画面表示のときだけ使えるなどの条件があり、対応していないブラウザーもあります。

今の方法 2: matchMedia と resize

知りたいのが「端末の向き」ではなく「表示領域が縦長か横長か」なら、メディアクエリを使うほうが確実です。orientation メディア特性は、表示領域の高さが幅以上なら portrait、そうでなければ landscape になります。

const portrait = matchMedia("(orientation: portrait)");

function applyOrientation() {
  document.documentElement.dataset.orientation = portrait.matches ? "portrait" : "landscape";
}

applyOrientation();
portrait.addEventListener("change", applyOrientation);

これは端末を回したときだけでなく、パソコンでウィンドウの大きさを変えたときや、タブレットで画面を分割したときにも働きます。ブラウザーによっては、ソフトウェアキーボードを出したときに表示領域が縮み、横長と判定されることもあります。

表示領域の大きさに合わせて描き直したいだけなら、向きにこだわらず resize イベントで大きさを読みます。resize は続けて何度も起きることがあるので、requestAnimationFrame() でまとめます。

let scheduled = false;

window.addEventListener("resize", () => {
  if (scheduled) return;
  scheduled = true;
  requestAnimationFrame(() => {
    scheduled = false;
    // ここでは、向きを変えたあとの大きさが読める
    // resizeGame() は、画面の大きさに合わせて描き直す処理 (別に用意しておく)
    resizeGame(window.innerWidth, window.innerHeight);
  });
});

特定の要素の大きさが変わったことを知りたいなら、ResizeObserver を使う方法もあります。

レイアウトはできるだけ CSS で済ませる

見た目を変えるだけなら、JavaScript は要りません。CSS のメディアクエリで、表示領域の向きや幅に合わせて指定を切り替えます。

/* 横長の表示領域では、2 列に並べる */
@media (orientation: landscape) {
  .gallery {
    display: grid;
    grid-template-columns: 1fr 1fr;
  }
}

/* iOS の Safari で、横向きにしたときに文字が勝手に大きくならないようにする */
html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

多くの場合は、向きよりも幅で切り替えるほうが、さまざまな画面の大きさに対応できます。iOS の Safari では、横向きにすると文字が自動で拡大されることがあり、text-size-adjust で止められます。スマートフォンのブラウザーに特有の扱いは、スマートフォンのカテゴリーのほかの記事でも取り上げています。

まとめ

  • orientationchange と window.orientation は非推奨。端末によってイベントの順番や、その時点の画面の大きさが違った
  • 画面の向きは screen.orientation の type と angle、change イベントで知る
  • 表示領域が縦長か横長かは、matchMedia("(orientation: portrait)") の change イベントで知る
  • 大きさに合わせて描き直すだけなら、resize を requestAnimationFrame() でまとめて処理する
  • レイアウトはできるだけ CSS のメディアクエリで済ませる

参考