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

  • 688
    いいね
  • 1
    コメント
この記事は最終更新日から1年以上が経過しています。

はじめに

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