下班可视化倒计时
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>工作时间进度条</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
/* padding: 20px; */
}
.progress-container {
width: 90%;
max-width: 600px;
background: rgba(255, 255, 255, 0.95);
border-radius: 20px;
padding: 30px;
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.2);
backdrop-filter: blur(10px);
text-align: center;
transition: transform 0.3s ease;
}
/* .progress-container:hover {
transform: translateY(-5px);
} */
h2 {
color: #333;
margin-top: 0;
margin-bottom: 20px;
font-weight: 600;
font-size: 28px;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.time-inputs {
display: flex;
justify-content: center;
gap: 20px;
margin-bottom: 25px;
flex-wrap: wrap;
}
.time-input-group {
display: flex;
flex-direction: column;
align-items: center;
}
.time-input-group label {
font-weight: 600;
color: #555;
margin-bottom: 8px;
}
.time-input {
padding: 10px 15px;
font-size: 16px;
border: 2px solid #ddd;
border-radius: 10px;
outline: none;
transition: border-color 0.3s;
width: 140px;
text-align: center;
}
.time-input:focus {
border-color: #667eea;
}
.progress-info {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
font-weight: 600;
color: #555;
}
.progress-bar-container {
position: relative;
height: 30px;
background: #e0e0e0;
border-radius: 15px;
overflow: hidden;
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 2px 4px rgba(0, 0, 0, 0.1);
margin-bottom: 25px;
}
.progress-bar {
position: relative;
height: 100%;
width: 0%;
background: linear-gradient(90deg, #667eea, #764ba2);
border-radius: 15px;
transition: width 0.1s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: 0 2px 8px rgba(102, 126, 234, 0.4);
}
.progress-bar::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
animation: shimmer 2s infinite;
border-radius: 15px;
}
.progress-bar::after {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 50%;
background: linear-gradient(to bottom, rgba(255, 255, 255, 0.3), transparent);
border-radius: 15px 15px 0 0;
}
@keyframes shimmer {
0% {
transform: translateX(-100%);
}
100% {
transform: translateX(100%);
}
}
.progress-text {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: bold;
font-size: 14px;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
z-index: 2;
}
.progress-percentage {
font-size: 24px;
font-weight: 700;
color: #333;
margin-bottom: 20px;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}
.time-info {
display: flex;
justify-content: space-around;
margin-bottom: 25px;
padding: 15px;
background: rgba(102, 126, 234, 0.05);
border-radius: 15px;
}
.time-item {
text-align: center;
}
.time-value {
font-size: 18px;
font-weight: 700;
color: #667eea;
}
.time-label {
font-size: 14px;
color: #666;
margin-top: 5px;
}
.current-time {
font-size: 18px;
font-weight: 600;
color: #333;
margin: 20px 0;
padding: 10px;
background: rgba(102, 126, 234, 0.1);
border-radius: 10px;
}
.status {
font-size: 18px;
font-weight: 600;
margin: 15px 0;
padding: 10px;
border-radius: 10px;
}
.status.before {
color: #f5576c;
background: rgba(245, 87, 108, 0.1);
}
.status.during {
color: #43e97b;
background: rgba(67, 233, 123, 0.1);
}
.status.after {
color: #f093fb;
background: rgba(240, 147, 251, 0.1);
}
.controls {
display: flex;
justify-content: center;
gap: 15px;
margin-top: 20px;
}
button {
padding: 12px 25px;
font-size: 16px;
font-weight: 600;
border: none;
border-radius: 50px;
cursor: pointer;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
position: relative;
overflow: hidden;
outline: none;
}
button::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(255, 255, 255, 0.2);
transform: translateX(-100%);
transition: transform 0.3s ease;
}
button:hover::before {
transform: translateX(100%);
}
button:active {
transform: translateY(2px);
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
}
#updateBtn {
background: linear-gradient(135deg, #667eea, #764ba2);
color: white;
}
#resetBtn {
background: linear-gradient(135deg, #43e97b, #38f9d7);
color: white;
}
button:disabled {
background: #cccccc !important;
cursor: not-allowed;
transform: none;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}
button:disabled::before {
display: none;
}
</style>
</head>
<body>
<div class="progress-container">
<h2>工作时间进度条</h2>
<div class="time-inputs">
<div class="time-input-group">
<label for="startTime">上班时间</label>
<select id="startTime" class="time-input"></select>
</div>
<div class="time-input-group">
<label for="endTime">下班时间</label>
<select id="endTime" class="time-input"></select>
</div>
</div>
<div class="current-time" id="currentTime">当前时间: <span id="currentClock">--:--:--</span></div>
<div class="status" id="workStatus">请设置工作时间</div>
<div class="progress-info">
<span>工作进度</span>
<span id="progressValue">0%</span>
</div>
<div class="progress-bar-container">
<div class="progress-bar" id="progressBar">
<div class="progress-text" id="progressText">0%</div>
</div>
</div>
<div class="progress-percentage" id="progressPercentage">0%</div>
<div class="time-info">
<div class="time-item">
<div class="time-value" id="startTimeValue">08:30</div>
<div class="time-label">上班时间</div>
</div>
<div class="time-item">
<div class="time-value" id="endTimeValue">18:30</div>
<div class="time-label">下班时间</div>
</div>
<div class="time-item">
<div class="time-value" id="workDuration">10小时</div>
<div class="time-label">工作时长</div>
</div>
</div>
<div class="controls">
<button id="updateBtn">开始计算</button>
<button id="resetBtn">重置</button>
</div>
</div>
<script>
let interval = null
let isRunning = false
let previousProgress = 0 // 保存上一次的进度值
const startTimeInput = document.getElementById('startTime')
const endTimeInput = document.getElementById('endTime')
const progressBar = document.getElementById('progressBar')
const progressText = document.getElementById('progressText')
const progressValue = document.getElementById('progressValue')
const progressPercentage = document.getElementById('progressPercentage')
const updateBtn = document.getElementById('updateBtn')
const resetBtn = document.getElementById('resetBtn')
const currentTimeElement = document.getElementById('currentClock')
const workStatusElement = document.getElementById('workStatus')
const startTimeValueElement = document.getElementById('startTimeValue')
const endTimeValueElement = document.getElementById('endTimeValue')
const workDurationElement = document.getElementById('workDuration')
// 生成每15分钟的选项
function generateTimeOptions() {
const options = []
for (let hours = 0; hours < 24; hours++) {
for (let minutes = 0; minutes < 60; minutes += 15) {
const timeStr = `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}`
options.push(`<option value="${timeStr}">${timeStr}</option>`)
}
}
return options.join('')
}
// 更新时间显示
function updateCurrentTime() {
const now = new Date()
const timeString = now.toTimeString().slice(0, 8)
currentTimeElement.textContent = timeString
return now
}
// 计算工作进度
function calculateWorkProgress() {
const now = updateCurrentTime()
// 获取输入的时间
const startTimeStr = startTimeInput.value || '08:30'
const endTimeStr = endTimeInput.value || '18:30'
// 更新显示的时间
startTimeValueElement.textContent = startTimeStr
endTimeValueElement.textContent = endTimeStr
// 计算工作时长
const [startHours, startMinutes] = startTimeStr.split(':').map(Number)
const [endHours, endMinutes] = endTimeStr.split(':').map(Number)
const workHours = endHours - startHours
const workMinutes = endMinutes - startMinutes
const totalWorkMinutes = workHours * 60 + workMinutes
workDurationElement.textContent = `${Math.floor(totalWorkMinutes / 60)}小时${totalWorkMinutes % 60}分钟`
// 创建日期对象用于比较
const startDate = new Date()
startDate.setHours(startHours, startMinutes, 0, 0)
const endDate = new Date()
endDate.setHours(endHours, endMinutes, 0, 0)
// 如果下班时间早于上班时间(跨天情况)
if (endDate <= startDate) {
endDate.setDate(endDate.getDate() + 1)
}
// 计算进度
let progress = 0
let statusText = ''
let statusClass = ''
if (now < startDate) {
// 工作尚未开始
progress = 0
statusText = '工作尚未开始'
statusClass = 'before'
} else if (now > endDate) {
// 工作已经结束
progress = 100
statusText = '工作时间已结束'
statusClass = 'after'
} else {
// 工作进行中
const totalWorkTime = endDate - startDate
const elapsedWorkTime = now - startDate
progress = Math.min(100, Math.max(0, (elapsedWorkTime / totalWorkTime) * 100))
statusText = '工作中...'
statusClass = 'during'
}
// 更新进度条显示
updateProgress(progress, statusText, statusClass)
}
// 更新进度条显示
function updateProgress(value, statusText, statusClass) {
// 使用动画函数逐步从上一次的值过渡到当前值
animateProgress(previousProgress, value, statusText, statusClass)
// 保存当前值作为下一次的起始值
previousProgress = value
}
// 动画函数,使进度条跳动更连贯
function animateProgress(fromValue, toValue, statusText, statusClass) {
const startTime = performance.now()
const duration = 300 // 动画持续时间(毫秒)
const animate = (currentTime) => {
const elapsed = currentTime - startTime
const progress = Math.min(elapsed / duration, 1)
// 使用缓动函数使动画更平滑
const easeProgress = 1 - Math.pow(1 - progress, 3)
const currentValue = fromValue + (toValue - fromValue) * easeProgress
// 保留三位小数
const formattedValue = currentValue.toFixed(3)
const width = currentValue + '%'
progressBar.style.width = width
progressText.textContent = formattedValue + '%'
progressValue.textContent = formattedValue + '%'
progressPercentage.textContent = formattedValue + '%'
// 更新状态显示
workStatusElement.textContent = statusText
workStatusElement.className = 'status ' + statusClass
// 如果动画未完成,继续执行
if (progress < 1) {
requestAnimationFrame(animate)
}
}
// 启动动画
requestAnimationFrame(animate)
}
// 开始计算进度
function startCalculation() {
if (isRunning) return
isRunning = true
updateBtn.textContent = '计算中...'
updateBtn.disabled = true
// 立即计算一次
calculateWorkProgress()
// 每100毫秒更新一次,使进度条跳动更连贯
interval = setInterval(calculateWorkProgress, 100)
}
// 停止计算
function stopCalculation() {
isRunning = false
clearInterval(interval)
updateBtn.textContent = '开始计算'
updateBtn.disabled = false
}
// 重置进度
function resetProgress() {
stopCalculation()
startTimeInput.value = '08:30'
endTimeInput.value = '18:30'
// 重置上一次的进度值
previousProgress = 0
updateProgress(0, '请设置工作时间', '')
workStatusElement.textContent = '请设置工作时间'
workStatusElement.className = 'status'
}
// 初始化下拉选择器
function initTimeSelectors() {
const timeOptions = generateTimeOptions()
startTimeInput.innerHTML = timeOptions
endTimeInput.innerHTML = timeOptions
// 设置默认值
startTimeInput.value = '08:30'
endTimeInput.value = '18:30'
}
// 事件监听器
updateBtn.addEventListener('click', startCalculation)
resetBtn.addEventListener('click', resetProgress)
// 时间输入变化时停止计算
startTimeInput.addEventListener('change', stopCalculation)
endTimeInput.addEventListener('change', stopCalculation)
// 初始化时间显示和下拉选择器
updateCurrentTime()
initTimeSelectors()
// 自动开始计算进度
startCalculation()
// 每秒更新当前时间显示
setInterval(updateCurrentTime, 1000)
</script>
</body>
</html>

浙公网安备 33010602011771号