2025年2月27日木曜日

自作で通知機能付きタイマーを作った

 JavaScriptで今度は「通知機能付きのタイマー」を作りました。

https://notification.atukan.com

 

前回と同じく一応ソースコードも残しておきますね

JavaScript

__________________

class HourlyNotifier {
    constructor(soundUrl = 'https://www.soundjay.com/buttons/beep-01a.mp3') {
        this.soundUrl = soundUrl;
        this.audio = new Audio(this.soundUrl);
        this.interval = null;
        this.countdownInterval = null;
        this.timeout = null;
        this.notificationIntervalMinutes = 60;
        this.statusElement = document.getElementById('status');
        this.countdownElement = document.getElementById('countdown');
        this.intervalInput = document.getElementById('intervalInput');
        this.requestNotificationPermission();
        this.setupKeyboardShortcuts();
    }

    async requestNotificationPermission() {
        if ("Notification" in window) {
            const permission = await Notification.requestPermission();
            if (permission !== "granted") {
                alert("通知が許可されていません。ブラウザの設定を確認してください。");
            }
        }
    }

    notify() {
        this.audio.play().catch(err => console.log("音声再生エラー:", err));
        if ("Notification" in window && Notification.permission === "granted") {
            new Notification("時間のお知らせ", {
                body: `現在の時刻: ${new Date().toLocaleTimeString('ja-JP')}`,
                icon: "https://via.placeholder.com/32"
            });
        }
        alert(`時間のお知らせ\n現在の時刻: ${new Date().toLocaleTimeString('ja-JP')}`);
    }

    updateStatus(message) {
        if (this.statusElement) {
            this.statusElement.textContent = message;
        }
    }

    updateCountdown() {
        if (!this.nextNotificationTime) return;
        const now = new Date();
        const timeLeftMs = this.nextNotificationTime - now;
        if (timeLeftMs <= 0) return;

        const minutesLeft = Math.floor(timeLeftMs / (1000 * 60));
        const secondsLeft = Math.floor((timeLeftMs % (1000 * 60)) / 1000);
        if (this.countdownElement) {
            this.countdownElement.textContent = `次の通知まで: ${minutesLeft}分 ${secondsLeft}秒`;
        }
    }

    setInterval(minutes) {
        const parsedMinutes = parseInt(minutes, 10);
        if (isNaN(parsedMinutes) || parsedMinutes <= 0) {
            alert("有効な分数を入力してください(1以上の整数)");
            return false;
        }
        this.notificationIntervalMinutes = parsedMinutes;
        return true;
    }

    start() {
        if (this.interval) return;

        const inputValue = this.intervalInput ? this.intervalInput.value : this.notificationIntervalMinutes;
        if (!this.setInterval(inputValue)) return;

        const intervalMs = this.notificationIntervalMinutes * 60 * 1000;

        this.nextNotificationTime = new Date(Date.now() + intervalMs);
        this.updateStatus(`通知: 開始 (${this.notificationIntervalMinutes}分間隔)`);

        this.timeout = setTimeout(() => {
            this.notify();
            this.nextNotificationTime = new Date(Date.now() + intervalMs);
            this.interval = setInterval(() => {
                this.notify();
                this.nextNotificationTime = new Date(Date.now() + intervalMs);
            }, intervalMs);
        }, intervalMs);

        this.updateCountdown();
        this.countdownInterval = setInterval(() => this.updateCountdown(), 1000);
    }

    stop() {
        if (this.timeout) {
            clearTimeout(this.timeout);
            this.timeout = null;
        }
        if (this.interval) {
            clearInterval(this.interval);
            this.interval = null;
        }
        if (this.countdownInterval) {
            clearInterval(this.countdownInterval);
            this.countdownInterval = null;
            this.nextNotificationTime = null;
            if (this.countdownElement) {
                this.countdownElement.textContent = "次の通知まで: -";
            }
        }
        this.updateStatus("通知: 停止");
    }

    setupKeyboardShortcuts() {
        document.addEventListener('keydown', (event) => {
            // Ctrl + N で開始
            if (event.ctrlKey && event.key === 'n') {
                event.preventDefault();
                this.start();
            }
            // Ctrl + Q で停止
            if (event.ctrlKey && event.key === 'q') {
                event.preventDefault();
                this.stop();
            }
        });
    }
}

const notifier = new HourlyNotifier();

______________________

 

HTML

______________________

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>カスタム通知タイマー</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            text-align: center;
            margin-top: 50px;
        }
        button, input {
            padding: 10px 20px;
            margin: 5px;
        }
        #status, #countdown {
            margin: 10px;
            font-size: 1.2em;
        }
        #shortcuts {
            margin: 10px;
            color: #555;
        }
    </style>
</head>
<body>
    <div id="status">通知: 停止</div>
    <div id="countdown">次の通知まで: -</div>
    <input type="number" id="intervalInput" placeholder="通知間隔(分)" min="1" value="60">
    <button onclick="notifier.start()">通知開始</button>
    <button onclick="notifier.stop()">通知停止</button>
    <div id="shortcuts">ショートカット: [Ctrl + N] で開始, [Ctrl + Q] で停止</div>
    <script src="your-script.js"></script>
</body>
</html>
 
__________________________________ 
 HTMLとは別に外部からJavaScriptを読み込ませる形で組んでいます。
 
 

 

0 件のコメント:

コメントを投稿

雑記。近況報告や今後の目標など

【網膜裂孔】 結膜炎になって数カ月後、眼科で検査を受けた際に網膜裂孔が発覚した。今は完全に完治しているが、検査の重要性を改めて実感した。痛みや明確な症状も特になかった。私の場合、目は命そのものだと感じるほど大切なので、以前よりも目の健康について真剣に考えるようになった。30歳...