本系列教程的上一讲,《range进度条播放器+LRC歌词同步教程(一)》,我们完成了LRC歌词显示的界面+歌词同步关键帧动画,但它还不能投入工作,我们有很多问题要解决。本讲要处理的问题是一个单一的问题:如何反复运行CSS关键帧动画。
同一个CSS关键帧动画,运行完一次之后再去运行,除非 animation 设为永动(infinite),否则,相同的元素再去运行同名动画无效,纯JS的传统解决方案是先清除动画,再命令元素运行动画,而且不能清除后马上运行,中间需要一个等待时间,为此需要一个 setTimeout 定时器,用于负责在动画运行结束时清除动画属性或动画名称,不能提前(否则动画还没执行完毕就已关闭)也不能太拖后(否则影响下一个动画的运行),时间要掐的恰到好处。然后呢,其它机制检测到应该运行下一个动画了,再加入动画属性或动画名称给元素。这是可行的办法,不过它涉及到的编程开销偏大也略显复杂,所以我个人倾向于使用另外一个实现手段:设计两个内容相同的关键帧动画,运行时让元素在两个动画间来回切换使用,永不重复,这是我们之前一直使用的lrc歌词插件使用的方法。
看看在上一讲基础上改造过的CSS基于动画的核心代码:
#lrc {} /* 主元素代码略 */
#lrc::before {
/* 其它代码略 */
clip-path: inset(0 100% 0 0);
animation: var(--ani) 4s linear forwards;
}
@keyframes lrcGo0 { to { clip-path: inset(0 0 0 0); } }
@keyframes lrcGo1 { to { clip-path: inset(0 0 0 0); } }
以上,我们设计了两个名称不同、内容一样的关键帧动画,它们都极其简短,因为伪元素内的 clip-path 属性设定为右边全部裁切(100%),关键帧动画只需提供终点裁切点(0)就行。完美主义者或强迫症患者——二者有时没啥区别——会担心关键帧动画没有起始点不安全,若此,加上 from 也不是什么难事,只需把伪元素里的 clip-path 语句塞进 from 语句即可。
接下来,我们试着用JS的 setInterval 定时器来模拟音乐循环播放永不停歇的样纸,里面我们设置一个自增变量 add,当 add 值被 5 整除,就让 lrc 伪元素运行动画,动画每一次运行4秒钟(这是animation属性规定好的,将来这个运行时长为不确定时间)。先看代码:
var add = 0, lastIdx = 0, aniAr = ['lrcGo0','lrcGo1'];
setInterval( () => {
if (add % 5 == 0) {
document.title = add;
lrc.style.setProperty('--ani', aniAr[lastIdx]);
lastIdx= lastIdx === 0 ? 1 : 0;
}
add = (add + 1) % 20;
}, 1000);
首先声明几个变量,add 用以计时;lastIdx 是动画索引依据;aniAr 是装载动画名称的数组。接着,让 setInterval 定时器投入工作,红色花括号 {} 里是 setInterval 要执行的代码,每隔一秒执行一次:先用一个条件语句检测 add 的值是否被5整除,如果被整除,则令页面标题(浏览器标签的内容)显示当前 add 值(观察之用),用元素的 style.setProperty() 方法设置lrc元素的CSS变量 --ani 的值,其值从动画名称数组中获取,然后用一个三元运算处理 lastIdx 索引值,让它在 0 和 1 之间不断更换。定时器最后令 add 变量自增,但其值总是受到 20 的干预、到了 19 就会从头再来,这样,动画一个巡回执行4次,对应的 add 值分别为 0、5、10、15,15到20之间刚好留给一轮中的最后一个动画足够的运行时间而后从头继续运行。
本节讲到这里,主要试验一下关键帧动画反复调用功能,现在可以给出完整代码示例了。以下代码,我们给 lrc 标签找了一个爸爸,让它有足够的施展空间。运行后会发现,第一节我们设计的lrc的界面有了特定舞台背景后愈发漂亮迷人——
<style>
#papa {
margin: auto;
width: 800px;
height: 360px;
background: linear-gradient(tan,gray);
box-shadow: 3px 3px 20px #000;
position: relative;
display: grid;
place-items: center;
}
#lrc {
position: absolute;
top: 10px;
font: bold 2.4em sans-serif;
color: lightblue;
text-shadow: 1px 1px 1px rgba(0,0,0,.45);
}
#lrc::before {
position: absolute;
content: attr(data-lrc);
width: 100%;
height: 100%;
color: transparent;
background: linear-gradient(rgba(250,0,0,.7),rgba(0,0,180,.8));
background-clip: text;
-webkit-background-clip: text;
clip-path: inset(0 100% 0 0);
animation: var(--ani) 2s linear forwards;
border-bottom: 1px solid navy;
}
@keyframes lrcGo0 { to { clip-path: inset(0 0 0 0); } }
@keyframes lrcGo1 { to { clip-path: inset(0 0 0 0); } }
</style>
<div id="papa">
<div id="lrc" data-lrc="HuaChao LRC">HuaChao LRC</div>
</div>
<script>
var add = 0, lastIdx = 0, aniAr = ['lrcGo0','lrcGo1'];
setInterval( () => {
if (add % 5 == 0) {
document.title = add;
lrc.style.setProperty('--ani', aniAr[lastIdx]);
lastIdx= lastIdx === 0 ? 1 : 0;
}
add = (add + 1) % 20;
}
, 1000);
</script>
代码就不在这里演示了,可到 pencil code 运行,或存为本地html文档后运行。