はじめに
HTML/CSS/JSを使ってデスクトップアプリケーションが作成できるElectron。
本記事ではデスクトップウィジェット作成するまでの流れを、簡単な時計アプリを題材として解説します。
※ この時計アプリのソースコードとパッケージは、GitHubにて公開しています。
GitHub: SallyAcolyte/tutorial_clock
対象読者
- HTML/CSS/JSで簡単なアプリは作れるが、Electronは初心者な方
- Electronで透過ウィンドウ/フレームレスウィンドウを扱う際のポイントが知りたい方
- 自分好みなデザイン/機能を持つウィジェットを作成したい方
※ Windows環境を前提としていますが、基本的な流れは他環境でも同様です。
チュートリアル
Electronの入手
Electronの本体は、以下からダウンロードできます。
Releases · atom/electron
※ Windows 64bit環境の場合は、electron-v[バージョン]-win32-x64.zip
を利用します。
アプリを実行する際に利用しますので、任意の場所に展開して下さい。
Hello World
まずは、お決まりの「Hello World」を表示するアプリを作成します。
app
というフォルダを作成し、以降のファイルはそのフォルダ内に入れて下さい。
package.json
始めにpackage.json
を作成します。
名前やバージョンなど、アプリに関する情報を記載するファイルです。
main
で指定されたファイルが、アプリの実行時に読み込まれます。
{
"name" : "tutorial_clock_digital",
"version" : "0.0.1",
"main" : "main.js"
}
main.js
次にmain
で指定したmain.js
を作成します。
ウィンドウ周りの処理などを実装しますが、まずはそのまま貼り付ければOKです。
後ほど「// ウィンドウ作成時のオプション」部分に追記します。
var app = require("app");
var BrowserWindow = require("browser-window");
var mainWindow = null;
app.on("window-all-closed", function() {
if (process.platform != "darwin") {
app.quit();
}
});
app.on("ready", function() {
mainWindow = new BrowserWindow({
// ウィンドウ作成時のオプション
});
// index.html を開く
mainWindow.loadUrl("file://" + __dirname + "/index.html");
mainWindow.on("closed", function() {
mainWindow = null;
});
});
index.html
最後に、index.html
を作成します。
実際にアプリのウィンドウ内に表示されるファイルです。
通常のHTMLでアプリのインタフェースを作成します。
<h1>Hello World</h1>
実行
* ここまででのファイル構成
app
├index.html
├main.js
└package.json
これで、必要なファイルは揃いました。
先ほど用意したelectron.exeに、作成したappフォルダを「フォルダごと」ドロップするとアプリが起動します。
または、コマンドプロンプト等から引数として渡してもOKです。
![アプリの起動(コマンドプロンプト)]
(https://qiita-image-store.s3.amazonaws.com/0/83758/47a2054b-8f2d-12e2-1f98-7d1fe00a2555.png)
アプリのウィンドウが開き、index.html
が表示されます。
時計の作成
ここから、実際に動作させる時計を作成します。
HTML/CSS/JSで、普通のブラウザで動くように作成すればOKです。
※ Electron特有の要素は無いので解説は割愛します。
<meta charset="utf-8">
<title>clock</title>
<div id="digital_clock"><!-- ここに時刻が入る --></div>
<script src="./clock.js"></script>
// 時計の描画処理をスタート
clock();
function clock () {
// 現在日時を取得
var d = new Date();
// デジタル時計を更新
updateDigitalClock(d);
// 次の「0ミリ秒」に実行されるよう、次の描画処理を予約
var delay = 1000 - new Date().getMilliseconds();
setTimeout(clock, delay);
}
function updateDigitalClock (d) {
var AA_str = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
var YY = d.getFullYear().toString().slice(-2);
var MM = d.getMonth() + 1;
var DD = d.getDate();
var AA = d.getDay();
var hh = d.getHours();
var mm = d.getMinutes();
var ss = d.getSeconds();
// 桁あわせ
if(MM < 10) { MM = "0" + MM; }
if(DD < 10) { DD = "0" + DD; }
if(hh < 10) { hh = "0" + hh; }
if(mm < 10) { mm = "0" + mm; }
if(ss < 10) { ss = "0" + ss; }
var text = YY + '/' + MM + '/' + DD + ' (' + AA_str[AA] + ')<br>' + hh + ':' + mm + ':' + ss
document.getElementById("digital_clock").innerHTML = text;
}
補足: 開発者ツールの利用
Electronには、Google Chromeと同様の開発者ツールが含まれています。
起動中のアプリで、Ctrl + Shift + i
を押すと表示されます。
開発に役立つ様々な機能がありますが、中でも必ず利用するのがコンソールです。
JavaScriptが意図したとおりに動作しない場合、開発者ツールのConsoleタブに何らかのエラーメッセージが出力されていないか確認して下さい。
透過ウィンドウの作成
次にデスクトップウィジェットに欠かせない、「透過ウィンドウ」を作成します。
ウィンドウ枠が無く、背景が透過したウィンドウです。
作成するには、main.js
でウィンドウ作成時のオプションを指定します。
// 一部抜粋
app.on("ready", function() {
mainWindow = new BrowserWindow({
// ウィンドウ作成時のオプション
"width": 180,
"height": 70,
"transparent": true, // ウィンドウの背景を透過
"frame": false, // 枠の無いウィンドウ
"resizable": false // ウィンドウのリサイズを禁止
});
※ 透過ウィンドウでは、ウィンドウのリサイズがサポートされません。
リサイズを行いたい場合、背景透過は行わずに"frame": false
だけを指定して下さい。
参考資料: electron/frameless-window.md at master · atom/electron
透過ウィンドウに必要なCSS
透過ウィンドウを作成する際に、よく利用されるCSSがいくつかあります。
利用しなくてもアプリは動作しますが、実用上の問題があります。
- 背景色/背景画像
- 透過ウィンドウでは「何もない部分」はクリック出来ないため、背景を指定します
- rgba(r, g, b, a)による色指定を行うことで、透明度を持った背景色が指定できます
-
-webkit-app-region: drag;
- 要素をウィンドウのタイトルバーのように扱う指定です
- いずれかの要素にこの指定を行わないと、ドラッグによるウィンドウ移動が出来ません
-
-webkit-app-region: no-drag;
-
drag
を指定した要素内にボタンなど操作可能な要素を配置する場合、no-drag
を指定して上書きします
-
-
-webkit-user-select: none;
- テキストの選択を無効化します
- 主にインタフェース部分に指定します
これらのCSSを指定するため、新たにclock.css
を作成します。
body {
background-color: rgba(24, 24, 24, .7);
color: #fff;
-webkit-app-region: drag;
-webkit-user-select: none;
}
また、index.html
からclock.css
を読み込みます。(2行目を追加)
<meta charset="utf-8">
<link href="./clock.css" rel="stylesheet">
<title>clock</title>
// 以下省略
アプリを起動すると、ウィンドウの枠が無くなり背景が透過されます。
また、右クリックするとウィンドウのメニューが表示され、タイトルバーと同じ扱いであることが分かります。
見た目の調整
CSS
透過したウィンドウに合わせ、CSSで見た目を整えます。
Electronでは表示にChromium(Chrome)を利用しているため、Chromeに合わせた記述を行います。
画像やフォントは相対パスによる指定のほか、URLによる外部の参照も可能です。
@import url(http://fonts.googleapis.com/css?family=Iceland);
body {
overflow: hidden;
margin: 0;
padding: 0;
border: 5px solid rgb(42, 42, 42);
background-color: rgba(24, 24, 24, .7);
box-shadow: 0 0 8px 3px #000 inset;
-webkit-app-region: drag;
-webkit-user-select: none;
}
#digital_clock {
font-family: "Iceland";
font-size: 25px;
line-height: 22px;
margin-top: 9px;
text-align: center;
color: #fff;
text-shadow: 1px 1px 3px #000;
}
※ フォントはGoogle FontsのIcelandを利用しています。
起動時のチラつき対策
Electronでは、アプリ起動からCSSが読み込まれるまでの間に、一瞬だけ白紙のページが表示されてしまいます。
動作上の問題はありませんが、黒配色のアプリではチラついて見えるため、ウィンドウが表示されるタイミングを変更します。
まず、main.js
のウィンドウ作成時のオプションに、"show": false
を追加します。
この指定を行うと、アプリ起動時にウィンドウが表示されなくなります。
// 一部抜粋
app.on("ready", function() {
mainWindow = new BrowserWindow({
// ウィンドウ作成時のオプション
"width": 180,
"height": 70,
"transparent": true, // ウィンドウの背景を透過
"frame": false, // 枠の無いウィンドウ
"resizable": false, // ウィンドウのリサイズを禁止
"show": false // アプリ起動時にウィンドウを表示しない
});
次に、clock.js
の先頭にウィンドウを表示する記述を追加します。
現在のウィンドウはrequire("remote").getCurrentWindow()
でオブジェクトとして取得でき、様々な操作が可能です。
参考資料: electron/browser-window.md at master · atom/electron
// ウィンドウのオブジェクトを取得
var win = require("remote").getCurrentWindow();
// ウィンドウを表示
win.show();
// 時計の描画処理をスタート
clock();
function clock () {
// 現在日時を取得
var d = new Date();
// 以下省略
※ ウィンドウの表示処理よりも前にエラーが発生した場合、アプリ起動後に何も表示されなくなります。
ウィンドウ位置の記憶
ウィンドウが表示される位置は、指定なき場合デスクトップの中央になります。
ウィジェットが中央に表示される意味はあまり無いので、前回終了時の位置を記憶し、起動時に復元する処理を追加します。
clock.js
の先頭を以下のように変更します。
// ウィンドウを開く
openWindow();
// 時計の描画処理をスタート
clock();
function openWindow () {
// ウィンドウのオブジェクトを取得
var win = require("remote").getCurrentWindow();
// ウィンドウ位置を復元
if (localStorage.getItem("windowPosition")) {
var pos = JSON.parse(localStorage.getItem("windowPosition"));
win.setPosition(pos[0], pos[1]);
}
// クローズ時にウィンドウ位置を保存
win.on("close", function() {
localStorage.setItem("windowPosition", JSON.stringify(win.getPosition()));
});
// ウィンドウを表示
win.show();
}
function clock () {
// 現在日時を取得
var d = new Date();
// 以下省略
ウィンドウからgetPosition()
で位置が[x, y]
として得られ、setPosition(x, y)
で指定した位置に移動できます。
参考資料: electron/browser-window.md at master · atom/electron
ウィンドウ位置のような設定情報を保存したい場合、ファイルへ書き出す方法もありますが、今回はlocalStorageを利用しています。
localStorageはChromeなどの主要ブラウザに実装されている機能です。
参考資料: Web Storage-HTML5のAPI、および、関連仕様
localStorageには文字列のみが格納できるため、JSON.stringifyを利用しJSONに変換した後格納しています。
タスクトレイに格納
ウィジェットのような常駐アプリは、タスクトレイに格納すると邪魔になりません。
タスクトレイに格納するためには、アイコンを用意する必要があります。
今回は、こちらの画像を利用しました:
app
フォルダにicon.png
として配置します。
そして、main.js
に以下の// タスクトレイに格納
部分を追加します。
// 一部抜粋
app.on("ready", function() {
mainWindow = new BrowserWindow({
// ウィンドウ作成時のオプション
"width": 250,
"height": 70,
"transparent": true, // ウィンドウの背景を透過
"frame": false, // 枠の無いウィンドウ
"resizable": false, // ウィンドウのリサイズを禁止
"show": false, // アプリ起動時にウィンドウを表示しない
"skip-taskbar": true // タスクバーに表示しない
});
// index.html を開く
mainWindow.loadUrl("file://" + __dirname + "/index.html");
mainWindow.on("closed", function() {
mainWindow = null;
});
// タスクトレイに格納
var Menu = require("menu");
var Tray = require("tray");
var nativeImage = require("native-image");
var trayIcon = new Tray(nativeImage.createFromPath(__dirname + "/icon.png"));
// タスクトレイに右クリックメニューを追加
var contextMenu = Menu.buildFromTemplate([
{ label: "表示", click: function () { mainWindow.focus(); } },
{ label: "終了", click: function () { mainWindow.close(); } }
]);
trayIcon.setContextMenu(contextMenu);
// タスクトレイのツールチップをアプリ名に
trayIcon.setToolTip(app.getName());
// タスクトレイが左クリックされた場合、アプリのウィンドウをアクティブに
trayIcon.on("clicked", function () {
mainWindow.focus();
});
// タスクトレイに格納 ここまで
});
併せて、// ウィンドウ作成時のオプション
で"skip-taskbar": true
を指定すると、ウィンドウがタスクバーに表示されなくなります。
参考資料:
electron/tray.md at master · atom/electron
electron/menu-item.md at master · atom/electron
配布用パッケージの作成
最後に、配布のためのパッケージ化を行います。
作成したアプリにElectron本体を組み込み、単体で実行可能にします。
パッケージ化はelectron-packagerを利用すると簡単です。
electron-packagerの導入
Node.jsを導入していない場合は、以下からインストールして下さい。
ダウンロード先: Download | Node.js
コマンドプロンプトから、npm install -g electron-packager
を実行します。
パッケージの作成
electron-packagerは、コマンドプロンプトから以下のように利用します。
> electron-packager [appフォルダ] [アプリ名] --platform=[対象OS] --arch=[対象環境] --version=[Electronのバージョン] --asar
※ --asar
はappフォルダをasarでアーカイブ化するオプションで、省略可能です。
(例) Windows 64bit向けのアプリケーションを作成する場合
> electron-packager .\app tutorial_clock --platform=win32 --arch=x64 --version=0.35.1 --asar
この例の場合、成功すると、tutorial_clock-win32-x64
というフォルダが作成されます。
フォルダ内のtutorial_clock.exe
を起動すると、作成したアプリが起動します。
作成したアプリを配布する際は、このtutorial_clock-win32-x64
をzipなどで圧縮すると良いでしょう。
補足: アイコンについて
上記の例では、tutorial_clock.exe
のアイコンはElectronのアイコンになりますが、--icon=[アイコンファイル]
を付け加えることで、任意のアイコンを指定できます。
> electron-packager .\app tutorial_clock --platform=win32 --arch=x64 --version=0.35.1 --asar --icon=icon.ico
Windows向けアプリでは画像ファイルをico形式に変換する必要があります。
いくつかの方法がありますが、以下の様なサービスを利用すると手軽です。
様々なファビコンを一括生成。favicon generator
完成
お疲れ様でした。以上でチュートリアルは終了です。
今回は単なる時計でしたが、中身の処理が複雑になっても基本的な流れは同じです。
インターネット上の様々なAPIを利用すれば、すぐに実用的なウィジェットが作成できるのではないでしょうか。
付録
アナログ時計
チュートリアル上は複雑になるため省略しましたが、この時計アプリにはアナログ時計を追加したバージョンがあります。
画像素材
60x60のPNG画像として、背景と針を準備しました。
実装
背景と針の画像をposition: absolute;
で重ねて表示し、
CSS3のtransform: rotate()
をJavaScriptから操作してそれぞれの針を回転させます。
※ 以下、要点のみを抜粋しています。
<div class="analog">
<img class="pin" id="pin_bg" src="./img/clock_bg.png">
<img class="pin" id="pin_hh" src="./img/clock_hh.png">
<img class="pin" id="pin_mm" src="./img/clock_mm.png">
<img class="pin" id="pin_ss" src="./img/clock_ss.png">
</div>
.analog {
position: absolute;
left: 5;
float: left;
width: 60px;
}
.analog .pin {
position: absolute;
}
function updateAnalogClock (d) {
// 秒針
var ss_deg = d.getSeconds() * 6;
document.getElementById("pin_ss").style.transform = "rotate(" + ss_deg + "deg)";
// 分針
var mm_deg = d.getMinutes() * 6 + d.getSeconds() / 10;
document.getElementById("pin_mm").style.transform = "rotate(" + mm_deg + "deg)";
// 時針
var hh_deg = d.getHours() * 30 + d.getMinutes() / 2;
document.getElementById("pin_hh").style.transform = "rotate(" + hh_deg + "deg)";
}
ソースコードの全文はGitHubにて公開していますので、ご自由にお使い下さい。
tutorial_clock/app_option at master · SallyAcolyte/tutorial_clock
その他のポイント
URLをブラウザで開く
アプリ内にリンクを作成すると、ブラウザではなくElectron内部で開いてしまいます。
ブラウザで開くには以下のようにrequire("shell").openExternal(url)
を利用します。
var shell = require("shell");
shell.openExternal("https://www.google.com/");
jQueryを利用する
Electronでは通常のJSライブラリを利用できますが、jQueryを利用する場合は少々対策が必要になります。
参考資料: ElectronでjQueryがundefinedになる - Qiita
ご質問・ご指摘等は、コメント欄か@SallyAcolyteまでお気軽にお寄せ下さい。