初めに
JavaScriptには、「何ミリ秒後に実行する」という処理を行うことができるsetTimeoutというメソッドが標準で搭載されてあります。
また、clearTimeoutというものも存在し、これは既に開始されたsetTimeoutを中断することができます。
しかし、その次にまた同じものを開始させた場合、カウントは途中からではなくまた始めからになってしまいます。
もちろんそのような場合の方が便利なときもありますが、途中から再開できるものも欲しいときがあったので、
少し改良して一時停止機能を搭載した自作のsetTimeoutを作りました。
コード
実際に作ってみたものがこちらになります。
// 時間を管理するオブジェクトの宣言
let TimeLines = {
ID: [],
StopTimeList: [],
isPauseList: []
};
let isPause = false;
// 自作のsetTimeout
function mySetTimeout(Function, Time) {
let StartTime = Date.now();
let TimerID = setTimeout(() => {
Function();
// setTimeoutが終了したらその情報を削除
ClearTimeoutData(TimerID);
}, Time);
TimeLines.ID.push(TimerID);
// [開始時間, 待機時間, 待機後に実行する関数]
TimeLines.StopTimeList.push([StartTime, Time, Function]);
TimeLines.isPauseList.push(isPause);
}
// 一時停止機能
function PauseTimeout() {
isPause = true;
// setTimeoutの一時停止
TimeLines.ID.forEach((ID, index) => {
// 既に一時停止している場合は終了
if (TimeLines.isPauseList[index]) return;
TimeLines.isPauseList[index] = true;
let StopTime = Date.now();
clearTimeout(ID);
// それぞれの待機時間から、開始してから一時停止するまでの時間を引く(開始してからの経過した時間を取得)
TimeLines.StopTimeList[index][1] -= (StopTime - TimeLines.StopTimeList[index][0]);
});
console.log("Pause");
}
// カウント再開
function ResumeTimeout() {
isPause = false;
//setTimeoutの再開
TimeLines.ID.forEach((ID, index) => {
// まだ一時停止していない場合は終了
if (!TimeLines.isPauseList[index]) return;
TimeLines.isPauseList[index] = false;
let ResumeTime = Date.now();
let TimerID = setTimeout(() => {
TimeLines.StopTimeList[TimeLines.ID.indexOf(TimerID)][2]();
// setTimeoutが終了したらその情報を削除
ClearTimeoutData(TimerID);
}, TimeLines.StopTimeList[index][1]);
TimeLines.ID[index] = TimerID;
TimeLines.StopTimeList[index][0] = ResumeTime;
});
console.log("Resume");
}
// setTimeoutの情報を削除する関数
function ClearTimeoutData(TimerID) {
let index = TimeLines.ID.indexOf(TimerID);
if (index != -1) {
TimeLines.ID.splice(index, 1);
TimeLines.StopTimeList.splice(index, 1);
TimeLines.isPauseList.splice(index, 1);
}
}
実用例
簡単なHTMLを作成し、試しに一時停止機能ができるカウントダウンを作ってみました。
HTML
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CountdownTimer</title>
</head>
<body>
<button id="start">スタート</button>
<button id="pause">ストップ</button>
<button id="resume">再開</button>
<div id="time"></div>
<!--今回のjsファイル-->
<script src="Timer.js"></script>
</body>
</html>
JavaScript
/**
先程記述したコードをここに書く
// 時間を管理するオブジェクトの宣言
let TimeLines = {
ID: [],
StopTimeList: []
};
・
・
・
}
*/
// 今回は10秒に設定
const TIMER = 10000;
const StartButton = document.body.querySelector("#start");
const PauseButton = document.body.querySelector("#pause");
const ResumeButton = document.body.querySelector("#resume");
function TimerFunction() {
// ここにタイマーが終了したときに実行する処理を書く
console.log("関数実行");
}
let PauseTimeList = [];
// タイマー表示の更新を10msごとに行う
setInterval(() => {
if (isPause) return;
const TimerList = document.body.querySelectorAll(".timetext");
TimerList.forEach((el, index) => {
const time = Math.max(PauseTimeList[index][0] + PauseTimeList[index][1] - Date.now(), 0);
const sec = Math.floor(time / 1000);
const ms = Math.floor((time % 1000) / 10);
el.innerHTML = `${sec.toString().padStart(2,"0")}:${ms.toString().padStart(2,"0")}`;
});
}, 10);
// スタートボタン
StartButton.addEventListener("click", () => {
// 一時停止中はタイマーを開始させないようにする
if (isPause) {
return console.warn("一時停止中のためタイマーを追加できません。");
}
// setTimeoutと同じ書き方でOK
mySetTimeout(TimerFunction, TIMER);
document.body.querySelector("#time").insertAdjacentHTML("afterbegin", '<p class="timetext">10:00</p>');
PauseTimeList.push([TIMER, Date.now()]);
});
// ストップボタン
PauseButton.addEventListener("click", () => {
if (isPause) return;
const TimerList = document.body.querySelectorAll(".timetext");
TimerList.forEach((el, index) => {
PauseTimeList[index][0] -= Date.now() - PauseTimeList[index][1];
});
// mySetTimeoutを一時停止させたいときにこの関数を記述する
PauseTimeout();
});
// 再開ボタン
ResumeButton.addEventListener("click", () => {
if (!isPause) return;
const TimerList = document.body.querySelectorAll(".timetext");
TimerList.forEach((el, index) => {
PauseTimeList[index][1] = Date.now();
});
// mySetTimeoutを再開させたいときにこの関数を記述する
ResumeTimeout();
});
実行結果
このHTMLを開くとこのようになっております。
(Edgeで実行したものです)
しっかりと動作してくれているのが分かります。
解説
やっていることは、PauseSetTmeoutが実行されたタイミングでclearTimeoutで一度中断し、ResumeTimeoutで開始してから中断するまでの時間の差を引いた時間、
つまり残り時間から再びsetTimeoutをするという感じです。
function PauseTimeout() {
isPause = true;
// setTimeoutの一時停止
TimeLines.ID.forEach((ID, index) => {
// 既に一時停止している場合は終了
if (TimeLines.isPauseList[index]) return;
TimeLines.isPauseList[index] = true;
let StopTime = Date.now();
// ----------ここでclearTimeoutをしている----------
clearTimeout(ID);
// それぞれの待機時間から、開始してから一時停止するまでの時間を引く(開始してからの経過した時間を取得)
TimeLines.StopTimeList[index][1] -= (StopTime - TimeLines.StopTimeList[index][0]);
});
console.log("Pause");
}
function ResumeTimeout() {
isPause = false;
//setTimeoutの再開
TimeLines.ID.forEach((ID, index) => {
// まだ一時停止していない場合は終了
if (!TimeLines.isPauseList[index]) return;
TimeLines.isPauseList[index] = false;
let ResumeTime = Date.now();
// ----------ここで新たにsetTimeoutをしている----------
let TimerID = setTimeout(() => {
// 実行する関数
TimeLines.StopTimeList[TimeLines.ID.indexOf(TimerID)][2]();
// setTimeoutが終了したらその情報を削除
ClearTimeoutData(TimerID);
}, TimeLines.StopTimeList[index][1]);
TimeLines.ID[index] = TimerID;
TimeLines.StopTimeList[index][0] = ResumeTime;
});
console.log("Resume");
}
それらに使う数値やsetTimeoutのIDの情報をTimeLinesオブジェクトに格納しています。
TimeLines.ID[i]
// --> i番目のsetTimeoutのIDが格納される配列
TimeLines.StopTimeList[i][]
// --> i番目のsetTimeoutのIDの開始した時間や残り時間、待機したあとに実行する関数を格納している二重配列
TimeLines.StopTimeList[i][0]
// --> setTimeoutが開始されたときの現在時刻(Date.now()での取得)[ms]
TimeLines.StopTimeList[i][1]
// --> setTimeoutの待機時間[ms]
TimeLines.StopTimeList[i][2]
// --> setTimeoutで待機したあとに実行される関数
また、ClearTimeoutDataは、完了したsetTimeoutの情報をTimeLinesオブジェクトから削除しています。
function ClearTimeoutData(TimerID) {
// 削除したいsetTimeoutのIDがどこにあるかを探す
let index = TimeLines.ID.indexOf(TimerID);
// 存在していれば削除
if (index != -1) {
TimeLines.ID.splice(index, 1);
TimeLines.StopTimeList.splice(index, 1);
TimeLines.isPauseList.splice(index, 1);
}
}
応用(Promiseに利用する)
これをPromiseに応用することで
「3秒後に関数1を実行し、その5秒後に関数2を実行する」
みたいなこともできます。
// ここに先程のmySetTimeout.jsを記述しておく
const Sleep = ms => new Promise(func => mySetTimeout(func, ms));
async function PromiseTimeout() {
console.log("開始");
// 3秒待機
await Sleep(3000);
// ここに3秒後に実行したい処理を記述する
console.log("関数1");
// 5秒待機
await Sleep(5000);
// ここに5秒後(開始してからだと8秒後)に実行したい処理を記述する
console.log("関数2");
}
// 実行
PromiseTimeout();
もちろんこれも先程のPauseTimeoutで一時停止、ResumeTimeoutで一時停止したところから再開できます。
最後に
今回は私の「こんな機能あったらいいな~」という思いから作ったものです。
この記事が誰かの役に立てたならとても嬉しいです!
もしもっといい案があれば是非教えてください!
ご閲覧ありがとうございました。