jQuery なし!バニラJS(JavaScript/CommonJS)のカウントダウンタイマーを作ってみたのでそのコードを紹介します。
サンプルコードもあるので参考にしてください。

神守 由理子/フロントエンドエンジニア
この記事の対象者
- JavaScriptが少しわかる
- Webコーディング・プログラミング勉強中
- jQuery使いたくない
- requestAnimationFrame も使ってみたい

ライブラリーいらず!
バニラJS(JavaScript/CommonJS)で実装するから、テキストエディターだけあれば実装できます!
この記事は、2025年2月に大幅にコードを改善し、さらにライブラリ(Luxon)で正確な時間を取得を追記しました。
new Date()の使い方
JavaScriptの Date オブジェクトについて。
JavaScript の日時は、基本的に協定世界時 (UTC) の1970年1月1日深夜0時からの経過ミリ秒数で指定されます。この日付と時刻は、コンピューターに記録される日付と時刻の値の主な基準値である UNIX Epoch と同じです。
const today = new Date();
console.log(today);ちょっと画像が小さいですが、現在の日時を取得できます。
私が住むフィリピンの時間になってますが、日本で同じコードを実行すればもちろん日本の時間が取得できます。
現在と終了日の差分を取得する
//ISO 8601形式で記述
const goal = new Date("2021-06-22T00:00:00");
const today = new Date();
getTime() で値の取得をします。
1970年1月1日深夜0時から指定した日時までの ミリ秒 を取得できます。
const diffTime = date.getTime() - now.getTime();差分が残り時間(ミリ秒)になるのでこの数値を日、時間、分、秒に変換します。
getMonthに気をつける
Dateオブジェクトは月や曜日、日にちなども取得できるメソッドが用意されています。
中でもgetMonthという月を取得するメソッドを使用する際は要注意、JSでは月だけなぜか0始まりで5月が4といったようになります。
出力する際はgetMonth() + 1といったように1プラスする必要があります。
Data オブジェクトがうまく動かない場合
次のような簡略化した書き方では、一部のブラウザ(特に古いバージョン)は、完全にISO 8601形式(日付と時間の標準)に準拠した文字列でないと解釈できないことがあるので注意。
new Date("2022-08-23 8:40:00")Data オブジェクトの取り扱いには以下も併せて注意が必要。
- ブラウザ依存: JavaScriptのDateオブジェクトはブラウザごとに実装が異なる。一部のブラウザはローカル形式の日付文字列をサポートするが、他のブラウザではエラーの発生する可能性がある。
- 時間帯: 明示的にタイムゾーンを指定しない場合、一部のブラウザはデフォルトのタイムゾーンを使用し、それに基づいて日時を解釈。
回避法。コンストラクタ引数を使用する。
const date = new Date(2022, 7, 23, 8, 40, 0); // 月は0から始まるので7は8月を表す
console.log(date);ライブラリを利用できます。 Luxon
切り捨てと除算を使って秒、分、時間、日数を取得する
まずは秒を取得してみましょう。
取得した値はミリ秒なので、 単純に1000で割り、小数点以下を切り捨て値が秒になります。
Math.floor()を使って小数点以下を切り捨てます。
const sec = Math.floor(diffTime / 1000);ここから分を除くためには、60で割った余りを取得しなければなりません。
余りを計算するのに便利なものが除算です。
先に除算の使い方を紹介します。
除算は%を使います。13を3で割ると余りは1になるので出力結果は1になります。
const num = 13 % 3
console.log(num)//出力結果は160(分)で割り切れな数が秒になるので、次のような計算式で出力できます。
const sec = Math.floor(diffTime / 1000) % 60;秒、分、時間、日数はそれぞれ次の通り算出できます。
const sec = Math.floor(diffTime / 1000) % 60;
const min = Math.floor(diffTime / 1000 / 60) % 60;
const hours = Math.floor(diffTime / 1000 / 60 / 60) % 24;
const days = Math.floor(diffTime / 1000 / 60 / 60 / 24);カウントダウンに必要なそれぞれの値を関数にまとめる
// 目標日時の設定
const goal = new Date("2021-05-23T08:15:00");
// カウントダウンの計算結果を格納する変数
let count;
// カウントダウンの計算関数
const countDown = (goal) => {
const now = new Date();
const diffTime = goal.getTime() - now.getTime();
// 残り時間の計算
if (diffTime > 0) {
const sec = Math.floor(diffTime / 1000) % 60;
const min = Math.floor(diffTime / 1000 / 60) % 60;
const hours = Math.floor(diffTime / 1000 / 60 / 60) % 24;
const days = Math.floor(diffTime / 1000 / 60 / 60 / 24);
count = { days, hours, min, sec };
} else {
// 目標日時を過ぎている場合処理を止める
count = { days: 0, hours: 0, min: 0, sec: 0 };
}
return count;
};setTimeoutでタイマー処理
タイマー処理をします。関数countDownで作った値をHTMLに反映させます。
<div class="countdown-timer">
<span id="days"></span>日<span id="hours"></span>時間<span id="min"></span
>分<span id="sec"></span>秒
</div>//Timer処理
function setCountDown() {
let counter = countDown(goal);
const countDownTimer = setTimeout(setCountDown, 1000);
for (let item in counter) {
document.getElementById(item).textContent = counter[item];
}
}
setCountDown();今回はsetTimeoutを使いました。
setTimeout(処理, 間隔);出力はオブジェクト配列をループさせるために for in 文を使いました。
for (const item in array) {
item //キー
array[item] //値
}タイマーの終了処理を入れる
このままでは0の状態でTimerだけ動き続けるので、終了処理を入れます。
残りの日時のすべての値が0になったら、タイマーを止めます。
// タイマー処理
const setCountDown = () => {
//先ほど作成した関数を格納
const counter = countDown(goal);
let end = 0;
// 1秒ごとにカウントダウンを更新
const countDownTimer = setTimeout(setCountDown, 1000);
// 結果をDOMに表示
for (const item in counter) {
document.getElementById(item).textContent = counter[item];
end += counter[item]
}
// カウントダウンが終了した場合
if (end === 0) {
clearTimeout(countDownTimer);
}
};requestAnimationFrame を使う事もできます。
// タイマー処理
const setCountDown = () => {
const counter = countDown(goal);
let end = 0;
// カウントダウンを更新
const countDownTimer = requestAnimationFrame(setCountDown);
// 結果をDOMに表示
for (const item in counter) {
document.getElementById(item).textContent = counter[item];
// parseIntを省略
end += counter[item];
}
// カウントダウンが終了した場合
if (counter.days === 0 && counter.hours === 0 && counter.min === 0 && counter.sec === 0) {
cancelAnimationFrame(countDownTimer);
}
};requestAnimationFrame と setTimeout の違いはこんな感じ。
| setTimeout | requestAnimationFrame | |
|---|---|---|
| メリット | 簡単、 任意の時間間隔で実行 | スムーズなアニメーション、 効率的 |
| デメリット | 描画と同期しない、 カクつくことがある | 正確な時間指定が難しい、 ループ処理が必要 |
| 使い方 | 指定した時間後に関数を実行 | ブラウザの描 画のタイミングで実行 |
正確な時間間隔での繰り返し処理が必要な場合は、setTimeout を使ってください。

キャンペーンやセール終了などはクレームになりかねないので、setTimeoutがいいかもですね。
タイマーのID名をセットします。
const ID名 = setTimeout(処理,間隔);ある条件で、clearTimeout(ID名)で終了させることができます。
if (条件式) {
clearTimeout(ID名);
}コードをまとめると
すべてのコードはこんな感じになります。
<div class="countdown-timer">
<span id="days"></span>日<span id="hours"></span>時間<span id="min"></span
>分<span id="sec"></span>秒
</div>// 目標日時の設定
const goal = new Date("2021-05-23T08:15:00");
// カウントダウンの計算結果を格納する変数
let count;
// カウントダウンの計算関数
const countDown = (goal) => {
const now = new Date();
const diffTime = goal.getTime() - now.getTime();
// 残り時間の計算
if (diffTime > 0) {
const sec = Math.floor(diffTime / 1000) % 60;
const min = Math.floor(diffTime / 1000 / 60) % 60;
const hours = Math.floor(diffTime / 1000 / 60 / 60) % 24;
const days = Math.floor(diffTime / 1000 / 60 / 60 / 24);
count = { days, hours, min, sec };
} else {
// 目標日時を過ぎている場合
count = { days: 0, hours: 0, min: 0, sec: 0 };
}
return count;
};
// タイマー処理
const setCountDown = () => {
const counter = countDown(goal);
let end = 0;
// 1秒ごとにカウントダウンを更新
const countDownTimer = setTimeout(setCountDown, 1000);
// 結果をDOMに表示
for (const item in counter) {
document.getElementById(item).textContent = counter[item];
end += counter[item];
}
// カウントダウンが終了した場合
if (end === 0) {
clearTimeout(countDownTimer);
}
};
// カウントダウンの開始
setCountDown();ここまでのコードではCSSを実装していないのですが、CSS付きでCodepenにサンプルをあげています。

興味がある方はこちらもどうぞ^ ^。
ライブラ リで正確な時間を取得(2025年2月追記)
キャンペーンなどに使うカウントダウンタイマーは、より正確な時間を取得して、タイムゾーンがずれないようにする必要があります。
先程も Luxon について少し紹介しましたが、他にもいくつかライブラリがあります。
| ライブラリ | 特徴 |
|---|---|
| Luxon | モダンなAPIと軽量性を両立 |
| date-fns-tz | 軽量で機能的 |
| Day.js | 軽量でMoment.js互換のAPI |
| js-Joda | ドメイン駆動設計で不変性を重視 |
今回は、Luxonを使ったコードを紹介します。お手軽にCDNを読み込んで使います。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Countdown Timer</title>
<!-- Luxon CDN -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/luxon/3.5.0/luxon.min.js"></script>
</head>
<body>
<!-- カウントダウンの表示要素 -->
<div class="countdown-timer">
<span id="days"></span>日<span id="hours"></span>時間<span id="min"></span>分<span id="sec"></span>秒
</div>
<script>
document.addEventListener('DOMContentLoaded', (event) => {
// 目標日時の設定
const goal = luxon.DateTime.fromISO("2025-05-23T08:15:00", { zone: 'Asia/Tokyo' });
// カウントダウンの計算結果を格納する変数
let count;
// カウントダウンの計算関数
const countDown = (goal) => {
const now = luxon.DateTime.now().setZone('Asia/Tokyo')
const diffTime = goal - now;
// 残り時間の計算
if (diffTime > 0) {
const sec = Math.floor(diffTime / 1000) % 60;
const min = Math.floor(diffTime / 1000 / 60) % 60;
const hours = Math.floor(diffTime / 1000 / 60 / 60) % 24;
const days = Math.floor(diffTime / 1000 / 60 / 60 / 24);
count = { days, hours, min, sec };
} else {
// 目標日時を過ぎている場合
count = { days: 0, hours: 0, min: 0, sec: 0 };
}
return count;
};
// タイマー処理
const setCountDown = () => {
const counter = countDown(goal);
let end = 0;
// 1秒ごとにカウントダウンを更新
const countDownTimer = setTimeout(setCountDown, 1000);
// 結果をDOMに表示
for (const item in counter) {
document.getElementById(item).textContent = counter[item];
end += counter[item];
}
// カウントダウンが終了した場合
if (end === 0) {
clearTimeout(countDownTimer);
}
};
// カウントダウンの開始
setCountDown();
});
</script>
</body>
</html>まとめ・カウントダウンはバニラJS(プレーンなJavaScript/CommonJS)だけでカンタンに実装できた!
シンブルですがバ ニラJS(プレーンなJavaScript/CommonJS)でカウントダウンを実装してみました。
もうちょっとデザインをブラッシュアップすれば、キャンペーンとかにも有効的に使えそうですね!
キャンペーンとして使用するに当たって注意です。
ひと昔前に、「キャンペーン終了まで残り●日」が毎日リセットされるカウントダウンをウェブサイトに実装し炎上した企業がありました。

お客に対してに対してウソはいかんです!
プログラミングを使ってキャンペーンを打つときは、誠実に!
ていうか、人として誠実が大事 ですよ!
余談が過ぎましたが、この記事が皆さんのコーディングライフの一助となれば幸いです。
最後までお読みいただきありがとうございました。
FAQ
- JavaScriptでカウントダウンタイマーを作る際、時間のズレを防ぐには?
標準の
new Date()は実行環境の端末時間に依存するため、キャンペーン等では Luxonなどのライブラリ を使用してタイムゾーン(Asia/Tokyo等)を明示的に指定するのがベストです。また、長時間動作させる場合は誤差が蓄積するため、1秒ごとに現在時刻と目標時刻の差分を再計算するロジックが必要です。
- タイマー更新に setTimeout と requestAnimationFrame どちらを使うべき?
正確な「1秒ごと」のカウントが必要なタイマーには、指定した間隔で実行できる
setTimeoutが適しています。一方で、ミリ秒単位の滑らかなプログレスバーなどのアニメーションを伴う場合は、ブラウザの描画に同期するrequestAnimationFrameが推奨されます。詳細は タイマー処理の比較表 をご覧ください。
- タイマーが終了した時に処理を止める方法は?
タイマーのIDを保持しておき、
clearTimeout(timerId)を実行することで停止できます。本記事の タイマーの終了処理 セクションでは、残り時間がゼロになった瞬間に自動でタイマーを破棄し、ブラウザの負荷を抑える実装方法を解説しています。
- Moment.js 以外で現在推奨される日付ライブラリは?
Moment.js は非推奨(メンテナンスモード)となったため、現在は Luxon, date-fns, Day.js などが主流です。特にタイムゾーンを扱う場合は、本記事でも紹介している Luxon がモダンで使いやすく、バニラJSとの相性も抜群です。

