見出し画像

laravel breeze (react) にavater機能を付ける(4) filepondとIntervention Image

の続き

filepondの適用

今、avatarのアップロードの部分が圧倒的旧世代感があるので何とかする。

install

npm install react-filepond filepond-plugin-file-validate-type filepond-plugin-image-preview

最低限、画像previewプラグインはやっぱ欲しいなっていう

設置

実はすげーワンタッチな感じでできる。まずズバっとこの5行を書き足す

import { FilePond, registerPlugin } from 'react-filepond';
import FilePondPluginFileValidateType from 'filepond-plugin-file-validate-type';
import FilePondPluginImagePreview from 'filepond-plugin-image-preview';
import 'filepond/dist/filepond.min.css';
import 'filepond-plugin-image-preview/dist/filepond-plugin-image-preview.min.css';

ただ、今回は画像を扱う、画像の場合はpreviewのプラグインとかも欲しいから

import { FilePond, registerPlugin } from 'react-filepond';
import FilePondPluginFileValidateType from 'filepond-plugin-file-validate-type';
import FilePondPluginImagePreview from 'filepond-plugin-image-preview';
import 'filepond/dist/filepond.min.css';
import 'filepond-plugin-image-preview/dist/filepond-plugin-image-preview.min.css';
registerPlugin(FilePondPluginFileValidateType, FilePondPluginImagePreview);

このように書くとよい。

置き換え

resources/js/Pages/Profile/Partials/UpdateProfileInformationForm.jsx
のinput type="file"的なところ


<FilePond
  name="avatar"
  onupdatefiles={files => {
    if (files.length > 0) {
      setData('avatar', files[0].file);
    } else {
      setData('avatar', null);
    }
  }}
  allowMultiple={false}
/>

確認

まあ実はこれだけで大抵動いてしまう。

また例のおっさん

送信が終わったらファイルをクリアーする

まあこんなのこそ残っていてもいいような気もするんだけど、気になる場合useRefでリファレンスを呼びこんでそこからメソッドを発動する。ここではclearFileという関数に纏めている

import React, { useRef } from 'react'; // useRefを追加して

// 略

export default function UpdateProfileInformation({ mustVerifyEmail, status, className = '' }) {
  const { user } = usePage().props.auth;
  const filePondRef = useRef(null); // filePondのrefを格納するやつ
// 略
  const clearFile = () => {
    /* // これを削除し
    const fileInput = document.getElementById('avatar');
    if (fileInput) {
      fileInput.value = '';
    }
    reset('avatar');
    */
    // この辺を追加する
    if (filePondRef.current) {
      filePondRef.current.removeFile();
    }
    reset('avatar');
  }

<FilePond
  ref={filePondRef} // refを与える
  name="avatar"
  onupdatefiles={files => {
    if (files.length > 0) {
      setData('avatar', files[0].file);
    } else {
      setData('avatar', null);
    }
  }}
  allowMultiple={false}
/>

さらなる改造

画像ファイルしか受けつけないようにする(+サイズ制限も)

これはフロントエンドとバックエンドの両方で実施する必要がある。
app/Http/Requests/ProfileUpdateRequest.php を更新

<?php

namespace App\Http\Requests;

use App\Models\User;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;

class ProfileUpdateRequest extends FormRequest
{
    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, \Illuminate\Contracts\Validation\Rule|array|string>
     */
    public function rules(): array
    {
        return [
            'name' => ['string', 'max:255'],
            'email' => ['email', 'max:255', Rule::unique(User::class)->ignore($this->user()->id)],
            'avatar' => ['nullable', 'image', 'max:2048']
        ];
    }
}

とかするとimageかつ2048Mに制限されるのであるが、フロントでは

npm install filepond-plugin-file-validate-size

し、filepond-plugin-file-validate-size を呼びこむ。そして

import { FilePond, registerPlugin } from 'react-filepond';
import FilePondPluginFileValidateType from 'filepond-plugin-file-validate-type';
import FilePondPluginImagePreview from 'filepond-plugin-image-preview';
import 'filepond/dist/filepond.min.css';
import 'filepond-plugin-image-preview/dist/filepond-plugin-image-preview.min.css';
import FilePondPluginFileValidateSize from 'filepond-plugin-file-validate-size';
registerPlugin(
  FilePondPluginFileValidateType,
  FilePondPluginImagePreview,
  FilePondPluginFileValidateSize // この行を追加
);

こんな感じでプラグインを登録したら

<FilePond
  name="avatar"
  onupdatefiles={files => {
    if (files.length > 0) {
      setData('avatar', files[0].file);
    } else {
      setData('avatar', null);
    }
  }}
  allowMultiple={false}
  acceptedFileTypes={['image/jpeg', 'image/png', 'image/gif']}
  maxFileSize="2MB"
/>

などする

File is too largeが出た。

しかし2Mの制限はキツいので今日日iphoneなんかからの画像を受信するには実際には3M〜5Mくらいだろう。

で、laravelからのエラーも一応出しているんだけど基本的にはユーザーには伝わらないようにしているから、ここの日本語化は必要ないだろう。なんか間違って出ちゃったときに何か出ればいいや

FilePondの日本語

まず、localeは既にinstallされている。これを適当にimportする。ja-jaって何なんだろ…

import localeJA from 'filepond/locale/ja-ja.js';

とかlocaleJAにimportしておいて

<FilePond
  ref={filePondRef}
  name="avatar"
  onupdatefiles={files => {
    if (files.length > 0) {
      setData('avatar', files[0].file);
    } else {
      setData('avatar', null);
    }
  }}
  allowMultiple={false}
  acceptedFileTypes={['image/jpeg', 'image/png', 'image/gif']}
  maxFileSize="2MB"
  {...localeJA}
/>

とか

日本語になりましたと

exifの削除とローテーション (intervention/image)

たとえばこのiphoneで取ったしょーもない画像

これをアップロードすると

こんな感じであやしい向きになる。

でまずツールなんかでこのexifを見てみると

Orientation                     : Rotate 90 CW
GPS Latitude                    : 34 deg 42' 5.81" N
GPS Longitude                   : 135 deg 29' 46.76" E

など、位置情報が残ってこういうの残ってると場合によっては家特定炎上みたいなしょーもない事を気にしないといけなので、exifのOrientation情報に基いて回転させて、かつexifを削除するのがもはやベストプラクティスと言う状態である。

Intervention Image

というわけでこのライブラリーを使う。なかなかこの程度の機能でも工数重たくなりますよね。

composer require intervention/image

これを通せば回転は行ってくれるが、exifの削除は行わない。いや、正確にはGDドライバーなら勝手にexifは消滅するが、imagickを使う場合は気をつけたい。

GDドライバーの場合

        if ($request->hasFile('avatar')) {
            $manager = ImageManager::gd();
            $image = $manager->read($request->file('avatar'));
            $image->save();

これだけで全てが足りる。今回はこちらを採用するが

Imagickドライバーの場合

            $manager = new ImageManager(new Driver());
            $image = $manager->read($request->file('avatar'));
            $image->save();
            $imagick = new Imagick($request->file('avatar')->path());
            $imagick->stripImage(); // EXIFデータを削除
            $imagick->writeImage($request->file('avatar')->path());

こんな感じ?今はgetCore()もなくなっちゃったりとかでまあとにかくimagemagickを使いたい場合は面倒くさいのでまあ頑張るぞいって感じっすかねえ。



この記事が気に入ったらサポートをしてみませんか?