Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

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

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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away