masalibの日記

システム開発、運用と猫の写真ブログです

React Firebase入門 FirebaseStorageのファイル圧縮してアップ

テストをしていたら画像がファイルサイズが大きい事に気が付きました。 Firebaseを無料で使っていくためにはファイルサイズは小さくした方がいいです。 Functionsで画像の圧縮をしたかったが、Google先生が用意しているサンプルがtypescriptで記載されていた。 いずれはtypescriptに移行しないといけないけど、今じゃない気がするので画像圧縮ライブラリー(blueimp-load-image)を導入して対応した。

この記事はReact Firebase入門シリーズです 1-1-1・ Firebase初期設定とFirebaseAuthのSignUp
1-1-2・ 「react-hook-form」を入れてみた
1-1-3・ AuthのSignUpの通信エラー対応
1-2 ・ FirebaseAuthのログイン処理
1-3 ・ FirebaseAuthのログイン認証とログアウト処理
1-4 ・ FirebaseAuthのパスワード初期化処理
1-5 ・ FirebaseAuthのメールアドレスの有効化
1-6 ・ FirebaseAuthのメールアドレスとパスワード変更
1-7 ・ FirebaseAuthの表示名変更
1-8 ・ FirebaseAuthの拡張項目追加
1-9-1 ・ FirebaseAuthのTwitter認証
1-9-2 ・ FirebaseAuthのTwitter認証(既存ユーザー向け)と解除
1-10 ・ FirebaseAuthのGoogle認証(既存ユーザー含む)と解除 2-1・ FirebaseStorageのファイルアップ:基礎
2-2・ FirebaseStorageのファイルアップ前に画像の切り抜き
2-3・ FirebaseStorageのファイルアップの移動
2-4・ FirebaseStorageのファイル圧縮 今ここ
3-1・FirestoreのCRUD
3-2・Firestoreのデータ取得補足
3-3・Firestoreのページネーション処理
3-4・Firestoreのコレクション(テーブル)のJOIN
4-1・Realtime Databaseでchatアプリ(書き込み)
4-2・Realtime Databaseでchatアプリ(一覧取得)
5・ FirebaseのHosting(デプロイ)
6-1・ Cloud FunctionsでHello,world
6-2・ Cloud Functions(エミュレータ)でHello,world
6-3・ ユーザーの作成時にCloud Functionsを実行
6-4・ Cloud Functionsでメール送信
6-5・ Cloud Functionsでslackに通知する
よかったら他の記事も見てください。

やりたい事

  • 画像の圧縮してファイルをアップする。

私の場合は、画像をクロップしてから圧縮するという通常じゃないパターンです。

処理としては以下の流れになる

  1. ローカル画像(普通の画像)
     ↓
  2. クロップ画像(data_url形式のバイナリーデータ)
     ↓
  3. 画像圧縮(blob形式のバイナリーデータ)
     ↓
  4. Firebase Storage(普通の画像)

もし通常パターンの圧縮を知りたい場合は参考にしたページをみてください

  1. ローカル画像(普通の画像)
     ↓
  2. 画像圧縮(blob形式のバイナリーデータ)
     ↓
  3. Firebase Storage(普通の画像)

zenn.dev

画像圧縮ライブラリーのインストール

npm install blueimp-load-image

プログラム上では普通に使うだけです。

import loadImage from 'blueimp-load-image'  //画像圧縮

github.com

この圧縮ライブラリーはreactのライブラリーじゃないのでPureのjavascriptでも動きます。

画像アップのプログラム

    const getCropData  = async(e) => {
        e.preventDefault();
        if (typeof cropper !== "undefined") {
        //console.log(cropper.getCroppedCanvas().toDataURL())
            //let imagedata = await cropper.getCroppedCanvas().toDataURL()  //デフォルト設定だとpng
            let imagedata = await cropper.getCroppedCanvas().toDataURL('image/jpeg')
            //console.log(imagedata)

            //data_url => Blob 
            let byteString = atob( imagedata.split( "," )[1] ) ;
            let mimeType = imagedata.match( /(:)([a-z\/]+)(;)/ )[2] ; //UnnecessaryのWARNINGがでるが無視するしかない

            for( var i=0, l=byteString.length, content=new Uint8Array( l ); l>i; i++ ) {
                content[i] = byteString.charCodeAt( i ) ;
            }
            let blob = new Blob( [ content ], {
                type: mimeType ,
            } ) ;

            const canvas = await loadImage(blob, {
                maxWidth: 960,
                canvas: true,
            });

            canvas.image.toBlob((blob) => {
                //firebase.storage().ref().child(`/${files[0].name}`).put(blob);
                // アップロード処理
                console.log("アップロード処理");
                const storages = firebase.storage();//storageを参照
                const storageRef = storages.ref("images/users/" + currentUser.uid + "/");//どのフォルダの配下に入れるか
                const imagesRef = storageRef.child("profilePicture.png");//ファイル名

                console.log("ファイルをアップする行為");
                const upLoadTask = imagesRef.put(blob);

                console.log("タスク実行前");
                setOpenCircularProgress(true);
                upLoadTask.on(
                    "state_changed",
                    (snapshot) => {
                        console.log("snapshot", snapshot);
                    },
                    (error) => {
                        console.log("err", error);
                        setError("ファイルのアップロードに失敗しました")
                        setOpenCircularProgress(false);
                    },
                    () => {
                        upLoadTask.snapshot.ref.getDownloadURL().then((downloadURL) => {
                        //console.log("File available at", downloadURL);
                        const url = new URL(downloadURL)
                        console.log(url.href + url.pathname + "?alt=media")
                        dispatch({
                            type: "setPhotoURL",
                            payload: url.href + url.pathname + "?alt=media"
                            });
                        });
                        setOpen(false);
                        setOpenCircularProgress(false);
                    }
                );
            }, mimeType);
            //const upLoadTask = imagesRef.putString(imagedata, 'data_url');
        }
    };

このgetCropDataの関数は「選択範囲で反映」のボタンを押した時に起動する

結果

2,820,841 バイト
 ↓
998,208 バイト

リサイズされました

追記:
クロップする時にjpeg形式を指定したらもっと下がった。圧縮いらなかったかも・・・
998,208 バイト
 ↓
130,115 バイト

できていない事

let mimeType = imagedata.match( /(:)([a-z\/]+)(;)/ )[2] ; //UnnecessaryのWARNINGがでるが無視するしかない

上記のコードだとWARNINGが発生する・・・よくわからず放置している

感想

  • だんだん複雑になってきた、リファクタリングしないといけない気がする
  • 一段落したらtypescriptで作り直したい

参考URL

lab.syncer.jp

github.com