令和7年10月4日を持って,グループ埋め込みをさせていただきます.詳細はこちらをご覧下さい.
1度にタイマーは5個まで動かせます.
キーの説明は「基本操作」をご覧下さい.キーがなくても画面で操作は可能です.
「タイマーを作成」を押すと右上にそのタイマーのみのキーの案内が出てきます.
ていうか,「タイマーを作成」を押さないとタイマーは始まらないので.
最大65分までの計測が可能です.【この65分は英検3級の筆記と同じ時間です.】
…といった感じでしょうか,よろしくお願いします!
申し訳ありませんが,このサイトの作成ソフトの仕様上,タイマー終了時に通知が出ておりません.
通知機能がほしい人は,こちらのhtmlをコピーして,どっかのプレビューサイトをご利用下さい.
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>【全操作キーで完結!】0.000秒単位でタイマー</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'BIZ UDPMincho', sans-serif;
background: linear-gradient(135deg, #00ac9b);
min-height: 100vh;
padding: 20px;
}
.main-container {
max-width: 1400px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 30px;
}
.title {
font-size: 36px;
font-weight: bold;
margin-bottom: 10px;
}
.subtitle {
font-size: 18px;
opacity: 0.9;
}
.timer-creator {
background: rgba(255, 255, 255, 0.95);
border-radius: 20px;
padding: 30px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
margin-bottom: 30px;
}
.creator-title {
font-size: 24px;
color: #333;
margin-bottom: 20px;
text-align: center;
font-weight: bold;
}
.timer-setup {
display: flex;
gap: 20px;
justify-content: center;
align-items: end;
flex-wrap: wrap;
margin-bottom: 20px;
}
.input-group {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
}
.timer-name-input {
width: 200px;
height: 50px;
font-size: 18px;
text-align: center;
border: 2px solid #00ac9b;
border-radius: 10px;
padding: 0 15px;
}
.timer-name-input:focus {
outline: none;
border-color: #00ac9b;
box-shadow: 0 0 10px rgba(52, 152, 219, 0.3);
}
.time-input {
width: 80px;
height: 50px;
font-size: 24px;
text-align: center;
border: 2px solid #00ac9b;
border-radius: 10px;
font-family: 'Courier New', monospace;
font-weight: bold;
}
.time-input:focus {
outline: none;
border-color: #00ac9b;
box-shadow: 0 0 10px rgba(52, 152, 219, 0.3);
}
.input-label {
font-size: 16px;
font-weight: bold;
color: #666;
}
.quick-set {
display: flex;
gap: 10px;
justify-content: center;
flex-wrap: wrap;
margin: 20px 0;
}
.quick-btn {
padding: 8px 16px;
font-size: 14px;
border: 2px solid #00ac9b;
background: white;
color: #3498db;
border-radius: 20px;
cursor: pointer;
transition: all 0.3s ease;
}
.quick-btn:hover {
background: #00ac9b;
color: white;
}
.create-btn {
padding: 15px 40px;
font-size: 18px;
font-weight: bold;
border: none;
border-radius: 50px;
cursor: pointer;
background: linear-gradient(45deg, #27ae60, #2ecc71);
color: white;
transition: all 0.3s ease;
display: block;
margin: 0 auto;
}
.create-btn:hover {
background: linear-gradient(45deg, #00ac9b);
transform: translateY(-2px);
}
.create-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
transform: none;
}
.timers-container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
gap: 20px;
}
.timer-card {
background: rgba(255, 255, 255, 0.95);
border-radius: 20px;
padding: 30px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
text-align: center;
position: relative;
}
.timer-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.timer-name {
font-size: 20px;
color: #333;
font-weight: bold;
background: #f8f9fa;
padding: 10px 20px;
border-radius: 25px;
border: 2px solid #00ac9b;
flex: 1;
margin-right: 10px;
}
.timer-keys {
font-size: 12px;
color: #666;
background: #00ac9b;
padding: 5px 10px;
border-radius: 15px;
white-space: nowrap;
}
.time-display {
font-size: 36px;
font-weight: bold;
color: #2c3e50;
margin: 20px 0;
font-family: 'Courier New', monospace;
background: #f8f9fa;
padding: 15px;
border-radius: 15px;
border: 3px solid #00ac9b;
letter-spacing: 1px;
}
.milliseconds {
color: #e74c3c;
font-size: 28px;
}
.timer-controls {
display: flex;
gap: 10px;
justify-content: center;
flex-wrap: wrap;
margin: 20px 0;
}
.btn {
padding: 12px 25px;
font-size: 16px;
font-weight: bold;
border: none;
border-radius: 50px;
cursor: pointer;
transition: all 0.3s ease;
min-width: 100px;
}
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.btn-start {
background: linear-gradient(45deg, #00ac9b);
color: white;
}
.btn-start:hover:not(:disabled) {
background: linear-gradient(45deg, #00ac9b);
transform: translateY(-2px);
}
.btn-stop {
background: linear-gradient(45deg, #00ac9b);
color: white;
}
.btn-stop:hover:not(:disabled) {
background: linear-gradient(45deg, #00ac9b);
transform: translateY(-2px);
}
.btn-delete {
background: linear-gradient(45deg, #00ac9b);
color: white;
}
.btn-delete:hover:not(:disabled) {
background: linear-gradient(45deg, #00ac9b);
transform: translateY(-2px);
}
.timer-status {
margin-top: 15px;
font-size: 16px;
font-weight: bold;
min-height: 20px;
}
.status-running {
color: #00ac9b;
}
.status-finished {
color: #00ac9b;
animation: blink 1s infinite;
}
.status-stopped {
color: #00ac9b;
}
@keyframes blink {
0%, 50% { opacity: 1; }
51%, 100% { opacity: 0.3; }
}
.delete-btn {
position: absolute;
top: 15px;
right: 15px;
width: 30px;
height: 30px;
border-radius: 50%;
background: #00ac9b;
color: white;
border: none;
cursor: pointer;
font-size: 18px;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
}
.delete-btn:hover {
background: #00ac9b;
transform: scale(1.1);
}
.help-text {
text-align: center;
margin-top: 20px;
font-size: 14px;
color: white;
opacity: 0.9;
line-height: 1.6;
}
.key-help {
background: rgba(255, 255, 255, 0.1);
border-radius: 10px;
padding: 15px;
margin-top: 15px;
}
@media (max-width: 768px) {
.timer-setup {
flex-direction: column;
align-items: center;
}
.timers-container {
grid-template-columns: 1fr;
}
.time-display {
font-size: 28px;
}
.milliseconds {
font-size: 22px;
}
}
</style>
</head>
<body>
<div class="main-container">
<div class="header">
<h1 class="title">【全操作キーで完結!】0.000秒単位でタイマー</h1>
<p class="subtitle">キーボードだけで全操作可能・最大5個のタイマーが同時動作</p>
</div>
<div class="timer-creator">
<h2 class="creator-title">【数字キー・矢印キーでキーボード入力!】0.000単位でタイマー (<span id="timerCount">0</span>/5)</h2>
<div class="timer-setup">
<div class="input-group">
<input type="text" class="timer-name-input" id="newTimerName" placeholder="タイマー名を入力" maxlength="20">
<label class="input-label">タイマー名</label>
</div>
<div class="input-group">
<input type="number" class="time-input" id="newMinuteInput" min="0" max="65" value="0" placeholder="0">
<label class="input-label">分</label>
</div>
<div class="input-group">
<input type="number" class="time-input" id="newSecondInput" min="0" max="59" value="0" placeholder="0">
<label class="input-label">秒</label>
</div>
</div>
<div class="quick-set">
<button class="quick-btn" onclick="quickSetNew(0, 30)">30秒</button>
<button class="quick-btn" onclick="quickSetNew(1, 0)">1分</button>
<button class="quick-btn" onclick="quickSetNew(3, 0)">3分</button>
<button class="quick-btn" onclick="quickSetNew(5, 0)">5分</button>
<button class="quick-btn" onclick="quickSetNew(10, 0)">10分</button>
<button class="quick-btn" onclick="quickSetNew(50, 0)">50分</button>
<button class="quick-btn" onclick="quickSetNew(65, 0)">65分</button>
</div>
<button class="create-btn" id="createBtn" onclick="createTimer()">タイマーを作成 (Jキー)</button>
</div>
<div class="timers-container" id="timersContainer">
<!-- タイマーがここに追加されます -->
</div>
<div class="help-text">
<div>💡 タイマーは終了時にデスクトップ通知でお知らせします</div>
<div class="key-help">
<div><strong>タイマー操作:</strong> J(セット) | K(全スタート/ストップ) | H(全リセット)</div>
<div><strong>矢印キー:</strong> タイマー名 → 分 → 秒 の順で移動</div>
<div id="keyAssignments">タイマーを作成すると個別キーが表示されます</div>
</div>
</div>
</div>
<script>
// グローバル変数
let timers = [];
let availableSlots = [1, 2, 3, 4, 5];
const keyMappings = {
1: { start: 'q', reset: 'w', startName: 'Q', resetName: 'W' },
2: { start: 'e', reset: 'r', startName: 'E', resetName: 'R' },
3: { start: 't', reset: 'y', startName: 'T', resetName: 'Y' },
4: { start: 'u', reset: 'i', startName: 'U', resetName: 'I' },
5: { start: 'o', reset: 'p', startName: 'O', resetName: 'P' }
};
// タイマークラス
class Timer {
constructor(slot, name, minutes, seconds) {
this.slot = slot;
this.name = name || `タイマー${slot}`;
this.totalTime = (minutes * 60 + seconds) * 1000;
this.remainingTime = this.totalTime;
this.interval = null;
this.isRunning = false;
this.startTime = 0;
this.pausedTime = 0;
this.isFinished = false;
this.keys = keyMappings[slot];
}
start() {
if (this.isFinished || this.remainingTime <= 0) return;
this.startTime = performance.now() - this.pausedTime;
this.interval = setInterval(() => this.update(), 1);
this.isRunning = true;
this.updateDisplay();
this.updateStatus('実行中...', 'status-running');
}
stop() {
if (this.interval) {
clearInterval(this.interval);
this.pausedTime = performance.now() - this.startTime;
this.isRunning = false;
this.updateDisplay();
this.updateStatus('一時停止', 'status-stopped');
}
}
reset() {
if (this.interval) {
clearInterval(this.interval);
}
this.remainingTime = this.totalTime;
this.pausedTime = 0;
this.isRunning = false;
this.isFinished = false;
this.updateTimeDisplay();
this.updateDisplay();
this.updateStatus('リセット完了', 'status-stopped');
}
update() {
const currentTime = performance.now();
const elapsed = currentTime - this.startTime;
this.remainingTime = Math.max(0, this.totalTime - elapsed);
this.updateTimeDisplay();
if (this.remainingTime <= 0) {
this.finish();
}
}
finish() {
clearInterval(this.interval);
this.remainingTime = 0;
this.isRunning = false;
this.isFinished = true;
this.updateTimeDisplay();
this.updateStatus('⏰ 終了!', 'status-finished');
this.updateDisplay();
this.sendNotification();
const card = document.getElementById(`timer-${this.slot}`);
if (card) {
card.style.animation = 'blink 0.5s 6';
setTimeout(() => {
card.style.animation = '';
}, 3000);
}
}
updateTimeDisplay() {
const display = document.getElementById(`display-${this.slot}`);
if (display) {
const formattedTime = this.formatTime(this.remainingTime);
display.innerHTML = formattedTime.replace(/\.(\d{3})$/, '<span class="milliseconds">.$1</span>');
}
}
updateDisplay() {
const startBtn = document.getElementById(`start-${this.slot}`);
if (startBtn) {
if (this.isFinished) {
startBtn.disabled = true;
startBtn.textContent = '終了';
startBtn.className = 'btn btn-delete';
} else if (this.isRunning) {
startBtn.disabled = false;
startBtn.textContent = 'ストップ';
startBtn.className = 'btn btn-stop';
} else {
startBtn.disabled = false;
startBtn.textContent = 'スタート';
startBtn.className = 'btn btn-start';
}
}
}
updateStatus(message, className) {
const status = document.getElementById(`status-${this.slot}`);
if (status) {
status.textContent = message;
status.className = 'timer-status ' + className;
}
}
formatTime(milliseconds) {
const totalSeconds = Math.floor(milliseconds / 1000);
const ms = Math.floor(milliseconds % 1000);
const minutes = Math.floor(totalSeconds / 60);
const seconds = totalSeconds % 60;
return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}.${ms.toString().padStart(3, '0')}`;
}
sendNotification() {
if ('Notification' in window && Notification.permission === 'granted') {
const notification = new Notification(`タイマー「${this.name}」終了!`, {
body: '設定した時間になりました',
icon: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjQiIGhlaWdodD0iNjQiIHZpZXdCb3g9IjAgMCA2NCA2NCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGNpcmNsZSBjeD0iMzIiIGN5PSIzMiIgcj0iMzAiIGZpbGw9IiNlNzRjM2MiLz4KPHN2ZyB4PSIxNiIgeT0iMTYiIHdpZHRoPSIzMiIgaGVpZ2h0PSIzMiIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJ3aGl0ZSI+CjxwYXRoIGQ9Ik0xMiAyQzYuNDggMiAyIDYuNDggMiAxMnM0LjQ4IDEwIDEwIDEwIDEwLTQuNDggMTAtMTBTMTcuNTIgMiAxMiAyem0wIDE4Yy00LjQxIDAtOC0zLjU5LTgtOHMzLjU5LTggOC04IDggMy41OSA4IDhTMTYuNDEgMjAgMTIgMjB6bTEtMTNoLTJ2NmwyIDJoMlY3eiIvPgo8L3N2Zz4KPC9zdmc+',
tag: `timer-${this.slot}`,
requireInteraction: true
});
notification.onclick = function() {
window.focus();
notification.close();
};
setTimeout(() => {
notification.close();
}, 5000);
}
}
}
// タイマー関数
function createTimer() {
if (availableSlots.length === 0) {
alert('最大5個までのタイマーを作成できます');
return;
}
const name = document.getElementById('newTimerName').value.trim();
const minutes = Math.max(0, Math.min(65, parseInt(document.getElementById('newMinuteInput').value) || 0));
const seconds = Math.max(0, Math.min(59, parseInt(document.getElementById('newSecondInput').value) || 0));
if (minutes === 0 && seconds === 0) {
alert('時間を設定してください');
return;
}
const slot = availableSlots.shift();
const timer = new Timer(slot, name, minutes, seconds);
timers.push(timer);
createTimerElement(timer);
updateTimerUI();
document.getElementById('newTimerName').value = '';
document.getElementById('newMinuteInput').value = 0;
document.getElementById('newSecondInput').value = 0;
}
function createTimerElement(timer) {
const container = document.getElementById('timersContainer');
const timerElement = document.createElement('div');
timerElement.className = 'timer-card';
timerElement.id = `timer-${timer.slot}`;
const formattedTime = timer.formatTime(timer.remainingTime);
timerElement.innerHTML = `
<button class="delete-btn" onclick="deleteTimer(${timer.slot})">×</button>
<div class="timer-header">
<div class="timer-name">${timer.name}</div>
<div class="timer-keys">${timer.keys.startName}:開始 ${timer.keys.resetName}:リセット</div>
</div>
<div class="time-display" id="display-${timer.slot}">
${formattedTime.replace(/\.(\d{3})$/, '<span class="milliseconds">.$1</span>')}
</div>
<div class="timer-controls">
<button class="btn btn-start" id="start-${timer.slot}" onclick="toggleTimer(${timer.slot})">スタート</button>
</div>
<div class="timer-status" id="status-${timer.slot}">準備完了</div>
`;
container.appendChild(timerElement);
}
function toggleTimer(slot) {
const timer = timers.find(t => t.slot === slot);
if (!timer) return;
if (timer.isRunning) {
timer.stop();
} else {
timer.start();
}
}
function resetTimer(slot) {
const timer = timers.find(t => t.slot === slot);
if (timer) {
timer.reset();
}
}
function deleteTimer(slot) {
const timer = timers.find(t => t.slot === slot);
if (timer) {
if (timer.interval) {
clearInterval(timer.interval);
}
timers = timers.filter(t => t.slot !== slot);
availableSlots.push(slot);
availableSlots.sort((a, b) => a - b);
const element = document.getElementById(`timer-${slot}`);
if (element) {
element.remove();
}
updateTimerUI();
}
}
function updateTimerUI() {
document.getElementById('timerCount').textContent = timers.length;
const createBtn = document.getElementById('createBtn');
if (availableSlots.length === 0) {
createBtn.disabled = true;
createBtn.textContent = 'タイマー上限 (5個)';
} else {
createBtn.disabled = false;
createBtn.textContent = 'タイマーを作成 (Jキー)';
}
updateKeyAssignments();
}
function updateKeyAssignments() {
const keyAssignments = document.getElementById('keyAssignments');
if (!keyAssignments) return;
if (timers.length === 0) {
keyAssignments.textContent = 'タイマーを作成すると個別キーが表示されます';
} else {
const assignments = timers.map(timer =>
`${timer.slot}個目: ${timer.keys.startName}(開始/停止) ${timer.keys.resetName}(リセット)`
).join(' | ');
keyAssignments.innerHTML = `<strong>個別操作:</strong> ${assignments}`;
}
}
function quickSetNew(minutes, seconds) {
document.getElementById('newMinuteInput').value = minutes;
document.getElementById('newSecondInput').value = seconds;
}
function allStartStop() {
const runningTimers = timers.filter(t => t.isRunning);
if (runningTimers.length > 0) {
timers.forEach(timer => {
if (timer.isRunning) {
timer.stop();
}
});
} else {
timers.forEach(timer => {
if (!timer.isFinished && timer.remainingTime > 0) {
timer.start();
}
});
}
}
function allReset() {
timers.forEach(timer => {
timer.reset();
});
}
// 入力制御
function handleInputKeydown(event) {
const allowedKeys = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'Backspace', 'Delete', 'Tab', 'Enter', 'ArrowLeft', 'ArrowRight'];
if (event.key === 'ArrowRight') {
event.preventDefault();
if (event.target.id === 'newTimerName') {
document.getElementById('newMinuteInput').focus();
document.getElementById('newMinuteInput').select();
} else if (event.target.id === 'newMinuteInput') {
document.getElementById('newSecondInput').focus();
document.getElementById('newSecondInput').select();
}
return;
}
if (event.key === 'ArrowLeft') {
event.preventDefault();
if (event.target.id === 'newSecondInput') {
document.getElementById('newMinuteInput').focus();
document.getElementById('newMinuteInput').select();
} else if (event.target.id === 'newMinuteInput') {
document.getElementById('newTimerName').focus();
document.getElementById('newTimerName').select();
}
return;
}
if ((event.target.id === 'newMinuteInput' || event.target.id === 'newSecondInput') &&
!allowedKeys.includes(event.key)) {
event.preventDefault();
event.target.blur();
return;
}
}
// イベントリスナー設定
document.getElementById('newTimerName').addEventListener('keydown', handleInputKeydown);
document.getElementById('newMinuteInput').addEventListener('keydown', handleInputKeydown);
document.getElementById('newSecondInput').addEventListener('keydown', handleInputKeydown);
// 通知許可
function requestNotificationPermission() {
if ('Notification' in window && Notification.permission === 'default') {
Notification.requestPermission();
}
}
// キーボードイベント
document.addEventListener('keydown', function(event) {
const activeElement = document.activeElement;
const isInputFocused = activeElement.tagName === 'INPUT';
const key = event.key.toLowerCase();
if (key === 'j') {
event.preventDefault();
createTimer();
return;
}
if (key === 'k' && !isInputFocused) {
event.preventDefault();
allStartStop();
return;
}
if (key === 'h' && !isInputFocused) {
event.preventDefault();
allReset();
return;
}
if (!isInputFocused) {
timers.forEach(timer => {
if (key === timer.keys.start) {
event.preventDefault();
toggleTimer(timer.slot);
} else if (key === timer.keys.reset) {
event.preventDefault();
resetTimer(timer.slot);
}
});
}
});
// Enterキーでタイマー作成
document.addEventListener('keydown', function(event) {
if (event.key === 'Enter') {
const activeElement = document.activeElement;
if (activeElement.id === 'newTimerName' ||
activeElement.id === 'newMinuteInput' ||
activeElement.id === 'newSecondInput') {
event.preventDefault();
createTimer();
}
}
});
// 初期化
updateTimerUI();
setTimeout(requestNotificationPermission, 1000);
</script>
<script>(function(){function c(){var b=a.contentDocument||a.contentWindow.document;if(b){var d=b.createElement('script');d.innerHTML="window.__CF$cv$params={r:'975f9587b799eb72',t:'MTc1NjMzOTgxMC4wMDAwMDA='};var a=document.createElement('script');a.nonce='';a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);";b.getElementsByTagName('head')[0].appendChild(d)}}if(document.body){var a=document.createElement('iframe');a.height=1;a.width=1;a.style.position='absolute';a.style.top=0;a.style.left=0;a.style.border='none';a.style.visibility='hidden';document.body.appendChild(a);if('loading'!==document.readyState)c();else if(window.addEventListener)document.addEventListener('DOMContentLoaded',c);else{var e=document.onreadystatechange||function(){};document.onreadystatechange=function(b){e(b);'loading'!==document.readyState&&(document.onreadystatechange=e,c())}}}})();</script></body>
</html>
※このコードの著作権はCanvaCodeに属する.