九保すこひ@フリーランスエンジニア|累計300万PVのブログ運営中
さてさて、これまでウェブかコマンドラインばかりの開発だったのがElectron
でデスクトップアプリを開発をするようになって、まだまだ試しにやってみたいことが思い浮かんできています。
特にElectron
はアップロードをせずにデスクトップ上のファイルにアクセスができるので、画像やテキストなどファイル関連の機能と親和性が高いのかなと感じています。
そこで、今回開発するアプリは「アニメGIFをつくる」アプリです。
アニメGIF(animated gif)とは、動画のように動かすことができる画像のことで、次のようなものです。インターネット上でも珍しくないのでみなさんも一度は見たことがあるんじゃないでしょうか。
このアニメGIF、「動きを見せたいんだけど、JavaScriptとか動画にするまででもないよな・・・」なんてときに使えます。しかも動画ほどファイルサイズが大きくもないので配布するにも都合が良かったりします。
ということで、今回はアニメGIFを作ることができるアプリを開発してみます!
※ 開発環境: Electron、ImageMagick 6.9
目次
やりたいこと
アニメGIFをつくるといっても、せっかくElectron
でアプリを作るので、次の機能もつけることにしました。
- 画像をアプリから選択できる
- 切り替わる画像の順番をドラッグ&ドロップで変更できる
- 画像が切り替わる間隔を指定できる
の3つです。
準備
今回、実際アニメGIFの作成はImageMagick
を使います。
そのため、事前にImageMagick
が使えるようインストールしておいてください。
私の同じくUbuntu環境でしたら以下のコマンドでインストールできます。
sudo apt install imagemagick
Electronのインストール
インストールは、初心者向き!electron で簡単なメモ帳をつくってみようの “electronのインストール” に詳しく書いてあるので、そちらを見てください。
フォルダ名はanime-gif-maker
です。
アプリの設定
main.js
を開いてウィンドウサイズ、位置、メニューバーの非表示を設定します。
mainWindow = new BrowserWindow({ width: 500, height: 500, x: 0, y: 0, autoHideMenuBar: true, webPreferences: { preload: path.join(__dirname, 'preload.js'), nodeIntegration: true } })
【追記:2020.3.19】Electron v5
以降のためにwebPreferences
を追加しました。
また、私の環境のようにLinuxを使っているとアプリ上でドラッグ&ドロップするとクラッシュしてしまうバグがあるので、app.disableHardwareAcceleration()
を使ってこれを回避しておきます。(どうやらハードウェア・アクセラレーションが原因のようです)
const {app, BrowserWindow} = require('electron') app.disableHardwareAcceleration()
CSS、JSのフレームワーク
レイアウト用にbootstrap
、JavaScript用にVue
、そして要素をドラッグ&ドロップで並べ替えることができるsortablejs
をインストールします。
npm i bootstrap --save npm i vue --save npm i sortablejs --save
レイアウトをつくる
では、bootstrap
を使ってレイアウトをつくります。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>アニメGIFをつくるアプリ!</title> <link rel="stylesheet" href="./node_modules/bootstrap/dist/css/bootstrap.min.css"> <style> body { padding: 15px 0; } #image-container { background: #eee; padding: 15px; border-radius: 5px; height: 300px; } #time { width: 200px; } #button { width: 150px; position: absolute; right: 15px; bottom: 20px; } </style> </head> <body> <div id="app" class="container"> <select id="time" class="form-control float-right"> <option>切り替わり(1/100秒)</option> </select> <label class="btn btn-light"> 画像を追加<input type="file" accept="image/*" multiple hidden> </label> <hr> <label>表示順を並べ替える(ドラッグ&ドロップできます)</label> <div id="image-container"> (ここに画像リスト) </div> <button id="button" type="button" class="btn btn-dark">アニメGIFをつくる</button> </div> <script> require('./renderer.js') </script> </body> </html>
まず画像を選択するボタンですが、通常の<input type="file">
ではなく、ボタンをクリックすることで「ファイル選択ダイアログ」が表示されるテクニックを使っています。
また、ファイル選択できるのはaccept="image/*"
で画像だけに制限し、multiple
で複数選択を許可しています。
そして、<select>
で画像が切り替わる間隔を指定できるようにし、選択された画像のリストはドラッグ&ドロップで順番を入れ替えられるようにしています。
実際の画像はこちらです。
では、JavaScript部分をつくっていきましょう!
JavaScript部分をつくる
Vueの基本形をつくる
まずはVueを読み込んで基本形をつくります。
<script src="./node_modules/vue/dist/vue.min.js"></script> <script> new Vue({ el: '#app' }) </script>
画像を選択する部分をつくる
次に画像の選択部分です。
<input type="file">
にファイルが選択されたときに実行するsetImages()
をクリックイベントとして設定します。
<input type="file" accept="image/*" @change="setImages($event)" multiple hidden>
そして、data
に選択された画像ファイルのデータが入るfiles
を追加し、さらにmethods
にもsetImage()
を追加します。
data: { files: [] }, methods: { setImages(e) { this.files = e.target.files } }
これで、ファイルが選択されたら自動的にfiles
にデータが反映されることになります。
切り替わり時間の部分をつくる
次に切り替わる間隔を選択するセレクトボックスの部分です。
まずdata
へ時間を格納するtime
を追加。
v-model
でバインディングします。
data: { files: [], time: 10 }
そして、いつもならtimeOptions
などとして選択肢も追加するのですが、今回はv-for
の数字バージョンを使うことにします。
<select id="time" class="form-control float-right" v-model="time"> <option v-for="i in 10" :value="i*10">切り替わり({{ i/10}} 秒)</option> </select>
実行したものがこちらです。
画像が表示される部分をつくる
画像がドラッグ&ドロップで並べ替えられるようにしたいので、先ほどインストールしたsortablejs
を読み込みます。
window.Sortable = require('sortablejs')
※ なお、通常の<script src="***">
ではうまくいきません。
次に、画像をv-for
でレンダリングします。
<div id="image-container"> <img v-for="(file,index) in files" :src="file.path" :data-index="index" class="image"> </div>
data-index
の部分は、後で画像の順番を決める時に使います。
また、画像が選択されたらimage-container
へSortable
を設定します。
setImages(e) { this.files = e.target.files Vue.nextTick(() => { const el = document.getElementById('image-container') Sortable.create(el) }) }
※ なお、Vue.nextTick()
を使っているのは、確実に表示がVueによって変更された後でSortable
を設定するためです。
では、一旦どんな風に表示されるかをチェックしてみましょう。
うまくいきました!
ドラッグ&ドロップで画像を移動させることもできます。
アニメGIFをつくる部分をつくる
では、やっと本題にたどりつきました。
「アニメGIFをつくる」ボタンがクリックされたときのコードをつくっていきます。
まずはファイル操作をするモジュールfs
、コマンドを実行するspawn
が使えるように読み込みます。
const fs = require('fs') const {spawn} = require('child_process')
なお、ImageMagick
のコマンドではファイル名が連番になっている必要があるため、tmp
というフォルダを作っておき、ここへ選択された画像のコピーを作ってからコマンドを実行するようにします。
そして、generateAnimatedGif()
が実行されるクリック・イベントです。
<button id="button" type="button" class="btn btn-dark" @click="generateAnimatedGif()">アニメGIFをつくる</button>
generateAnimatedGif()
、その中で呼び出すrefreshTempFiles()
、makeTempFiles()
はこうなります。
generateAnimatedGif() { this.refreshTempFiles() this.makeTempFiles() const now = Date.now() const tmpPathPattern = __dirname +'/tmp/*.png' const outputPath = `${__dirname}/animated_${now}.gif` const command = `/usr/bin/convert -delay ${this.time} -loop 0 -alpha remove ${tmpPathPattern} ${outputPath}` const process = spawn('/bin/sh', ['-c', command]) process.on('close', (code) => { if(code == 0) { alert('アニメGIFが作成されました!'); } }); }, refreshTempFiles() { const tmpPath = __dirname +'/tmp/' fs.readdirSync(tmpPath) .forEach((file) => { fs.unlinkSync(tmpPath + file) }) }, makeTempFiles() { const el = document.getElementById('image-container') const images = el.childNodes images.forEach((image, i) => { let index = image.getAttribute('data-index') let file = this.files[index] let filename = i.toString().padStart(6, '0') +'.png' let tempPath = __dirname +'/tmp/'+ filename fs.copyFileSync(file.path, tempPath) }) }
まずrefreshTempFIles()
でtmp
フォルダ内の全ファイルを削除します。
そして、makeTempFiles()
でimage-container
の中にある画像をchildNodes
で取得し一時ファイルを連番で作成していきます。
さらに、最後にspawn
でコマンドを実行しますが、ここで重要なのが/usr/bin/convert
や/usr/sh
などのパスです。
これらは、which sh
やwhich convert
などでコマンドを実行すると表示される絶対パスです(Windowsの場合はwhere
)。パスが通っていれば問題ありませんが念の為この形にしています。各自環境に合わせて変更してください。
なお、今回のconvert
コマンドには-alpha remove
をつけて透過部分を削除するようにしています。
では、実際に今回のアプリを使って作成したアニメGIFをご覧ください。
↓↓↓
お疲れ様でした!
教材ソースコードをダウンロードする
今回実際に開発したソースコード一式をダウンロードすることができます。
※ ただしnpmのインストールやアニメGIF化する画像はご自身で用意してください。
Electron でアニメGIFをつくる