Electronで簡単な時計アプリを作った

作成:2021.08.27

更新:2021.08.27

Electron

Electronとは何か?

HTML、CSS、JavaScriptを使って、PC用アプリを作ることができるフレームワーク。

成果

https://github.com/yazte1008/electron-clock

以下、詳細な流れをメモ。

Electronのインストール

任意のプロジェクトディレクトリに、ローカルインストールする。

ターミナル
npm i -D electron

起動してみる。

ターミナル
npx electron

Electronのデフォルトウィンドウが開けばOK。
ここでは特に使わないので閉じる。

開発環境の準備

ディレクトリ構成

以下のような構成にするので、不足しているディレクトリ、ファイルを追加する。
ファイルの中身はこの時点では空でOK。

プロジェクトディレクトリ
.
├── main.js
├── package-lock.json
├── package.json
└── src
    ├── css
    │   └── style.css
    ├── index.html
    └── scss
        ├── component
        │   ├── _window.scss
        │   └── _clock.scss
        ├── foundation
        │   ├── _base.scss
        │   └── _reset.scss
        └── style.scss

必要なモジュールの追加

Sass

ターミナル
npm i -D autoprefixer node-sass node-sass-globbing postcss-cli

ホットリロード

ターミナル
npm i -D electron-reload

ビルド

ターミナル
npm i -D electron-builder

package.jsonの調整

ビルド時に必要になる項目(name、version、description、author)を追加。
エントリーポイント(main)の指定を追加。
SassのコンパイルやElectronの実行用のエイリアス(scripts)を追加。

package.json
{
  "name": "electron-clock",
  "version": "1.0.0",
  "description": "This is a clock made of Electron.",
  "author": "yazte1008",
  "main": "main.js",
  "scripts": {
    "sass": "node-sass --importer node_modules/node-sass-globbing/index.js src/scss/ --output src/css/ --output-style expanded --sourcemap=none",
    "sass-watch": "node-sass --importer node_modules/node-sass-globbing/index.js src/scss/ --output src/css/ --output-style expanded --sourcemap=none --watch",
    "autoprefixer": "postcss --use autoprefixer --no-map src/css/*.css -d src/css/",
    "dev": "npm run sass-watch & npm run sass & npm run autoprefixer & electron ."
  },
  "browserslist": [
    "last 2 versions"
  ],
  "devDependencies": {
    "autoprefixer": "^10.3.3",
    "electron": "^13.2.2",
    "electron-builder": "^22.11.7",
    "electron-reload": "^2.0.0-alpha.1",
    "node-sass": "^6.0.1",
    "node-sass-globbing": "^0.0.23",
    "postcss-cli": "^8.3.1"
  }
}

エントリーポイントの設定

メニューとかも設定できて面白い。
ここでは最小限のアプリ名メニューのほかに、リロードと開発者ツールを入れる。

main.js
// モジュールの読み込み
const { app, Menu, BrowserWindow } = require('electron')

// ホットリロードの設定
if (!app.isPackaged) {
  const electronReload = require('electron-reload')

  electronReload(__dirname, {
    electron: require(`${__dirname}/node_modules/electron`),
  })
}

// Macの判定
const isMac = process.platform === 'darwin'

// ウィンドウを生成する
function createWindow() {
    // インスタンスを生成
    const mainWindow = new BrowserWindow({
      width: 800,
      height: 600,
    })

    // HTMLをロード
    mainWindow.loadFile('src/index.html')

    // ▼ メニュー ------------------------------

    // メニューの内容
    const template = Menu.buildFromTemplate([...
      // Mac用のアプリ名メニュー
      (isMac
        ? [
            {
              label: app.name,
              submenu: [
                {
                  role: 'about',
                  label: app.name + 'について',
                },
                {
                  type: 'separator' // 区切り線も入れられる
                },
                {
                  role: 'quit',
                  label: '終了する',
                }
              ]
            }
          ]
        : []
      ),

      // 表示
      {
        label: '表示',
        submenu: [
          {
            role:'reload',
            label:'再読み込み'
          },
          {
            type: 'separator'
          },
          {
            role: 'toggleDevTools',
            label: 'デベロッパー ツール',
          },
        ]
      },
    ])

    // メニューを追加
    Menu.setApplicationMenu(template)

    // ▲ メニュー ------------------------------

}

/*------------------------------
  ** イベントライフサイクル
------------------------------*/

// 初期化時
app.whenReady().then(() => {
  createWindow()

  // 起動時
  app.on('activate', () => {
    if (BrowserWindow.getAllWindows().length === 0) {
      createWindow()
    }
  })
})

// 終了時
app.on('window-all-closed', () => {
  if (isMac === false) {
    app.quit()
  }
})

アプリの作成

HTML

src/index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>時計</title>
  <link rel="preconnect" href="https://fonts.gstatic.com">
  <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Oswald:700&display=swap">
  <link rel="stylesheet" href="./css/style.css">
</head>
<body>
  <div class="window">
    <div class="clock" id="clock"></div>
  </div>
  <script src="./js/script.js"></script>
</body>
</html>

Sass

メイン

src/scss/style.scss
/* ==============================
  ** Foundation
==============================*/

@import 'foundation/reset';
@import 'foundation/base';

/* ==============================
  ** Component
==============================*/

@import 'component/**/*'

リセット

src/scss/foundation/_reset.scss
/* A Modern CSS Reset */
// https://github.com/hankchizljaw/modern-css-reset/blob/master/dist/reset.min.css
*,*::before,*::after{box-sizing:border-box}
ul[class],ol[class]{padding:0}
body,h1,h2,h3,h4,p,ul[class],ol[class],figure,blockquote,dl,dd{margin:0}
body{min-height:100vh;scroll-behavior:smooth;text-rendering:optimizeSpeed;line-height:1.5}
ul[class],ol[class]{list-style:none}
a:not([class]){text-decoration-skip-ink:auto}
img{max-width:100%;display:block}
article>*+*{margin-top:1em}
input,button,textarea,select{font:inherit}
img:not([alt]){filter:blur(10px)}
@media(prefers-reduced-motion:reduce){
  *{animation-duration:.01ms !important;animation-iteration-count:1 !important;transition-duration:.01ms !important;scroll-behavior:auto !important}
}

ベース

src/scss/foundation/_base.scss
html {
  overflow-y: scroll;
  font-size: 62.5%;
}

body {
  font-family: "Helvetica Neue", Arial, "Hiragino Kaku Gothic ProN", "Hiragino Sans", Meiryo, sans-serif;
  font-size: 1.6rem;
  color: #003966;
  text-size-adjust: none;
  line-height: 1;
  background-color: #e6f2f7;
}

コンポーネント

src/scss/component/_wrapper.scss
.window {
  display: flex;
  justify-content: center;
  align-items: center;
  width: 100vw;
  height: 100vh;
}
src/scss/component/_clock.scss
.clock {
  display: flex;
  justify-content: center;
  font-family: 'Oswald', sans-serif;
  font-size: 3.0rem;
  color: #013966;

  &__item {
    width: 20px;
    text-align: center;
  }
}

JavaScript

src/js/script.js
window.addEventListener('DOMContentLoaded', () => {
  // DOMを取得する
  const clock = document.getElementById('clock');

  // 時間を更新する
  const reloadTime = () => {
    // 時間を取得する
    const current_date = new Date();
    let current_hour = current_date.getHours();
    let current_minute = current_date.getMinutes();
    let current_second = current_date.getSeconds();

    // 2桁に整形
    current_hour = (current_hour.toString().length !== 2)
      ? '0' + current_hour
      : current_hour;
    current_minute = (current_minute.toString().length !== 2)
      ? '0' + current_minute
      : current_minute;
    current_second = (current_second.toString().length !== 2)
      ? '0' + current_second
      : current_second;

    // 出力する内容
    const clock_value = `${current_hour}:${current_minute}:${current_second}`;

    // レイアウトのため1文字ずつ子要素でラップしてコードを生成
    let clock_html = '';
    [...clock_value].forEach((value) => {
      clock_html += `<div class="clock__item">${value}</div>`;
    });

    // 出力
    clock.innerHTML = clock_html

    // 1秒ごとに再出力する
    setTimeout(() => {
      reloadTime();
    }, 1000);
  }

  // 実行
  reloadTime();
});

アプリの実行

ターミナル
npm run dev

アプリのビルド

OSごとにオプションが分かれている。
ここではたとえばMac用にビルド。

ターミナル
npx electron-builder build --mac

dis/mac-arm64/electron-clock.appが生成される。