SOURCE

console 命令行工具 X clear

                    
>
console
let video = document.querySelector('video');
let track = video.addTextTrack('subtitles', 'text', 'zh-cn');
let timer = null;
track.mode = 'showing';
let lyric = `
[00:00.88]不该
[00:05.54]词:方文山
[00:06.12]曲:周杰伦
[00:08.62]演唱:周杰伦,aMEI
[00:10.29]
[00:24.31]Jay:
[00:25.91]
[00:26.67]假装我们还在一块
[00:30.83]
[00:33.16]我真的演不出来
[00:36.31]
[00:39.46]还是不习惯你不在
[00:43.97]
[00:44.89]这身份转变太快
[00:48.43]
[00:49.64]aMEI:
[00:50.73]
[00:51.89]画面里不需要旁白
[00:57.12]
[00:58.64]却谁都看的出来
[01:02.74]
[01:05.02]是我情绪涌了上来
[01:09.97]
[01:10.64]想哭却一遍空白
[01:14.29]
[01:15.52]Jay:
[01:16.90]雪地里相爱
[01:18.13]他们说零下已结晶的誓言不会坏
[01:22.47]合:
[01:23.25]但爱的状态
[01:24.53]却不会永远都冰封
[01:26.93]而透明的存在
[01:28.84]合:
[01:29.20]轻轻飘 落下来
[01:32.19]许下的梦 融化的太快
[01:35.05]
[01:36.01]或许我们都不该醒来
[01:39.79]
[01:40.71]aMEI:
[01:41.46]你还是住在我的回忆里不出来
[01:47.55]Jay:
[01:48.07]让我们微笑离开让故事留下来
[01:53.75]合:
[01:54.26]放手后爱依然在
[01:57.41]雪融了就应该花开
[02:01.86]缘若尽了
[02:03.47]就不该再重来
[02:06.64]aMEI:
[02:07.19]你依旧住在我的回忆里不出来
[02:13.07]Jay:
[02:13.52]我离开将你的手交给下个最爱
[02:19.25]aMEI:
[02:19.85]纠缠与
[02:20.80]合:
[02:21.14]固执等待
[02:23.00]反而是另一种伤害
[02:26.83]
[02:27.34]彼此紧握的手松开
[02:30.88]去拥抱更多未来
[02:34.69]
[02:45.95]Jay:
[02:47.15]错过的时间怎么买
[02:51.57]
[02:53.91]谁都付不出来
[02:57.30]
[02:57.86]aMEI:
[02:59.34]
[03:00.18]或许我们学会释怀
[03:04.37]
[03:05.71]让过去安静下来
[03:09.13]
[03:11.07]合:
[03:12.24]雪地里相爱
[03:13.34]他们说零下已结晶的誓言不会坏
[03:17.44]
[03:18.53]但爱的状态
[03:19.79]却不会永远都冰封
[03:22.12]而透明的存在
[03:23.91]合:
[03:24.43]轻轻飘 落下来
[03:27.45]许下的梦融化的太快
[03:30.31]
[03:31.09]或许我们都不该醒来
[03:34.79]
[03:36.28]aMEI:
[03:36.80]你还是住在我的回忆里不出来
[03:42.73]Jay:
[03:43.22]让我们微笑离开让故事留下来
[03:49.03]合:
[03:49.51]放手后爱依然在
[03:52.57]雪融了就应该花开
[03:56.32]
[03:56.98]缘若尽了
[03:58.57]就不该再重来
[04:01.83]aMEI:
[04:02.34]你依旧住在我的回忆里不出来
[04:08.28]Jay:
[04:08.73]我离开将你的手交给下个
[04:12.67]合:
[04:13.16]最爱
[04:15.09]aMEI:
[04:15.54]纠缠与
[04:16.10]合:
[04:16.45]固执等待
[04:18.27]反而是另一种伤害
[04:22.67]彼此紧握的手松开
[04:26.21]去拥抱更多未来
[04:30.00]
`
let titles = (function () {
    let reg = /(?:\[(?<min>[0-9]+)\:(?<sec>[0-9]+\.[0-9]+)\])(?<text>.*)/;
    let mreg = /(\[[0-9]+\:[0-9]+\.[0-9]+\].*)/g;
    let arr = lyric.match(mreg) || [];
    let lyrics = arr.filter(function (item) {
        return reg.test(item);
    }).map(function (item) {
        let match = reg.exec(item);
        let text = match.groups.text;
        let sec = parseFloat(match.groups.sec);
        let min = parseInt(match.groups.min);
        let btime = parseFloat(sec + parseFloat((min * 60).toFixed(4)))+1;
        return {
            text,
            btime
        }
    }).map(function (item, index, array) {
        if (array[index + 1]) {
            item.etime = array[index + 1].btime - 0.1;
        } else {
            item.etime = item.btime + 10;
        }
        return item;
    })
    return lyrics;
})();

function addTitles() {
    titles.forEach(function (item) {
        let cue = new VTTCue(item.btime, item.etime, item.text);
        cue.line = 10;
        track.addCue(cue);
    });
}

video.onloadedmetadata = addTitles;

function replaceRule(targetIndex, option) {
    if (document.styleSheets[0].cssRules[targetIndex]) {
        document.styleSheets[0].removeRule(targetIndex);
    }
    document.styleSheets[0].addRule(option.selector, option.style, targetIndex);
}

function animate(begin, end) {
    let duration = end - begin;
    timer = setInterval(function () {
        let step = (100-((end - video.currentTime) / (end-begin) * 100)).toFixed(4);
        replaceRule(1,{
            selector: 'video.video::cue',
            style: `background-size: ${step}%;`,
        });
        if(video.currentTime > end) {
            clearInterval(timer);
        } 
    }, 20);
}

track.oncuechange = function (e) {
    let ac = e.currentTarget.activeCues[0]
    if (ac) {
        animate(ac.startTime, ac.endTime);
    }
}

video.onseeking = function () {
    clearInterval(timer);
}

video.onpause = function () {
    clearInterval(timer);
}

video.onplay = function () {
    clearInterval(timer);
    let ac = track.activeCues[0];
    if(ac) {
        animate(ac.startTime,ac.endTime);
    }
}

video.onratechange = function () {
    clearInterval(timer);
    let ac = track.activeCues[0];
    if(ac) {
        animate(ac.startTime,ac.endTime);
    }
}
<html lang="en">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=, initial-scale=">
	<meta http-equiv="X-UA-Compatible" content="">
	<title></title>
</head>
<body>
	<video class="video" height="500" data-size="50% 100%" controls src="https://mvwebfs.ali.kugou.com/202205081703/a5824e188afde871638eabcdd4dea4cf/G059/M02/16/17/ew0DAFdiZ1iARGOeCQrmQ1joFRw667.mp4"></video>
</body>
</html>
.video::cue {
    background: linear-gradient(to right,#2cdd71,#2cdd71) #eee no-repeat;
    -webkit-background-clip: text;
    color: transparent;
    transform: scale(2);
    font-weight: 600;
}