LaravelでWebP形式の画像変換や配信に対応する

公開したwebサイトを PageSpeed Insights で解析したところ、画像配信の改善を促されたので次世代画像配信形式のWebP形式の配信をLaravelで対応した備忘録です。

WebP形式とは

WebPは、Googleによって開発された画像フォーマットで、ウェブ上での画像の高速な表示と圧縮効率を両立させるために設計されました。

JPEGやPNGといった他の一般的な画像フォーマットと比較して、WebPは高い圧縮効率を提供します。

初期の段階では一部のブラウザでしかサポートされていませんでしたが、現在では主要なブラウザ(Google Chrome、Mozilla Firefox、Microsoft Edgeなど)でWebP画像を表示することができます。

ただし、古いブラウザや一部の環境では対応していないこともあるため、適切なフォールバックを提供することが重要です。

WebP形式の画像を表示する方法

大抵のブラウザではWebP形式の配信に対応していますが、念の為にpng形式などの表示も合わせて実装しておくべきです。

対応方法はHTML5形式の記述でブラウザが対応していればWebP形式で配信し、対応しなければimgタグの画像が表示されるようになります。

<picture>
  <source type="image/webp" srcset="/path/to/file.webp" />
  <img src="/path/to/file.png" alt="file name" class="mx-auto h-32 w-auto object-contain" width="300" height="180" />
</picture>

LaravelでWebP形式の画像保存

Intervention Image を使用します

https://github.com/Intervention/image

使用するためには

  • GD Library (>=2.0)
  • Imagick PHP extension (>=6.5.7)

が必要になります。

大まかな流れとしては

  • すでに配信している画像URLが存在する
  • 複数モデルでWebP対応したい
    • Imageモデルを作成してリレーション表示
  • WebPでは表示しない、通常の画像もまとめてリサイズする
composer require intervention/image
    'providers' => ServiceProvider::defaultProviders()->merge([
        Intervention\Image\ImageServiceProvider::class,
    ])->toArray(),

    'aliases' => Facade::defaultAliases()->merge([
        'Image' => Intervention\Image\Facades\Image::class,
    ])->toArray(),

上記の設定で使えるようになります。

変換後の画像をURLを保存するために Image モデルを作成していきます。

(公式ドキュメントのこちらを参照しつつ対応: https://laravel.com/docs/10.x/eloquent-relationships#one-to-one-polymorphic-relations )

マイグレーションファイルはこちら

        Schema::create('images', function (Blueprint $table) {
            $table->id();
            $table->string('url');
            $table->string('webp_url');
            $table->text('original_url');
            $table->string('label'); // 1モデルに複数画像の場合に対応できるように
            $table->integer('width');
            $table->integer('height');
            $table->string('imageable_id'); // 対応先がULIDでの保存なのでstring
            $table->string('imageable_type');
            $table->timestamps();

            $table->unique(['imageable_id', 'imageable_type', 'label']);
        });

取得するモデルでは以下のようにしておきます

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphOne;

class Post extends Model
{

    public function image(): MorphOne
    {
        return $this->morphOne(Image::class, 'imageable')
            ->where('label', 'thumbnail');
    }
}

以上が下準備、実際に画像の変換をするのは以下のコードで

use App\Models\Post;
use Illuminate\Support\Facades\Storage;
use Intervention\Image\Facades\Image;

$width = 300;
$height = null;

$post = Post::with(['image'])->find(1);
$url = $post->thumbnail;

$value = [
    'label' => 'image',
    'original_url' => $url,
];

foreach (['png', 'webp'] as $type) {
    $filename = 'thumbnail.' . $type;
    $filepath = 'post/' . $post->id . '/' . $filename;
    $image = Image::make($url)
        ->resize($width, $height, function ($constraint) {
            $constraint->aspectRatio(); // アスペクト比維持
        });
    Storage::disk('public')
        ->put($filepath, (string)$image->encode($type));
    $imageUrl = Storage::disk('public')->url($filepath);

    if ($type === 'png') {
        $value['url'] = $imageUrl;
        $value['width'] = $image->width();
        $value['height'] = $image->height();
    }
    if ($type === 'webp') {
        $value['webp_url'] = $imageUrl;
    }
}

($post->image)
    ? $post->image->save($value)
    : $post->image()->create($value);

これをコマンドやjob/queueに入れて画像を保存したら変更しなおす流れを作りましょう。

LaravelでのWebP画像表示 (Vue.js版)

画像が正常に保存されていれば以下のコードで表示できます。

<picture>
    <source
        type="image/webp"
        :srcset="post.image?.webp_url"
    />
    <img
        :src="post.image?.url ?? post.thumbnail"
        :alt="post.title"
        :height="post.image?.height"
        :width="post.image?.width"
        class="mx-auto h-32 w-auto object-contain"
    />
</picture>

Vue.jsでの表示では動的にリレーションの読み込みをしてくれないので、コントローラーであらかじめ Imageモデルを読み込みしておきましょう。

Post::with(['image']);

or

$post->load(['image']);

最後に

LaravelによるWebP形式の画像保存や配信に対応する方法でした。

こちらの方法でWebP形式の画像保存ができるので、公式のドキュメントを閲覧しながらご自分のプロジェクトに合った形式に変えていくことをオススメします。

公式ドキュメントはこちら

https://image.intervention.io/v2

以上です、お疲れ様でした。