39
16

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

JavaScriptで15分毎にデスクトップ通知するアラームを作る(通知APIとクロージャ)

Last updated at Posted at 2019-12-06

やりたいこと

個人での長時間のPC作業中に、振り返ったり頭を切り替えたりできるようにするため、15分くらいの間隔でお知らせしてくれるアラームがほしい。

方法の検討

  • スマホのアプリだとPC作業に気づかなかったり、逆に集中が切れてしまったりするので、PCでこっそり動くのがいい
  • でも作業用PCに余計なアプリは入れたくない

自分でJavaScriptで作って、ブラウザで開いておく方法を試してみる。

通知APIを動かす

JavaScriptを使うにしても、余計なライブラリはなるべく使わずに済ませたい。
幸い標準で通知APIなるものが用意されてるので、これを使う。
まずは単に通知を表示するスクリプトを作ってみる。

notification.js
window.onload = function() {
   Notification.requestPermission();
   const notification = new Notification("Check!");
};

↑を読み込む適当なHTMLを作って、ブラウザで開く。

alarm15.html
<html>
<head>
   <script src="notification.js"></script>
</head>
<body>
</body>
</html>

HTMLを表示すると、よくある感じの通知の許可要求ダイアログが表示される。
(このへんWebに公開する場合はいろいろ考えなきゃいかんけど、ローカルなのであまり気にせず)
image.png
許可すると、画面右上によくある感じの通知が表示される。
image.png
これを15分ごとに表示させるようにしたい。

実装その1 シンプルに

まずは素直に実装してみる。

alarm15-simple.js
window.onload = function() {
   Notification.requestPermission();
   setInterval(checkTime, 1000);   
};
 
let previousMinutes;

const checkTime = function() {
    const currentTime = new Date();
    const minutes = currentTime.getMinutes();
    if (previousMinutes !== minutes && minutes % 15 === 0) {
        previousMinutes = minutes;
        const notification = new Notification("Check!");
    }
};

setInterval(checkTime, 1000) と書くことで、1秒間隔で関数 checkTime を実行する。
関数 checkTime では現在時刻をチェックして、0/15/30/45分だったら一度だけデスクトップ通知をする。
(通知の間隔を変えたければ minutes % 15 の数値を変えればよい。最初は1分間隔で試すとよい)

これでやりたいことは実現できたけど…
このコードだと、直前の「分」を保持している変数 previousMinutes をグローバルスコープに置いてしまっている。
これを安全なスコープに閉じ込めてあげたい。

実装その2 クロージャ

このようにしてスコープを閉じられた。見た目の動作は同じ。

alarm15-closure.js
window.onload = function() {
   Notification.requestPermission();
   setInterval(checkTime, 1000);   
};
 
const checkTime = function() {
   let previousMinutes;
   return function() {
       const currentTime = new Date();
       const minutes = currentTime.getMinutes();
       if (previousMinutes !== minutes && minutes % 15 === 0) {
           previousMinutes = minutes;
           const notification = new Notification("Check!");
       }
   }  
}();

いい感じのクロージャのサンプルになったので、ざっくり説明。

定数 checkTime には、無名関数を即時実行した結果が代入される。
無名関数を即時実行して返ってくる結果は return function() ... と記述のある通り、これまた無名関数。
この無名関数のコードは実装その1の checkTime 関数とまったく同じ。
結果として、実装その1とその2はまったく同じ関数 checkTime を実行するように見えるけど…。

実装その1とその2で、関数 checkTime の違いは、関数のスコープ。
実装その1は、最も外側に関数が定義されているので、関数 checkTime のスコープはグローバルスコープ。
実装その2は、外側の無名関数内に関数が定義されているので、関数 checkTime のスコープは外側の無名関数が作るローカルスコープ。
(このように、実行時でなく定義時に決まるスコープをレキシカルスコープと言う)

で、実装その2は同じローカルスコープに変数 previousMinutes が定義されている。
同じスコープなので関数 checkTime からアクセスできるけど、他のスコープにある関数からはアクセスできない。
これで、変数 previousMinutes は必要な関数からのみアクセスされる、安全なスコープに閉じ込めることができた。

おまけ設定

通知が味気ないので、ちょっとだけオプションを設定してみる。

alarm15-closure.js
window.onload = function() {
   Notification.requestPermission();
   setInterval(checkTime, 1000);   
};
 
const checkTime = function() {
   let previousMinutes;
   const options = {
       body: "調子はどうだい?",
       icon: "img/youkai_kappa.png"
   };
   return function() {
       const currentTime = new Date();
       const minutes = currentTime.getMinutes();
       if (previousMinutes !== minutes && minutes % 15 === 0) {
           previousMinutes = minutes;
           const notification = new Notification("Check!", options);
       }
   }  
}();

こんなかんじになる。
(画像はお好みで)
image.png

参考資料

Notifications API を使用する - WebAPI | MDN
WindowOrWorkerGlobalScope.setInterval() - Web API | MDN
開眼! JavaScript 第7章「スコープとクロージャ」
JavaScript の原理:クロージャの真実

おわりに

この記事は Mikatus Advent Calendar 2019 7日目として公開しました。
明日は我らがリーダー @takuya0301 さんです!
参考資料の「クロージャの真実」もぜひご覧ください(本記事の説明はざっくりなので)。

39
16
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
39
16

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?