PHP

画像のHTMLタグを lazyload(遅延読み込み)用に変換・置換する

PHPでHTMLの画像タグ<IMG />を lazyload.js 用に置き換えるメモ。WordPressでも出力されるsrcset、sizes属性にも対応させます。

LazyLoadは、バニラ、要は素のJS版とjQuery版があります。ウチではどうせjQueryを読み込むことが多いのでjQuery版の方をよく使っています。

参考: Lazy Load Remastered

で、詳しい使い方は割愛しますが、LazyLoadで画像を遅延読み込みさせるには、とりあえずとても軽い画像を割り当てておいて、data-src(jQuery版ではdata-original)属性に本来の画像パスを設定しておかないといけません。

<img decoding="async" data-src="./large.jpg" src="./blank.png" alt="" class="lazyload" />
<noscript><img decoding="async" src="./large.jpg" alt="" /><\/noscript>

たいてい、スクリプト無効環境に配慮して<NOSCRIPT />で囲った本来の画像タグもセットにした、上記のような記述になります。

いちいち書いてられないうえ、WordPressの the_post_thumbnail()のように自動で出力されるものもあるので正規表現で置き換えます。


function replace_to_lazyload( $content ) {
  return preg_replace_callback('/(?<!noscript>)<img([^>]+?)src=[\'"]?([^\'"\s>]+)[\'"]?([^>]*)>/i', function ($matches) {
    // すでにdata-original(src)属性があれば何もしない
    if ( strpos( $matches[0], ' data-original=' ) ) return $matches[0];

    // srcset, sizes 属性、'lazyload' クラス、を追加
    $append_class = false;
    foreach( array(1, 3) as $n ) {
      if ( strpos($matches[$n], ' srcset=') ) {
        $matches[$n] = str_replace(' srcset=', ' data-srcset=', $matches[$n]);
      }
      if ( strpos($matches[$n], ' sizes=') ) {
        $matches[$n] = str_replace(' sizes=', ' data-sizes=', $matches[$n]);
      }
      if ( strpos($matches[$n], 'class=') ) {
        $matches[$n] = preg_replace('/class=([\'"]?)/i', 'class=${1}lazyload ', $matches[$n]);
        $append_class = true;
        break;
      }
    }
    if ( ! $append_class ) {
      $matches[3] = ' class="lazyload"' . $matches[3];
    }

    // ダミーの画像
    $blank = 'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==';

    //
    $lazyload = sprintf( '<img%1$sdata-original="%2$s" src="%4$s"%3$s>', $matches[1], $matches[2], $matches[3], $blank );

    // スクリプト無効環境用のタグ追加
    $lazyload .= sprintf( '<noscript>%s</noscript>', $matches[0] );

    return $lazyload;
  }, $content );
}

// 元のコード(WPで吐き出されるようなHTML)
$img = '<img decoding="async" width="600" height="600" src="http://example.jp/large.jpg" class="wp-post-image-1" alt="テスト画像" loading="lazy" srcset="http://example.jp/large-300x300.jpg 300w, http://example.jp/large.jpg 600w" sizes="(max-width: 300px) 100vw, 300px">';

// 遅延読み込み用に変換して出力
echo replace_to_lazyload( $img );
>
<img decoding="async" width="600" height="600" data-original="http://example.jp/large.jpg" src="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="lazyload wp-post-image-1" alt="テスト画像" loading="lazy" data-srcset="http://example.jp/large-300x300.jpg 300w, http://example.jp/large.jpg 600w" data-sizes="(max-width: 300px) 100vw, 300px" /><noscript><img decoding="async" width="600" height="600" src="http://example.jp/large.jpg" class="wp-post-image-1" alt="テスト画像" loading="lazy" srcset="http://example.jp/large-300x300.jpg 300w, http://example.jp/large.jpg 600w" sizes="(max-width: 300px) 100vw, 300px" /></noscript>

レスポンシブな画像の属性、、srcset, sizesはそのままdata-*属性に置き換えてやれば良いようです。例は自分用にdata-original属性に置き換えているのでコピペする場合は適宜data-src属性に書き換えてください。

それから、簡潔にするためにいくつか妥協しているところがあって、例えば「大文字のタグもマッチするけどその後の属性処理では大文字無視」とか「画像を囲む noscript 要素に属性があったり後ろに改行とか混じるとマッチしない」とか、運用するHTMLによってはうまく動作しない可能性もあります。そのあたりご了承ください。

あと、JS側でのセレクタに .lazyload のような指定が必要なければ、クラスを追加する行はいらなくなってもう少しシンプルにできると思います。ダミーの画像にはデータURL(data:image/〜)のGIF使ってますが、見にくいとか長いとか気に入らなければ1x1pxの透過PNGとかでもいいと思います。

WordPressでは、フィルターフックに登録とかして運用するのが普通だと思います。他の色々なフック、画像を吐き出すようなものの後に処理するといいと思います。

// 投稿のコンテンツにフィルター(99番)を追加
add_filter('the_content', 'replace_to_lazyload', 99, 1);