PHP
WordPress
AMP
amp-img

WordPressのimgタグをamp-imgタグで出力する方法

記事「画像の遅延読込に役立つamp-imgの使い方 - ICS MEDIA」で、amp-imgタグを使うと遅延読み込み(レイジーロード)で役立つことを紹介した。

画像の遅延読み込みは縦に長いページで効果的。特にブログ投稿で画像を大量に扱う場合に力を発揮する。WordPressのようなCMSで利用すると、遅延読み込みの恩恵を受けられるだろう。

そこで、WordPressでの実装方法を紹介する。このコードはICS MEDIAで使っている変換コードそのものだが、様々なケースに対応できるよう汎用化していないので、コピーして使う場合は注意してほしい(免責)。

考え方

WordPressのフックのタイミング

WordPressの画像タグを置換する方法が公式には存在しない。何かのフックがあるわけではない(投稿画面での画像挿入時のフックはあるが、少し目的が異なる)。

WordPressのthe_content()メソッドで記事の出力を得ることができる。このとき、WordPressは自動的にimgタグにHTML 5.1から搭載されたsrcset属性を付与する。srcset属性は記事「レスポンシブイメージで画像の表示を最適化 - ICS MEDIA」で紹介しているように、端末のサイズごとに無駄に大きな画像を転送させないための手段だ。amp-imgでもsrcset属性は可能な限り使うべきだ。

そうなると、投稿画面での画像挿入時のフックはタイミングが早すぎる。現実的に使えるのはthe_content()メソッドをフックすることだ。

置換方法

imgタグをamp-imgタグに単純に文字列置換すればいいわけではない。文字列置換だけでうまくいくケースもあるだろうが、失敗するケースもある。例えば、the_content()メソッドではimg要素にsrcset属性だけでなくsizes属性も自動的に追加する。このsizes属性がamp-imgのレスポンシブ対応と相性が悪い。私の場合は、sizes属性だけは除去するようにした。

また、amp-imgタグにはlayout="responsive"属性をつけるのが多くのケースで事実上必須である。

いずれにせよ、amp-img要素の属性値はimg要素から引き継いだり加工しなければならない。それは文字列置換だけで処理するのは困難だ。そこで考え方としては、DOM操作を使う。PHPにはphpqueryというライブラリがあるので、これを使う。

余談だが、フロントエンド側でimgタグをamp-imgタグにjQueryで置換してみたが、DOMContentLoadedのタイミングだと遅すぎて、ブラウザはimgタグのsrc属性を読み込もうとする。JSだと遅すぎるので、サーバーサイドの段階でamp-imgに置換するしかない。

実装方法

利用するには「phpquery」が必要になる。テーマフォルダーのなかに格納していることが前提。

functions.php の適当な場所に次の関数を定義する。

functions.php
/**
 * コンテンツのHTML文字列からimg要素をamp-img要素に変換します。
 * 動作には「phpQuery-onefile.php」が必要です。
 * ダウンロードして参照できるようにしておいてください。
 * 
 * @param string
 * @return string
 */
function convertImgToAmpImg($the_content)
{
    // PHPのパスを解決(相対パスだとライブラリを読み込めないため)
    require_once(dirname(__FILE__) . "/libs/phpQuery-onefile.php");

    // 仮想DOMを構築(phpQueryで走査するため)
    $html = <<<HTML
<html>
<body>{$the_content}</body>
</html>
HTML;

    // DOMを構築
    $dom = phpQuery::newDocument($html);

    // img要素を探し出して、繰り返す
    foreach ($dom->find("img") as $img) {
        // 参照を取る
        $pqImg = pq($img);

        // 属性値をコピーする
        $obj["src"] = $pqImg->attr("src");
        $obj["width"] = $pqImg->attr("width");
        $obj["height"] = $pqImg->attr("height");
        $obj["srcset"] = $pqImg->attr("srcset");
        $obj["alt"] = $pqImg->attr("alt");
        // sizes属性は表示崩れの可能性があるのでコピーしない

        // src 属性がなければ変換しない
        if (empty($obj["src"])) {
            continue;
        }

        // width と height がなければ強制的に 16:9 を代入する
        if (empty($obj["width"]) || empty($obj["height"])){
            $obj["width"] = 960;
            $obj["height"] = 540;
        }

        // 属性をコピーする
        $attrStr = [];
        foreach ($obj as $key => $value) {
            if (!empty($value)) {
                $attrStr[] = "$key=\"$value\"";
            }
        }

        // layout属性を追加する
        $attrStr[] = 'layout="responsive"';

        // img要素をamp-img要素に置き換える
        // コピーした属性値をくっつける
        $pqImg->replaceWith("<amp-img " . join(" ", $attrStr) . " />");
    }

    // contentの内容を返す
    return $dom->find("body")->html();
}

記事を出力する場面では、add_filter()メソッドでフックしておく。

single.php
<section itemprop="articleBody" role="main">
    <?php
    // img タグを amp-img に置換する
    add_filter('the_content', 'convertImgToAmpImg', 9999);
    // 記事コンテンツを出力する
    the_content();
    ?>
</section>

利用環境

  • WordPress 4.9.6
  • PHP 5.6.99-hhvm
  • HHVM Version 3.9.1

まとめ

amp-imgは便利。WordPressに組み込むと効果的だが、影響範囲が大きいので、実装は慎重に。