马黑黑 发表于 2022-9-26 19:38

用伪元素模拟歌词逐字同步演示

<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>

马黑黑 发表于 2022-9-26 19:39

代码
<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 19:39

本帖最后由 马黑黑 于 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 19:39

本帖最后由 马黑黑 于 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 盒子,通过它传送给它的伪元素。

红影 发表于 2022-9-26 19:59

这个很成功。第三种歌词逐字同步华丽登场,给黑黑点赞{:4_199:}

红影 发表于 2022-9-26 20:00

我好像回快了,不知道黑黑还需要占位不{:4_173:}

马黑黑 发表于 2022-9-26 20:23

红影 发表于 2022-9-26 20:00
我好像回快了,不知道黑黑还需要占位不

不用了,占多了

马黑黑 发表于 2022-9-26 20:24

红影 发表于 2022-9-26 19:59
这个很成功。第三种歌词逐字同步华丽登场,给黑黑点赞

伪元素就酱紫被降服了{:4_173:}

红影 发表于 2022-9-27 09:03

又来看一遍,将需要的控制元素全部用变量代替,并用变量间数值的传递,来达到操控伪元素的目的。
想法很聪明,真的实现需要考虑很得很细致。我看的人都蒙,设计的就更不容易了。黑黑厉害{:4_187:}

红影 发表于 2022-9-27 09:12

马黑黑 发表于 2022-9-26 20:24
伪元素就酱紫被降服了

以变量为桥梁,联通你我他。{:4_173:}不过说起来容易,做起来很难,反正我没看明白{:4_173:}

马黑黑 发表于 2022-9-27 12:11

红影 发表于 2022-9-27 09:12
以变量为桥梁,联通你我他。不过说起来容易,做起来很难,反正我没看明白

CSS变量起到红娘作用,将无法会面的伪元素菇凉和JS小伙拉到天桥上约了

马黑黑 发表于 2022-9-27 12:11

红影 发表于 2022-9-27 09:03
又来看一遍,将需要的控制元素全部用变量代替,并用变量间数值的传递,来达到操控伪元素的目的。
想法很聪 ...

这里就三个变量的传递,不复杂

红影 发表于 2022-9-27 20:45

马黑黑 发表于 2022-9-27 12:11
CSS变量起到红娘作用,将无法会面的伪元素菇凉和JS小伙拉到天桥上约了

CSS变量是七夕的鹊桥{:4_189:}

红影 发表于 2022-9-27 20:46

马黑黑 发表于 2022-9-27 12:11
这里就三个变量的传递,不复杂

黑黑厉害,到底还是弄出来伪元素的逐字同步了{:4_199:}

马黑黑 发表于 2022-9-27 21:13

红影 发表于 2022-9-27 20:46
黑黑厉害,到底还是弄出来伪元素的逐字同步了

想过的,就不轻易放弃,真不行那也只得放弃

马黑黑 发表于 2022-9-27 21:13

红影 发表于 2022-9-27 20:45
CSS变量是七夕的鹊桥

嗯{:4_181:}

红影 发表于 2022-9-27 22:01

马黑黑 发表于 2022-9-27 21:13
想过的,就不轻易放弃,真不行那也只得放弃

还好最后不是扔掉,而是又得到一个同步方式{:4_173:}

马黑黑 发表于 2022-9-27 22:41

红影 发表于 2022-9-27 22:01
还好最后不是扔掉,而是又得到一个同步方式

杠上了至少会努力一阵子的

红影 发表于 2022-9-28 11:15

马黑黑 发表于 2022-9-27 22:41
杠上了至少会努力一阵子的

努力了就有成果,黑黑太厉害了{:4_199:}

马黑黑 发表于 2022-9-28 12:10

红影 发表于 2022-9-28 11:15
努力了就有成果,黑黑太厉害了

没有成果的情况也大把存在的
页: [1] 2 3 4
查看完整版本: 用伪元素模拟歌词逐字同步演示