马黑黑 发表于 2022-9-17 09:27

请跟我来:进度可控+模拟逐字同步

本帖最后由 马黑黑 于 2022-9-17 11:33 编辑

效果请查看:请跟我来

一、CSS

使用背景移动的方式令lrc歌词产生演唱进度动感,#lrc 盒子充满黑科技:

#lrc {
      position: absolute;
      top: 100px;
      font: bold 2em sans-serif;
      color: transparent;
      letter-spacing: 2px;
      background: linear-gradient(-90deg, darkred, pink) 100% 100% / 200% 200%;
      background-clip: text;
      -webkit-background-clip: text;
}

红色那句,设置线性渐变,第一个颜色 darkred(深红色)模拟进度着色,第二个颜色 pink(粉红色)是歌词基本色(但会发生渐变效果)。后面的两组数字,第一组是 background-position(背景位置),第二组是 background-size(背景大小)。蓝色那两句,设置背景以文本为剪裁路径,即仅在文本上显示背景色。

#lrc 盒子不调用 @keyframes 关键帧动画,该工作将由 JS 完成。


@keyframes 关键帧动画比较简单,但相同的内容设计了两组,这是将来JS反复调用关键帧动画最廉价的方式:



      @keyframes bgMove1 { from { background-position: 0 0; } to { background-position: -100% 0; } }
      @keyframes bgMove2 { from { background-position: 0 0; } to { background-position: -100% 0; } }

因为我们前面使用的渐变背景方向是 -90deg,所以,这里,令背景位置从 0% 走到 -100% 。

二、JS

为了让歌词在被唱到的时段里能稳定运行关键帧动画,需要改进以前花潮lrc歌词同步的机制,做到,在这个时段里,歌词只上屏一次(以前的机制是在当句歌词演唱期间,当句歌词不停地上屏,因为是同一句歌词、同一个位置,肉眼感觉不到)。所以要加入一个判断机制。我的做法是,设计一个变量 mKey,该变量作为用以输出歌词的依据,它输出歌词前与 audio 的 timeupdate 事件配合完成判断即其他工作,它输出一句歌词后自动累进,为下一句歌词做准备。mKey 和另外的变量声明在这里:

let mKey = 0, mSeek = false, mFlag = true;

初始值肯定是 0,随后它会改变。其他变量在此一并说明:

mSeek - 进度被人为改变的变量,此变量现在可以删掉,定稿后用不上它
mFlag - 用以判断调用哪一个关键帧动画的依据,请结合前面CSS相关说明帮助理解

下来处理歌词输出的问题。


先看 audio 控件 timeupdate 监听事件:

aud.addEventListener('timeupdate', () => {
      //其他代码
      for(j=0; j<lrcAr.length; j++) {
                if(aud.currentTime >= lrcAr) { // ①
                        if(mKey === j) showLrc(lrcAr); //②
                        else continue; //③
                }
      }
});


这里使用 for 语句循环歌词数组 lrcAr,如果 ① 当前播放时间与数组的时间信息匹配,则,②,如果当前 mKey 变量的值与循环到的匹配了歌句时间信息的 j 绝对相等(===,两个也成,==),就调用显示歌词函数 showLrc(参数) ,否则,③,在当前句演唱期间,跳出该次比对机制。

再看歌词输出函数 showLrc(time) :

let showLrc = (time) => {
      lrc.style.animation = (mFlag ? 'bgMove1 ' : 'bgMove2 ') + time + 's linear forwards'; //根据 mFlag 的值给 animation 指定名称,同时进行其他赋值
      lrc.innerHTML = lrcAr; //输出歌词
      mKey += 1; //mKey变量累进
      mFlag = !mFlag; //变量值互反以保证下一次调用另外一个动画
}


这个函数需要一个参数 time,time 参数是被带入参数,它指向关键帧动画时长 duration(dur),每一句歌词演唱所用的时间不同,并且,在人为调节播放进度后,调节到的那一句剩余播放时间又不等于该句记录的总用时,所以需要这个 time 变量。这个变量,在自然播放时,timeupdate 事件会给出 lrcAr 数组记录的时间:

if(mKey === j) showLrc(lrcAr); //②

lrcAr[2] 就是 lrcAr 数组 第 j 句 第 3 个成语的信息。

而当是手动,或因循环播放,调整了播放进度——

let calcKey = () => {
      for(j = 0; j < lrcAr.length; j ++) { //循环歌词数组
                if(aud.currentTime <= lrcAr) { //如果被调整后的播放进度时间小于等于第 j 个数组的记录时间
                        mKey = j - 1; //则给 mKey 变量赋值 :减 1 才是调节到的那一句
                        break; //条件成立跳出循环
                }
      }
      if(mKey <0) mKey = 0; //非手工调节时,如,播放完了重播,也同样触发进度被调节的行为,此时得出的 mKey 会小于 0
      let mtime = lrcAr - (aud.currentTime - lrcAr);
      showLrc(mtime);
}


在上面 calcKey() 函数里,最后的两句红色代码,计算手工调整进度后调节到的当句歌词所剩余的播放时间,然后调用 showLrc(time) 函数。这个函数除了干这个事,还有更核心的工作,就是计算调整进度之后,应播放的歌词是哪一句,找出来并赋值给 mKey 变量,完成这事,也需要循环一次 lrcAr 歌词数组,解释看上面的注释文本。

calcKey() 函数在什么时候执行?最省事的实现机制是利用 audio 的 seeked 事件,它和 seeking 事件一样,都会在播放进度被人为或自动重播时发生,seeked 在改变完成时触发,seeking在改变发生时触发,用哪一个都差不多,我选择 seeked:

aud.addEventListener('seeked', () => calcKey());

这是监听 seeked 事件,当手动调整了播放进度,或自然播放结束重新播放时,都会调用 calcKey() 函数,以保证 mKey 变量的值是我们所希望的。这样就解决了音频的播放进度被调控时,歌词着色能相对完好得以维持。

最后还需要考虑手动暂停/播放的歌词着色动画问题,这个简单,在设置按钮的函数里加入关键帧动画的播放/暂停即可:

let mState = () => aud.paused ? (btnplay.style.display = 'block', btnpause.style.display = 'none', lrc.style.animationPlayState = 'paused') : (btnplay.style.display = 'none', btnpause.style.display = 'block', lrc.style.animationPlayState = 'running');

貌似小小的功能,里面却充满着玄妙,希望大家能读懂。祝今日好心情!

梦缘 发表于 2022-9-17 09:47

代码的变化,看着有点头大。

梦缘 发表于 2022-9-17 09:49

这个和昨天的代码,变化不大吧!{:4_185:}

马黑黑 发表于 2022-9-17 09:57

梦缘 发表于 2022-9-17 09:49
这个和昨天的代码,变化不大吧!

不大的,有点

马黑黑 发表于 2022-9-17 09:59

梦缘 发表于 2022-9-17 09:47
代码的变化,看着有点头大。

实现机制发生变化,代码需要重新组织。以不变应万变这里行不通的{:5_106:}

红影 发表于 2022-9-17 10:17

“因为我们前面使用的渐变背景方向是 -90deg,所以,这里,令背景位置从 0% 走到 -100% 。”
这样就正好是两个颜色的替换吧,聪明{:4_199:}

红影 发表于 2022-9-17 10:19

“设计一个变量 mKey,该变量作为用以输出歌词的依据,它输出歌词前与 audio 的 timeupdate 事件配合完成判断即其他工作,它输出一句歌词后自动累进,为下一句歌词做准备。”
这个挺难的,不容易懂。

红影 发表于 2022-9-17 10:22

“如果 ① 当前播放时间与数组的时间信息匹配,则,②,如果当前 mKey 变量的值与循环到的匹配了歌句时间信息的 j 绝对相等(===,两个也成,==),就调用显示歌词函数 showLrc(参数) ,否则,③,在当前句演唱期间,跳出该次比对机制。”
这么多判断语句啊{:4_173:}

红影 发表于 2022-9-17 10:24

“这个函数除了跟这个事,还有更核心的工作,就是计算调整进度之后,应播放的歌词是哪一句,找出来并赋值给 mKey 变量,完成这是,也需要循环一次 lrcAr 歌词数组”

一个套一个,看到这里已经成功地绕迷糊了,我的单核脑袋啊,根本不够用了。

红影 发表于 2022-9-17 10:25

“利用 audio 的 seeked 事件,它和 seeking 事件一样,都会在播放进度被人为或自动重播时发生,seeked 在改变完成时触发,seeking在改变发生时触发,用哪一个都差不多,”

这哥俩都太陌生了,哪一个都不认识{:4_173:}

红影 发表于 2022-9-17 10:28

“这样就解决了音频的播放进度被调控时,歌词着色能相对完好得以维持。”
原来按下暂停时,歌词不会继续着色是这句来的{:4_204:}

红影 发表于 2022-9-17 10:29

“貌似小小的功能,里面却充满着玄妙”

玄妙貌似出来得多了点,被轰蒙了{:4_173:}

马黑黑 发表于 2022-9-17 10:42

红影 发表于 2022-9-17 10:29
“貌似小小的功能,里面却充满着玄妙”

玄妙貌似出来得多了点,被轰蒙了

更新的代码都解释了

马黑黑 发表于 2022-9-17 10:43

红影 发表于 2022-9-17 10:17
“因为我们前面使用的渐变背景方向是 -90deg,所以,这里,令背景位置从 0% 走到 -100% 。”
这样就正好是 ...

其实正想设计也是可以的,有空试试就知道

马黑黑 发表于 2022-9-17 10:44

红影 发表于 2022-9-17 10:19
“设计一个变量 mKey,该变量作为用以输出歌词的依据,它输出歌词前与 audio 的 timeupdate 事件配合完成判 ...

你一直对 for 循环不太明白

马黑黑 发表于 2022-9-17 10:44

红影 发表于 2022-9-17 10:22
“如果 ① 当前播放时间与数组的时间信息匹配,则,②,如果当前 mKey 变量的值与循环到的匹配了歌句时间信 ...

这些判断语句还算少的

马黑黑 发表于 2022-9-17 10:47

红影 发表于 2022-9-17 10:24
“这个函数除了跟这个事,还有更核心的工作,就是计算调整进度之后,应播放的歌词是哪一句,找出来并赋值给 ...

这个 calcKey 函数用于 seeked 事件被触发时

马黑黑 发表于 2022-9-17 10:49

红影 发表于 2022-9-17 10:25
“利用 audio 的 seeked 事件,它和 seeking 事件一样,都会在播放进度被人为或自动重播时发生,seeked 在 ...

原来我打算绕开 seeked 和 seeking,但那样的话,编程成本会大大增加,有它能够更好地把握 mKey

加林森 发表于 2022-9-17 10:49

学习、研究。

马黑黑 发表于 2022-9-17 10:50

红影 发表于 2022-9-17 10:28
“这样就解决了音频的播放进度被调控时,歌词着色能相对完好得以维持。”
原来按下暂停时,歌词不会继续着 ...

其实就是 animationPlayState,关键帧动画播放状态,以前经常用到它,比如旋转按钮如何停下、继续
页: [1] 2 3 4 5 6
查看完整版本: 请跟我来:进度可控+模拟逐字同步