用伪元素模拟歌词逐字同步演示
<style>#papa { left: -214px; width: 1024px; height: 640px; background: #ccc; box-shadow: 3px 3px 20px #000; display: grid; place-items: center; user-select: none; position: relative; z-index: 1; }
#mplayer { position: absolute; bottom: 0; width: 300px; height: 80px; display: grid; place-items: center; cursor: pointer; }
#mplayer:hover #btnwrap, #mplayer:hover #prog { transform: translateY(var(--yy)); }
#mplayer:hover #btnwrap { background: linear-gradient(to top right, red, green); border-radius: 50%; opacity: .75; }
#btnwrap, #prog { position: absolute; display: grid; place-items: center; transition: .5s; }
#btnwrap { --yy: -15px; width: 40px; height: 40px; transform: rotate(45deg); border: 1px solid tan; border-radius: 6px; opacity: .25; }
#btnplay { width: 20px; height: 20px; transform: translateX(3px); background: #eee; clip-path: polygon(0 0, 0% 100%, 100% 50%); }
#btnpause { width: 2px; height: 20px; border-style: solid; border-width: 0px 4px; border-color: transparent #eee; display: none; }
#prog { --yy: 20px; width: 300px; height: 16px; border-radius: 10px; background: linear-gradient(90deg, orange, red 100%, transparent 0); border: 1px solid gray; font: normal 14px / 16px sans-serif; color: #333; opacity: .75; }
#lrc { position: absolute; font: bold 2.6em sans-serif; color: rgba(255,255,255,.95); text-shadow: 1px 1px 1px #000; --motion: cover2; --tt: 1s; --state: running; }
#lrc::before { position: absolute; content: attr(data-lrc); width: 0; height: 100%; left: 0; top: 0; color: rgba(160,200,40,.8); overflow: hidden; white-space: nowrap; animation: var(--motion) var(--tt) linear forwards; animation-play-state: var(--state); }
@keyframes cover1 { from { width: 0; } to { width: 100%; } }
@keyframes cover2 { from { width: 0; } to { width: 100%; } }
</style>
<div id="papa">
<div id="lrc" data-lrc="花潮论坛lrc在线">花潮论坛lrc在线</div>
<div id="mplayer">
<div id="btnwrap"><span id="btnplay"></span><span id="btnpause"></span></div>
<div id="prog">00:00 | 00:00</div>
</div>
</div>
<script>
let mKey = 0, mSeek = false, mFlag = true, aud = new Audio();
let lrcAr = [
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
];
aud.src = 'http://www.kumeiwp.com/sub/filestores/2022/03/01/032cfd5b76ebc8b5277544e8bfc53d71.mp3';
aud.autoplay = true;
aud.loop = true;
btnwrap.onclick = () => aud.paused ? aud.play() : aud.pause();
prog.onclick = (e) => aud.currentTime = aud.duration * e.offsetX / prog.offsetWidth;
aud.addEventListener('pause', () => mState());
aud.addEventListener('play', () => mState());
aud.addEventListener('seeked', () => calcKey());
aud.addEventListener('timeupdate', () => {
prog.style.background= 'linear-gradient(90deg, green, red ' + aud.currentTime / aud.duration * 100 + '%, snow 0)';
prog.innerText = toMin(aud.currentTime) + ' | ' + toMin(aud.duration);
for(j=0; j<lrcAr.length; j++) {
if(aud.currentTime >= lrcAr) {
if(mKey === j) showLrc(lrcAr);
else continue;
}
}
});
let mState = () => aud.paused ? (btnplay.style.display = 'block', btnpause.style.display = 'none', lrc.style.setProperty('--state', 'paused')) : (btnplay.style.display = 'none', btnpause.style.display = 'block', lrc.style.setProperty('--state', 'running'));
let showLrc = (time) => {
let name = mFlag ? 'cover1' : 'cover2';
lrc.innerHTML = lrc.dataset.lrc = lrcAr;
lrc.style.setProperty('--motion', name);
lrc.style.setProperty('--tt', time + 's');
lrc.style.setProperty('--state', 'running');
mKey += 1;
mFlag = !mFlag;
};
let calcKey = () => {
for(j = 0; j < lrcAr.length; j ++) {
if(aud.currentTime <= lrcAr) {
mKey = j - 1;
break;
}
}
if(mKey <0) mKey = 0;
if(mKey > lrcAr.length - 1) mKey = lrcAr.length - 1;
let time = lrcAr - (aud.currentTime - lrcAr);
showLrc(time);
};
let toMin = (val)=> {
if (!val) return '00:00';
val = Math.floor(val);
let min = parseInt(val / 60), sec = parseFloat(val % 60);
if(min < 10) min = '0' + min;
if(sec < 10) sec = '0' + sec;
return min + ':' + sec;
};
</script>
代码
<style>
#papa { margin: auto; width: 1024px; height: 640px; background: #ccc; box-shadow: 3px 3px 20px #000; display: grid; place-items: center; user-select: none; position: relative; z-index: 1; }
#mplayer { position: absolute; bottom: 0; width: 300px; height: 80px; display: grid; place-items: center; cursor: pointer; }
#mplayer:hover #btnwrap, #mplayer:hover #prog { transform: translateY(var(--yy)); }
#mplayer:hover #btnwrap { background: linear-gradient(to top right, red, green); border-radius: 50%; opacity: .75; }
#btnwrap, #prog { position: absolute; display: grid; place-items: center; transition: .5s; }
#btnwrap { --yy: -15px; width: 40px; height: 40px; transform: rotate(45deg); border: 1px solid tan; border-radius: 6px; opacity: .25; }
#btnplay { width: 20px; height: 20px; transform: translateX(3px); background: #eee; clip-path: polygon(0 0, 0% 100%, 100% 50%); }
#btnpause { width: 2px; height: 20px; border-style: solid; border-width: 0px 4px; border-color: transparent #eee; display: none; }
#prog { --yy: 20px; width: 300px; height: 16px; border-radius: 10px; background: linear-gradient(90deg, orange, red 100%, transparent 0); border: 1px solid gray; font: normal 14px / 16px sans-serif; color: #333; opacity: .75; }
#lrc { position: absolute; font: bold 2.6em sans-serif; color: rgba(255,255,255,.95); text-shadow: 1px 1px 1px #000; --motion: cover2; --tt: 1s; --state: running; }
#lrc::before { position: absolute; content: attr(data-lrc); width: 0; height: 100%; left: 0; top: 0; color: rgba(160,200,40,.8); overflow: hidden; white-space: nowrap; animation: var(--motion) var(--tt) linear forwards; animation-play-state: var(--state); }
@keyframes cover1 { from { width: 0; } to { width: 100%; } }
@keyframes cover2 { from { width: 0; } to { width: 100%; } }
</style>
<div id="papa">
<div id="lrc" data-lrc="花潮论坛lrc在线">花潮论坛lrc在线</div>
<div id="mplayer">
<div id="btnwrap"><span id="btnplay"></span><span id="btnpause"></span></div>
<div id="prog">00:00 | 00:00</div>
</div>
</div>
<script>
let mKey = 0, mSeek = false, mFlag = true, aud = new Audio();
let lrcAr = [
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
];
aud.src = 'http://www.kumeiwp.com/sub/filestores/2022/03/01/032cfd5b76ebc8b5277544e8bfc53d71.mp3';
aud.autoplay = true;
aud.loop = true;
btnwrap.onclick = () => aud.paused ? aud.play() : aud.pause();
prog.onclick = (e) => aud.currentTime = aud.duration * e.offsetX / prog.offsetWidth;
aud.addEventListener('pause', () => mState());
aud.addEventListener('play', () => mState());
aud.addEventListener('seeked', () => calcKey());
aud.addEventListener('timeupdate', () => {
prog.style.background= 'linear-gradient(90deg, green, red ' + aud.currentTime / aud.duration * 100 + '%, snow 0)';
prog.innerText = toMin(aud.currentTime) + ' | ' + toMin(aud.duration);
for(j=0; j<lrcAr.length; j++) {
if(aud.currentTime >= lrcAr) {
if(mKey === j) showLrc(lrcAr);
else continue;
}
}
});
let mState = () => aud.paused ? (btnplay.style.display = 'block', btnpause.style.display = 'none', lrc.style.setProperty('--state', 'paused')) : (btnplay.style.display = 'none', btnpause.style.display = 'block', lrc.style.setProperty('--state', 'running'));
let showLrc = (time) => {
let name = mFlag ? 'cover1' : 'cover2';
lrc.innerHTML = lrc.dataset.lrc = lrcAr;
lrc.style.setProperty('--motion', name);
lrc.style.setProperty('--tt', time + 's');
lrc.style.setProperty('--state', 'running');
mKey += 1;
mFlag = !mFlag;
};
let calcKey = () => {
for(j = 0; j < lrcAr.length; j ++) {
if(aud.currentTime <= lrcAr) {
mKey = j - 1;
break;
}
}
if(mKey <0) mKey = 0;
if(mKey > lrcAr.length - 1) mKey = lrcAr.length - 1;
let time = lrcAr - (aud.currentTime - lrcAr);
showLrc(time);
};
let toMin = (val)=> {
if (!val) return '00:00';
val = Math.floor(val);
let min = parseInt(val / 60), sec = parseFloat(val % 60);
if(min < 10) min = '0' + min;
if(sec < 10) sec = '0' + sec;
return min + ':' + sec;
};
</script>
本帖最后由 马黑黑 于 2022-9-26 20:50 编辑
CSS和HTML的变化:
DOM节点,显示lrc歌词的仅一行:
<div id="lrc" data-lrc="花潮论坛lrc在线">花潮论坛lrc在线</div>
这一行,用 data-lrc 引入与 id="lrc" 的 div 盒子一样的文本,它源自 CSS 伪元素中定义的类似于变量的东东:
content: attr(data-lrc);
data- 是书写规范,后面的部分自定义。JS可以读写它,基于父元素:
let str =element.dataset.lrc; //读取
element.dataset.lrc = '(。・∀・)ノ゙嗨'; //写入
(element 是元素的操作句柄)
这样,JS 就可以动态地给 lrc 盒子的 data-lrc 赋值,实现伪元素的文本与盒子显示的相一致,然后为元素设置不同的前景色,再按指定时间去改变自己的宽度,陆续覆盖父元素,达到文本逐一被染色的效果。
CSS部分,分lrc盒子和它的伪元素:
#lrc {
position: absolute;
font: bold 2.6em sans-serif;
color: rgba(255,255,255,.95);
text-shadow: 1px 1px 1px #000;
--motion: cover2; /* 动画名称 */
--tt: 1s; /* 动画运行周期时长 */
--state: running; /* 动画运行状态 */
}
#lrc::before {
position: absolute;
content: attr(data-lrc);
width: 0;
height: 100%;
left: 0;
top: 0;
color: rgba(160,200,40,.8);
overflow: hidden;
white-space: nowrap;
animation: var(--motion) var(--tt) linear forwards;
animation-play-state: var(--state);
}
#lrc 盒子,设定了三个变量,上面的代码都做了注释。伪元素 ::before 对应使用这三个变量,都是基于 animation 属性的。这些变量的动态赋值,将由 JS 根据播放进度和其他状态的改变而进行。
下楼讲JS部分。
本帖最后由 马黑黑 于 2022-9-26 21:03 编辑
JS的变动并不多,共两处:
一个是 showLr() 函数——
let showLrc = (time) => {
let name = mFlag ? 'cover1' : 'cover2'; //变换关键帧动画名称
lrc.innerHTML = lrc.dataset.lrc = lrcAr; //lrc 盒子和其伪元素的 attr 值同时赋值
lrc.style.setProperty('--motion', name); //给 lrc 盒子设定动画名称
lrc.style.setProperty('--tt', time + 's'); //给 lrc 盒子设定 --tt 变量 :动画运行周期时长
lrc.style.setProperty('--state', 'running'); //给 lrc 盒子 --state 变量 :运行状态
mKey += 1;
mFlag = !mFlag;
};
红色部分的代码,都是操纵 lrc 盒子,通过它,将变量值传送给伪元素。
二个变化是按钮状态设置函数里:
let mState = () => aud.paused ? (btnplay.style.display = 'block', btnpause.style.display = 'none', lrc.style.setProperty('--state', 'paused')) : (btnplay.style.display = 'none', btnpause.style.display = 'block', lrc.style.setProperty('--state', 'running'));
这个函数是一个三元运算,其意为:如果 audio 的暂停状态为真,则显示播放按钮、隐藏暂停按钮,并令 lrc 盒子的 --state 变量为暂停(paused);反之,隐藏播放按钮、显示暂停按钮,并令 lrc 盒子的 --state 变量为运行(running)。--state 变量的赋值依然是针对 lrc 盒子,通过它传送给它的伪元素。
这个很成功。第三种歌词逐字同步华丽登场,给黑黑点赞{:4_199:} 我好像回快了,不知道黑黑还需要占位不{:4_173:} 红影 发表于 2022-9-26 20:00
我好像回快了,不知道黑黑还需要占位不
不用了,占多了 红影 发表于 2022-9-26 19:59
这个很成功。第三种歌词逐字同步华丽登场,给黑黑点赞
伪元素就酱紫被降服了{:4_173:} 又来看一遍,将需要的控制元素全部用变量代替,并用变量间数值的传递,来达到操控伪元素的目的。
想法很聪明,真的实现需要考虑很得很细致。我看的人都蒙,设计的就更不容易了。黑黑厉害{:4_187:} 马黑黑 发表于 2022-9-26 20:24
伪元素就酱紫被降服了
以变量为桥梁,联通你我他。{:4_173:}不过说起来容易,做起来很难,反正我没看明白{:4_173:} 红影 发表于 2022-9-27 09:12
以变量为桥梁,联通你我他。不过说起来容易,做起来很难,反正我没看明白
CSS变量起到红娘作用,将无法会面的伪元素菇凉和JS小伙拉到天桥上约了 红影 发表于 2022-9-27 09:03
又来看一遍,将需要的控制元素全部用变量代替,并用变量间数值的传递,来达到操控伪元素的目的。
想法很聪 ...
这里就三个变量的传递,不复杂 马黑黑 发表于 2022-9-27 12:11
CSS变量起到红娘作用,将无法会面的伪元素菇凉和JS小伙拉到天桥上约了
CSS变量是七夕的鹊桥{:4_189:} 马黑黑 发表于 2022-9-27 12:11
这里就三个变量的传递,不复杂
黑黑厉害,到底还是弄出来伪元素的逐字同步了{:4_199:} 红影 发表于 2022-9-27 20:46
黑黑厉害,到底还是弄出来伪元素的逐字同步了
想过的,就不轻易放弃,真不行那也只得放弃 红影 发表于 2022-9-27 20:45
CSS变量是七夕的鹊桥
嗯{:4_181:} 马黑黑 发表于 2022-9-27 21:13
想过的,就不轻易放弃,真不行那也只得放弃
还好最后不是扔掉,而是又得到一个同步方式{:4_173:} 红影 发表于 2022-9-27 22:01
还好最后不是扔掉,而是又得到一个同步方式
杠上了至少会努力一阵子的 马黑黑 发表于 2022-9-27 22:41
杠上了至少会努力一阵子的
努力了就有成果,黑黑太厉害了{:4_199:} 红影 发表于 2022-9-28 11:15
努力了就有成果,黑黑太厉害了
没有成果的情况也大把存在的