【WordPress】wp_is_mobile() がiPadを検出してくれないので解決【Safariのみ】

WordPress

iPad版safariだけおかしい!?

ある案件で、webサイトのタブレット端末での表示調整をした際、なぜかiPad版のsafariでのみ挙動の変更が反映されないということが起きた。

ちなみに実機は

  • iPad(第6世代)
  • iPadOS 15.1

結論から言うと、ワードプレスのテンプレートタグ wp_is_mobile() がiPadOSのsafariでのみtrueを返してくれていない、というのが原因であった。
Chromeでは問題なかったので、ブラウザ依存の何らかの原因であることは容易に予測がついたのだが、javascriptの記述に問題があると思い込んで(そこぐらいしか思いつかない)散々悩んだが、うん、ワードプレスを信頼しすぎてたwww
もしかしてこのwp_is_mobile() が効いていないのかも、と疑ってみたらビンゴだったww
ということは、User Agent関連ということになる。

wp_is_mobile() を覗いてみる

このテンプレートタグは、
WP v5.8.3では wp-includes/vars.php の139行目あたりから記述が見られる。

/**
 * Test if the current browser runs on a mobile device (smart phone, tablet, etc.)
 *
 * @since 3.4.0
 *
 * @return bool
 */
function wp_is_mobile() {
    if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
        $is_mobile = false;
    } elseif ( strpos( $_SERVER['HTTP_USER_AGENT'], 'Mobile' ) !== false // Many mobile devices (all iPhone, iPad, etc.)
        || strpos( $_SERVER['HTTP_USER_AGENT'], 'Android' ) !== false
        || strpos( $_SERVER['HTTP_USER_AGENT'], 'Silk/' ) !== false
        || strpos( $_SERVER['HTTP_USER_AGENT'], 'Kindle' ) !== false
        || strpos( $_SERVER['HTTP_USER_AGENT'], 'BlackBerry' ) !== false
        || strpos( $_SERVER['HTTP_USER_AGENT'], 'Opera Mini' ) !== false
        || strpos( $_SERVER['HTTP_USER_AGENT'], 'Opera Mobi' ) !== false ) {
            $is_mobile = true;
    } else {
        $is_mobile = false;
    }

    /**
     * Filters whether the request should be treated as coming from a mobile device or not.
     *
     * @since 4.9.0
     *
     * @param bool $is_mobile Whether the request is from a mobile device or not.
     */
    return apply_filters( 'wp_is_mobile', $is_mobile );
}

ということで、簡単に言うと、wp_is_mobile()とは、

  • UAが得られなければ、あるいは空配列なら FALSE
  • UAの文字列に"Mobile"、"Android"、"Silk/" ・・・のいずれかが含まれるなら TRUE
  • その他の場合は FALSE

というboolean値が得られる関数です。
一応、スマホとタブレットに関してはいずれもTRUEを返す前提の関数なのですが、ここに問題があるのでしょう。

iPadOS の Safariが返す Uer Agent はふた通りある。

というわけで、iPadOSのSafariが返すUAについて調べてみたところ、以下の記事を発見。
【解説】iPad OSのユーザーエージェントに「iPad」が含まれない理由

どうやらiPad OS では設定 -> safari の中に「デスクトップ用Webサイトを表示」という項目があるらしい・・・




この項目のON/OFFによって吐き出されるUAが変わる・・・だと・・・!?

上記記事によると、

[Safari on iPad + iPad OS13(デスクトップ用Webサイトを表示:On設定時)]
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0 Safari/605.1.15

[Safari on iPad + iPad OS13(デスクトップ用Webサイトを表示:Off設定時)]
Mozilla/5.0 (iPad; CPU OS 13_1_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.1 Mobile/15E148 Safari/604.1

なんと、OFFであればUA文字列に現れる"Mobile"が、ONだと"Macintosh"とかになっている。

これはユーザー環境に依存してしまうので、とりあえずこの項目がONでも有効な切り分けを考えねばならない、ということで対処の方向性は固まりました。

※ ちなみに、iPhone(iOS)の場合は"Mobile"や"iPhone"という文字列が入るので問題にはならない。

UAでの判別を見直す?

ここでいったんiPadOSのUA(デスクトップ用Webサイト表示ON )とMacBookPro(Mojave 10.14.6)のSafariのUAを比べてみる。

さて、上記記事より引用したiPadOS13のSafariのUA(デスクトップ用Webサイト表示ON)は

[Safari on iPad + iPad OS13(デスクトップ用Webサイトを表示:On設定時)]
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0 Safari/605.1.15

MacBookPro(Mojave 10.14.6)のSafariのUAは

Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3 Safari/605.1.15

同じやん。
結論。UAによる判別は無理。

対処方法

とはいえ、iPadOSのsafari以外はwp_is_mobile()で判別できているので、できればこれはこれで活かしたいのだが、UAで判別できない以上、他の方法を考えるしかなく、結局Javascriptということに。

調べてみたところ、UAでの判別 + onTouchEnd属性の有無による処理をANDで重ねて判別するとのこと。

1) UAが"Macintosh"という文字列を持つ ・・・ Mac または iPad(デスクトップ用Webサイト表示ON)
2) UAが"Mobile"という文字列を持つ ・・・ iPad(デスクトップ用Webサイト表示OFF)を含む多くのモバイルデバイス
3) そのページ(document)が ontouchend属性を持つか判別 ・・・タッチ端末でtrue

Macにはsurfaceのようなタッチ端末が存在しないという事実に依存するのは将来的に問題となる可能性はゼロではないが・・・
とりま現状では、上記の1〜3が全てtrueになればiPadは検出でき、ついでに2にもう少し条件を足すと他のタブレットも検出可能。

ということで、コードはこれです。

実際のところ、タブレットとスマホは分けて検出できればそれに越したことはないので、こんな感じにしてみた。

// タブレットだけ検出
function js_is_tablet() {
  const ua = window.navigator.userAgent.toLowerCase()
  if (
    (ua.indexOf("windows") != -1 && ua.indexOf("touch") != -1 && ua.indexOf("tablet pc") == -1 )
    || ua.indexOf("ipad") != -1
    || (ua.indexOf("android") != -1 && ua.indexOf("mobile") == -1)
    || (ua.indexOf("firefox") != -1 && ua.indexOf("tablet") != -1)
    || ua.indexOf("kindle") != -1
    || ua.indexOf("silk") != -1
    || ua.indexOf("playbook") != -1
    || ua.indexOf("ipad") > -1
    || ( ua.indexOf("macintosh") > -1 && "ontouchend" in document ) // この行ね!
  ) {
    return true;
  } 
}

// スマホだけ検出
function js_is_smp() {
  const ua = window.navigator.userAgent.toLowerCase()
  if (
    (ua.indexOf("windows") != -1 && ua.indexOf("phone") != -1)
    || ua.indexOf("iphone") != -1
    || ua.indexOf("ipod") != -1
    || (ua.indexOf("android") != -1 && ua.indexOf("mobile") != -1)
    || (ua.indexOf("firefox") != -1 && ua.indexOf("mobile") != -1)
    || ua.indexOf("blackberry") != -1
  ) {
    return true;
  }
}

まだ十分にテストできているコードではないのだが、エラーさえ吐かなければ当面は乗り切れそう。

まだUA使う??

つまりこういうことです。
【テックコラム】Chromeブラウザによる User Agent の削減と User-Agent Client Hints への移行

この情報は1年以上前からマークして、UAを使うことに関しては極力避けるようにしていたのだが、とりあえず、デバイス判別を関数にするなりなんなりして、とにかくメンテナンス性だけ確保しておけばまあいいのかな、という気もする。

基本的にはviewportの幅による出し分けで済むようになると思うが、それはまた別の機会に。