
九保すこひです(フリーランスのITコンサルタント、エンジニア)
さてさて、前回記事Laravelでwebp形式の画像をつくる&使うではwebp
というGoogle
が開発した新しい&軽量な画像をつくってみました。
そして、この画像に関する流れでもう一つある機能をLaravel
でつくってみたくなりました。
それは・・・
gitアニメをつくる機能
です。
「gifアニメ」とは、内容が動いて動画のように見える画像のことです。
おそらく皆さんも一度はみたことがあると思います。
こういうやつですね↓↓↓
gifアニメはファイルサイズ的にいうとそれほど大きくないので、「動画を使うまではしなくていいけど、何か動きがほしい」という場合には重宝されると思います。
そこで!
今回はLaravel
+ Vue
を使ってgifアニメを作る方法をご紹介します。
ぜひ皆さんのお役に立てると嬉しいです
(最後に実際に開発したソースコードをダウンロードすることができますよ)
開発環境: Laravel 6.x、Vue 2.6
目次 [非表示]
やりたいこと
ただ単にgifアニメをつくるだけでは面白くないので、今回は次の手順で実装してみます。
- ブラウザから複数の画像を選択&プレビュー
- 画像を送信
- 送信された画像でgifアニメを作成
- 最後に作成されたgifアニメを表示
では実際にやっていきましょう!
フォルダを準備する
今回作成するgif
アニメ画像は、/public/images/gif
フォルダの中に保存します。そのため、このフォルダを作成し、書き込み権限を与えておいてください。
画像を用意する
gifアニメにする画像を用意します。
今回はLINEスタンプで販売している画像を改造して以下の6つの画像で作成します。(皆さんも適当な画像がない場合、テストとして使ってください)
パッケージをインストールする
gifアニメをつくるためのパッケージが公開されていますので先にcomposer
でインストールしておきましょう。
composer require sybio/gif-creator
ルートをつくる
ではここからが実際にプログラムを書いていく作業になります。
今回必要なルートは以下の2つです。
routes/web.php
に追加しておいてください。
Route::get('animated_gif/create', 'AnimatedGifController@create');
Route::post('animated_gif', 'AnimatedGifController@store');
上のルートが実際にブラウザからアクセスするURL
で、下がAjax
で画像を送信するURL
になります。
コントローラーをつくる
続いてコントローラーです。
AnimatedGifController
という名前の専用コントローラーをつくるので、以下のコマンドを実行してください。
php artisan make:controller AnimatedGifController
すると、app/Http/Controllers/AnimatedGifController.php
というファイルが作成されるので中身を以下のように変更してください。
<?php
namespace App\Http\Controllers;
use GifCreator\GifCreator;
use Illuminate\Http\Request;
class AnimatedGifController extends Controller
{
public function create() {
return view('animated_gif.create');
}
public function store(Request $request) {
$result = false;
$gif_url = '';
if($request->hasFile('images')) {
$frames = []; // 画像のコマ
$durations = []; // 画像が変化する間隔(ミリ秒)
$loop = 0; // 何回ループするか。0は無限
foreach($request->images as $image) {
if($image->isValid()) {
$frames[] = $image->path();
$durations[] = 20;
}
}
$gc = new GifCreator();
$gc->create($frames, $durations, $loop);
$gif_data = $gc->getGif();
$uri = 'images/gif/'. date('U') .'.gif';
$save_path = public_path($uri);
file_put_contents($save_path, $gif_data);
$result = true;
$gif_url = url($uri);
}
return [
'result' => $result,
'gif_url' => $gif_url
];
}
}
create()
は画像を選択するフォームを表示するメソッドで、store()
が画像が送信されてくるメソッドになります。
重要なのはstore()
ですが、この中では単に送信されてきた画像データをGifCreator()
のインスタンスに1つずつ登録し、最終的にgetGif()
でgif画像のデータを取得&保存しているだけです。
ビューをつくる
では最後にビューを作成します。
resources/views/animated_gif/create.blade.php
というファイルを作成し中身を以下のように変更してください。
※ ちなみに今回は比較的新しいコードの書き方を多めに使ってみました。これを機にぜひ勉強してみてください
<html>
<head>
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" rel="stylesheet">
<style>
img {
border: 3px solid #000;
}
</style>
</head>
<body class="p-4">
<div id="app" class="container">
<!-- 画像を選択する部分 -->
<div class="row">
<div class="col-12">
<div class="form-group">
<label>gifアニメにする画像を選んでください(複数)</label>
<input class="form-control" type="file" accept="image/*" multiple @change="onFileChange">
</div>
</div>
</div>
<!-- 選択された画像のプレビュー部分 -->
<div class="row">
<div class="col-4 p-3" v-for="(image,index) in images">
<div>
<span v-text="(index+1)"></span>コマ目
</div>
<img class="img-fluid" :src="image.data">
</div>
</div>
<!-- 画像が選択されたら表示される部分 -->
<div class="row" v-if="images.length">
<div class="col-12 pt-4">
<button class="btn btn-primary" type="button" @click="onSubmit">画像をアップロードする</button>
</div>
</div>
<!-- gifアニメが完成したら表示される部分 -->
<div v-if="gifUrl" class="pt-4">
<img :src="gifUrl">
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.0"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.0/axios.min.js"></script>
<script>
new Vue({
el: '#app',
data: {
images: [],
gifUrl: ''
},
methods: {
onFileChange(e) {
this.images = [];
const files = e.target.files;
let promises = [];
[].forEach.call(files, file => { // 選択された画像をループ
let promise = new Promise(resolve => {
const reader = new FileReader();
reader.onload = e => {
const imageData = e.target.result;
// Promiseに画像データを返す
resolve({
file: file,
data: imageData
});
};
reader.readAsDataURL(file);
});
promises.push(promise);
});
Promise.all(promises).then(images => {
// ファイル名で並び替え
images.sort((a, b) => (a.file.name > b.file.name) ? 1 : -1);
this.images = images;
});
},
onSubmit() {
const url = '/animated_gif';
let formData = new FormData();
this.images.forEach(image => {
formData.append('images[]', image.file);
});
axios.post(url, formData)
.then(response => {
if(response.data.result) {
this.gifUrl = response.data.gif_url;
}
});
}
}
});
</script>
</body>
</html>
では、ひとつずつ説明していきます。
画像を選択&プレビュー表示する部分
画像が選択されたときに実行されるのが、onFileChange()
です。
この中では、選択された画像を[].forEach.call()
で1つずつループし、画像データを読み込むためにPromise
をつくっています。
Promise
は配列になってpromises
に格納され、最終的にPromise.all()
の中で実行される(つまり、ここで画像データが読み込まれる)ことになります。
Promise.all()
はイメージとしては、「チームとして結果がどうなったか」でその後の処理を判断するもので、例えば野球で考えると分かりやすいかもしれません。
- 打順1番の選手から順に打席にたつ
- ヒットがでたら、次の選手が打席へ(←繰り返す)
- ただし、ひとりでもアウトになった時点で試合は負け
つまり、全ての人がヒットを打つことができればPromise.all()
は「成功」、一人でもアウトになったら「失敗」という判断になります。そしてPromise
の中で成功、失敗を決めるのはresolve()
とreject()
です。
let promise = new Promise((resolve, reject) => {
// 処理が成功したら
resolve('データ');
// 失敗したら
reject('(データ)');
});
そして、失敗した場合はcatch()
の方が実行されることになります。
Promise.all(promises).then(data => {
// すべて成功した場合
})
.catch(data => {
// ひとつでも失敗した場合
});
なお、なぜこのような形でPromise
を使っているかというと、「画像の読み込みはいつ完了するかわからないから」です。
つまり、FileReader
のonload
が実行された時点で画像データを集めていくわけですが、画像によってはサイズが小さくすぐ読み込めるけれども、別の画像は読み込みに時間がかかる場合があるため、わざわざPromise.all()
で全ての処理が完了してから(チームとしての結果がわかってから)次の処理に移っているわけです。
プレビュー画像を表示する部分
Promise
を使って取得した画像データを直接<img>
タグのsrc
にセットすると画像が表示されますので、これをプレビュー画像をして使っています。
<div class="col-4 p-3" v-for="(image,index) in images">
<div>
<span v-text="(index+1)"></span>コマ目
</div>
<img class="img-fluid" :src="image.data">
</div>
画像を送信する部分
選択された画像をAjaxで送信するためにaxios
を使っていますが、前回記事と同様にFormData
を使っていることに注目してください。(通常のパラメータとしては送信できません)
なお、画像を送信しgifアニメの作成が成功したらjson
で以下のようなデータが返ってくることになります。
{
"result": true,
"gif_url": "(gifアニメのURL)"
}
テストしてみる
では、実際テストしてみましょう!
まずはページを表示して画像を選択します。
すると、選択された画像のプレビューが表示されるので、送信ボタンをクリックします。
すると、作成されたgifアニメがボタンの下に表示されました!
実際に作成された画像がこちらです。
成功です
ダウンロードする
今回実際に開発したソースコード一式を以下からダウンロードすることができます。
※ ただし、フォルダや送信する画像の用意、パッケージのインストールなどはご自身で行ってください。
Laravelでgifアニメをつくるおわりに
ということで今回はLarave
+ Vue
でgifアニメをつくってみました。
いろいろなテクノロジーを組み合わせると意外と簡単にいろいろな機能が実装できることを知ってもらえると嬉しいです。
なお、JavaScript
の新しい書き方、Promise
や[].forEach.call()
はいかがだったでしょうか。IE 11
には対応していませんが、どんどんIEは過去のものになっている(Edge
はChrome
ベースになりますしね)ので、これからは主流になってくるのは間違いないかと思います。
また、JavaScript
はブラウザだけでなくnodejs
としてサーバーサイドの開発にも使えるようになりましたので、そういった部分でも覚えておいて損はしないんじゃないでしょうか。
ぜひ皆さんも今回の内容を参考にしてみてくださいね。
ではでは〜♪