Edited at

Electronでデスクトップウィジェットを作るまで

More than 3 years have passed since last update.


はじめに

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で指定されたファイルが、アプリの実行時に読み込まれます。


package.json

{

"name" : "tutorial_clock_digital",
"version" : "0.0.1",
"main" : "main.js"
}


main.js

次にmainで指定したmain.jsを作成します。

ウィンドウ周りの処理などを実装しますが、まずはそのまま貼り付ければOKです。

後ほど「// ウィンドウ作成時のオプション」部分に追記します。


main.js

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でアプリのインタフェースを作成します。


index.html

<h1>Hello World</h1>



実行

* ここまででのファイル構成

app
├index.html
├main.js
└package.json

これで、必要なファイルは揃いました。

先ほど用意したelectron.exeに、作成したappフォルダを「フォルダごと」ドロップするとアプリが起動します。

アプリの起動(ドロップ)

または、コマンドプロンプト等から引数として渡してもOKです。

アプリの起動(コマンドプロンプト)

アプリのウィンドウが開き、index.htmlが表示されます。

Hello World


時計の作成

ここから、実際に動作させる時計を作成します。

HTML/CSS/JSで、普通のブラウザで動くように作成すればOKです。

※ Electron特有の要素は無いので解説は割愛します。


index.html

<meta charset="utf-8">

<title>clock</title>

<div id="digital_clock"><!-- ここに時刻が入る --></div>

<script src="./clock.js"></script>



clock.js

// 時計の描画処理をスタート

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タブに何らかのエラーメッセージが出力されていないか確認して下さい。

DevTools_console


透過ウィンドウの作成

次にデスクトップウィジェットに欠かせない、「透過ウィンドウ」を作成します。

ウィンドウ枠が無く、背景が透過したウィンドウです。

作成するには、main.jsでウィンドウ作成時のオプションを指定します。


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を作成します。


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行目を追加)


index.html

<meta charset="utf-8">

<link href="./clock.css" rel="stylesheet">
<title>clock</title>

// 以下省略


アプリを起動すると、ウィンドウの枠が無くなり背景が透過されます。

また、右クリックするとウィンドウのメニューが表示され、タイトルバーと同じ扱いであることが分かります。

透過ウィンドウ


見た目の調整


CSS

透過したウィンドウに合わせ、CSSで見た目を整えます。

Electronでは表示にChromium(Chrome)を利用しているため、Chromeに合わせた記述を行います。

画像やフォントは相対パスによる指定のほか、URLによる外部の参照も可能です。


clock.css

@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を追加します。

この指定を行うと、アプリ起動時にウィンドウが表示されなくなります。


main.js

// 一部抜粋

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


clock.js

// ウィンドウのオブジェクトを取得

var win = require("remote").getCurrentWindow();

// ウィンドウを表示
win.show();

// 時計の描画処理をスタート
clock();

function clock () {
// 現在日時を取得
var d = new Date();
// 以下省略


※ ウィンドウの表示処理よりも前にエラーが発生した場合、アプリ起動後に何も表示されなくなります。


ウィンドウ位置の記憶

ウィンドウが表示される位置は、指定なき場合デスクトップの中央になります。

ウィジェットが中央に表示される意味はあまり無いので、前回終了時の位置を記憶し、起動時に復元する処理を追加します。

clock.jsの先頭を以下のように変更します。


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に変換した後格納しています。


タスクトレイに格納

ウィジェットのような常駐アプリは、タスクトレイに格納すると邪魔になりません。

タスクトレイに格納

タスクトレイに格納するためには、アイコンを用意する必要があります。

今回は、こちらの画像を利用しました: icon.png

appフォルダにicon.pngとして配置します。

そして、main.jsに以下の// タスクトレイに格納部分を追加します。


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は、コマンドプロンプトから以下のように利用します。

> 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画像として、背景と針を準備しました。

clock_bg.pngclock_hh.pngclock_mm.pngclock_ss.png


実装

背景と針の画像をposition: absolute;で重ねて表示し、

CSS3のtransform: rotate()をJavaScriptから操作してそれぞれの針を回転させます。

※ 以下、要点のみを抜粋しています。


index.html

    <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>


clock.css

.analog {

position: absolute;
left: 5;
float: left;
width: 60px;
}

.analog .pin {
position: absolute;
}



clock.js

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までお気軽にお寄せ下さい。