下班可视化倒计时

<!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>
posted @ 2025-12-04 19:01  朝颜浅语  阅读(0)  评论(0)    收藏  举报