JavaScript 实现时间戳进度条

1,027 阅读3分钟

效果预览

动画.gif

概述

本文教你如何用像素值(px)表示时间(如:2023-5-6 10:00:00),并且让时间随着鼠标的操作而变化。你只需要掌握一个简单的转换公式,就可以编写出一个漂亮的时间戳进度条。

计算逻辑

假设有一个长度为300px的进度条,并且已知开始时间和结束时间(时间戳需要为秒),当鼠标点击进度条某一段区域时,使用offsetX获取鼠标位置,再进行如下计算:

  1. 用 offsetX / 进度条长度 * 间隔时长(结束时间 - 开始时间) = 时间(秒)
  2. new Date(开始时间 + 时间(秒) * 1000)

你可能会好奇,为什么这样的计算能够准确地显示鼠标所在的时间。别着急,下面就告诉你答案。

  1. 鼠标在进度条上的进度 = e.offsetX / 进度条长度 * 100;
    • 小数百分比转为百分数(%),需要乘以100。计算方式和结果为 0.1 * 100 = 10% ... 1 * 100 = 100;
    • 我们可以利用这种形式去乘以“间隔时长”,以换算出鼠标所在位置为几秒;
    • 假设变量 duration = 7200,e.offsetX / 进度条长度 * duration,持续递进。计算方式和结果为 0.1 * 7200 = 720 ... 1 * 7200 = 7200
  2. new Date(开始时间 + 时间(秒) * 1000) 这个就很好理解了,开始时间 + 时间(秒)生成完整的时间戳(秒),乘以 1000 ,则是为了转换为毫秒;

如:

let time = offsetX / 进度条长度 * 间隔时长(结束时间 - 开始时间)
2023/10/10 10:00:00 = new Date(startTime + time * 1000).toLocaleString()

代码预览

源码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="referrer" content="no-referrer">
    <title>Title</title>
    <style>
        #bar-box {
            position: relative;
            margin: 50px;
        }

        #progress-bar {
            position: relative;
            margin: 50px;
            width: 300px;
            height: 10px;
            background: #7aa1d2;
            border-radius: 5px;
            cursor: pointer;
        }

        #progress-bar #completed-progress {
            border-radius: 5px;
            height: 100%;
            position: absolute;
            top: 0;
            left: 0;
            background: #6c5e86;
            z-index: 100;
        }

        #progress-bar span {
            position: absolute;
            top: -1px;
            left: 0;
            height: 12px;
            width: 12px;
            border-radius: 50%;
            background: #a0522d;
            z-index: 101;
        }

        #top-tip, #bottom-tip {
            position: absolute;
        }

        #top-tip {
            top: -25px;
        }

        #bottom-tip {
            top: 15px;
        }
    </style>
</head>
<body>
<img src="https://img-blog.csdnimg.cn/6d15082ac7234ec7a16065e74f689590.jpeg" alt="" style="width: 200px;height: 200px">
<div>
    <p>
        进度条开始时间:<span class="time"></span>
    </p>
    <p>
        进度条结束时间:<span class="time"></span>
    </p>
</div>
<div id="bar-box">
    <div id="progress-bar">
        <div id="completed-progress"></div>
        <span id="progress-button"></span>
    </div>
    <span id="top-tip"></span>
    <span id="bottom-tip"></span>
</div>
<button id="button">开始播放</button>
<button id="stop">停止播放</button>
<button id="reset">重置</button>

<script>
    const doc = document
    const timeDom = doc.getElementsByClassName('time')
    const topTip = doc.getElementById('top-tip')
    const bottomTip = doc.getElementById('bottom-tip')
    const progressBar = doc.getElementById('progress-bar')
    const progressButton = doc.getElementById('progress-button')
    const completedProgress = doc.getElementById('completed-progress')
    const button = doc.getElementById('button')
    const stop = doc.getElementById('stop')
    const reset = doc.getElementById('reset')

    const progressBarW = progressBar.offsetWidth
    const progressButtonW = progressButton.offsetWidth

    let frame = null
    let offsetPx = 0
    let date = new Date()
    let startTime = date.getTime()
    let endTime = date.getTime() + 2 * 60 * 60 * 1000  // 小时
    // let endTime = date.getTime() + 2 * 24 * 60 * 60 * 1000  // 天

    timeDom[0].innerText = new Date(startTime).toLocaleString()
    timeDom[1].innerText = new Date(endTime).toLocaleString()

    progressBar.onmousemove = mousemoveBar
    progressBar.onclick = clickBar
    button.onclick = clickButton
    stop.onclick = clickStop
    reset.onclick = clickReset

    let offsetX = 0

    function mousemoveBar(e) {
        // 鼠标移动不能精确的达到 e.offsetX = progressBarW 或 e.offsetX = 0,此时需要判断是否移动到末尾或起始位置
        if (e.offsetX === 1)
            offsetX = 0
        else
            offsetX = e.offsetX === progressBarW - 1 ? progressBarW : e.offsetX

        topTip.innerText = createTime(offsetX)
    }

    function clickBar(e) {
        offsetPx = e.offsetX - progressButtonW / 2
        // progressButton.style.left = `${offsetPx}px`
        progressButton.style.left = `${offsetPx / progressBarW * 100}%`
        completedProgress.style.width = `${e.offsetX / progressBarW * 100}%`
        bottomTip.innerText = createTime(e.offsetX)
    }

    function clickButton() {
        if (offsetPx !== progressButtonW / 2) offsetPx += progressButtonW / 2 // 点击播放时,避免出现一瞬间回退几个像素的的情况
        start()
    }

    function start() {
        if (offsetPx > progressBarW) {
            cancelAnimationFrame(frame)
            return
        }

        frame = requestAnimationFrame(start)

        // progressButton.style.left = `${offsetPx - progressButtonW / 2}px`
        progressButton.style.left = `calc(${offsetPx / progressBarW * 100}% - ${progressButtonW / 2}px)`
        completedProgress.style.width = `${offsetPx / progressBarW * 100}%`
        bottomTip.innerText = createTime(offsetPx)
        offsetPx += 0.5
    }

    function clickStop() {
        cancelAnimationFrame(frame)
    }

    function clickReset() {
        cancelAnimationFrame(frame)
        offsetPx = 0
        progressButton.style.left = offsetPx
        completedProgress.style.width = '0%'
        bottomTip.innerText = ''
        topTip.innerText = ''
    }

    // 秒 = 毫秒 / 1000
    // 间隔时长(秒) = 结束时间 - 开始时间
    const duration = endTime / 1000 - startTime / 1000

    /**
     * 鼠标在进度条上的进度(小数百分比) = e.offsetX / progressBarW
     * 我们知道,小数百分比转为百分数(%),需要乘以100,计算方式为 0.1 * 100 = 10%  1 * 100 = 100
     * 我们可以利用这种形式去乘以”间隔时长“,以换算出鼠标所在的位置,是多少秒
     * 如:假设 duration = 7200,e.offsetX / progressBarW * duration 持续递进,计算方式为 0.1 * 7200 = 720 ... 1 * 7200 = 7200,
     **/
    function createTime(x) {
        let time = x / progressBarW * duration
        return new Date(startTime + time * 1000).toLocaleString()
    }
</script>
</body>
</html>