她是水
<style>#papa { left: -214px; width: 1024px; height: 640px; background: gray url('https://638183.freep.cn/638183/Pic/81/100s.jpg') no-repeat center/cover; box-shadow: 3px 3px 20px #000; overflow: hidden; user-select: none; display: grid; place-items: center; position: relative; }
/* svg按钮↓*/
#mplayer { position: absolute; bottom: 0; }
#btnpause { display: none; }
#btnwrap, #btnplay, #btnpause { cursor: pointer; }
#btnplay:hover + btnwrap, #btnpause:hover + btnwrap { fill: gold; }
#btnwrap:hover { fill: gold; }
#btnwrap:hover + #btnplay + #btnpause { fill: red;}
#tmsg { dominant-baseline: middle; fill: snow; }
#track { shape-rendering: crispEdges; cursor: pointer; }
/* svg 歌词及动态效果 ↓ */
#lrc { position: absolute; width: 1000px; height: 80px; top: 10px; }
.text { font: bold 3em sans-serif; text-anchor: middle; dominant-baseline: middle; letter-spacing: 4px; fill: none; stroke-width: 1.5px; stroke-dasharray: 90 310; animation: stroke 6s infinite linear; }
.text-1{ stroke: snow; text-shadow: 0 0 5px red; animation-delay: -1.5s; }
.text-2{ stroke: orange; text-shadow: 0 0 5px green; animation-delay: -3s; }
.text-3{ stroke: gold; text-shadow: 0 0 5px blue; animation-delay: -4.5s; }
.text-4{ stroke: tomato; text-shadow: 0 0 5px purple; animation-delay: -6s; }
@keyframes stroke { to { stroke-dashoffset: -400; } }
</style>
<div id="papa">
<svg id="lrc">
<text x="50%" y="50%" class="text text-1">lrc歌词</text>
<text x="50%" y="50%" class="text text-2">lrc歌词</text>
<text x="50%" y="50%" class="text text-3">lrc歌词</text>
<text x="50%" y="50%" class="text text-4">lrc歌词</text>
</svg>
<svg id="mplayer" width="400" height="60">
<circle id="btnwrap" cx="20" cy="30" r="15" fill="olive" />
<polygon id="btnplay" points="15 20, 15 40, 30 30" fill="snow" />
<g id="btnpause" fill="snow">
<rect x= "15" y="20" width="3" height="20" />
<rect x="22" y="20" width="3" height="20" />
</g>
<g id="track"stroke-width="2">
<line x1="45" y1="30" x2="245" y2="30" stroke="transparent" stroke-width = "20" />
<line x1="45" y1="30" x2="245" y2="30" stroke="snow" />
<line id="prog" x1="45" y1="30" x2="245" y2="30" stroke="red" stroke-dasharray="200" stroke-dashoffset="200" />
</g>
<text id="tmsg" x="260" y="30">00:00 | 00:00</text>
</svg>
</div>
<script>
let lrcAr = [
['0.00','她是水 - 黄龄'],
['21.07','都说水中月不比那镜中花'],
['28.07','伸手捧一掬空掌心无他不见月牙'],
['34.02','量 一寸 一尺 一丈 无极无量'],
['38.02','我叹 我想 我情不自禁观想观想'],
['42.08','舞起霓裳合十手掌 她在身旁'],
['50.08','她说爱在水中 一笑一场梦'],
['54.08','我与谁相拥'],
['57.08','淌过身旁的人 呢喃自称'],
['61.06','吝啬这湿吻'],
['64.06','太美我不敢看 窥探这一半'],
['68.04','多情又如何'],
['71.04','低声唱啊唱啊 尝不尽这牵挂'],
['76.09','涉险为你蒸发'],
['93.07','她似水中月唤我去温柔乡'],
['100.06','我似梦一场唏嘘这光年只去不往'],
['106.02','春 了夏 了秋 冬雪悄悄落下'],
['110.02','冻结 埋藏 冰下的誓言是真吗'],
['114.03','是假的吗'],
['116.03','那些话 融化只不过是刹那'],
['121.03','她忘了回答'],
['122.09','她说爱在水中 一笑一场梦'],
['126.08','我与谁相拥'],
['129.08','淌过身旁的人 呢喃自称'],
['133.06','吝啬这湿吻'],
['136.07','太美我不敢看 窥探这一半'],
['140.04','多情又如何'],
['143.04','低声唱啊唱啊 尝不尽这牵挂'],
['148.09','涉险为你蒸发'],
['151.04','嘿呀 嘿呀 怎么让她留下'],
['158.05','嘿呀 嘿呀 怎么把她遗忘'],
['163.09','她在水中看江南繁华落尽'],
['166.00','曲中叹 伊人魂影若泣 淡然从容'],
['168.06','在夜的朦胧里赏月色落寂'],
['171.01','她说相濡以沫 分秒必争'],
['173.00','承君此诺 必守一生'],
['174.06','在烟雨笼罩水中月'],
['176.05','涟漪荡漾落松叶'],
['181.02','她说爱在水中 一笑一场梦'],
['185.01','我与谁相拥'],
['188.00','淌过身旁的人 呢喃自称'],
['191.08','吝啬这湿吻'],
['194.08','太美我不敢看 窥探这一半'],
['198.07','多情又如何'],
['201.07','低声唱啊唱啊 尝不尽这牵挂'],
['207.01','涉险为你蒸发']
];
let aud = new Audio(), lw = prog.getTotalLength(), txtAr = document.querySelectorAll('.text');
aud.src = 'https://music.163.com/song/media/outer/url?id=1975659253.mp3';
aud.autoplay = true;
aud.loop = true;
track.onclick = (e) => aud.currentTime = aud.duration * (e.offsetX - prog.getAttribute('x1')) / lw;
btnwrap.onclick = btnpause.onclick = btnplay.onclick = () => aud.paused ? aud.play() : aud.pause();
aud.addEventListener('playing', ()=> btnstate());
aud.addEventListener('pause', ()=> btnstate());
aud.addEventListener('timeupdate', () => {
prog.style.strokeDashoffset = lw - lw * aud.currentTime / aud.duration + 'px';
tmsg.textContent = toMin(aud.currentTime) + ' | ' + toMin(aud.duration);
for(j = 0; j < lrcAr.length;j ++) {
if(aud.currentTime >= lrcAr) {
for(ele of txtAr) ele.textContent = lrcAr;
}
}
});
let btnstate = () => aud.paused ? (btnplay.style.display = 'block', btnpause.style.display = 'none') : (btnplay.style.display = 'none', btnpause.style.display = 'block');
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 { left: -214px; width: 1024px; height: 640px; background: gray url('https://638183.freep.cn/638183/Pic/81/100s.jpg') no-repeat center/cover; box-shadow: 3px 3px 20px #000; overflow: hidden; user-select: none; display: grid; place-items: center; position: relative; }
/* svg按钮↓*/
#mplayer { position: absolute; bottom: 0; }
#btnpause { display: none; }
#btnwrap, #btnplay, #btnpause { cursor: pointer; }
#btnplay:hover + btnwrap, #btnpause:hover + btnwrap { fill: gold; }
#btnwrap:hover { fill: gold; }
#btnwrap:hover + #btnplay + #btnpause { fill: red;}
#tmsg { dominant-baseline: middle; fill: snow; }
#track { shape-rendering: crispEdges; cursor: pointer; }
/* svg 歌词及动态效果 ↓ */
#lrc { position: absolute; width: 1000px; height: 80px; top: 10px; }
.text { font: bold 3em sans-serif; text-anchor: middle; dominant-baseline: middle; letter-spacing: 4px; fill: none; stroke-width: 1.5px; stroke-dasharray: 90 310; animation: stroke 6s infinite linear; }
.text-1{ stroke: snow; text-shadow: 0 0 5px red; animation-delay: -1.5s; }
.text-2{ stroke: orange; text-shadow: 0 0 5px green; animation-delay: -3s; }
.text-3{ stroke: gold; text-shadow: 0 0 5px blue; animation-delay: -4.5s; }
.text-4{ stroke: tomato; text-shadow: 0 0 5px purple; animation-delay: -6s; }
@keyframes stroke { to { stroke-dashoffset: -400; } }
</style>
<div id="papa">
<svg id="lrc">
<text x="50%" y="50%" class="text text-1">lrc歌词</text>
<text x="50%" y="50%" class="text text-2">lrc歌词</text>
<text x="50%" y="50%" class="text text-3">lrc歌词</text>
<text x="50%" y="50%" class="text text-4">lrc歌词</text>
</svg>
<svg id="mplayer" width="400" height="60">
<circle id="btnwrap" cx="20" cy="30" r="15" fill="olive" />
<polygon id="btnplay" points="15 20, 15 40, 30 30" fill="snow" />
<g id="btnpause" fill="snow">
<rect x= "15" y="20" width="3" height="20" />
<rect x="22" y="20" width="3" height="20" />
</g>
<g id="track"stroke-width="2">
<line x1="45" y1="30" x2="245" y2="30" stroke="transparent" stroke-width = "20" />
<line x1="45" y1="30" x2="245" y2="30" stroke="snow" />
<line id="prog" x1="45" y1="30" x2="245" y2="30" stroke="red" stroke-dasharray="200" stroke-dashoffset="200" />
</g>
<text id="tmsg" x="260" y="30">00:00 | 00:00</text>
</svg>
</div>
<script>
let lrcAr = [
['0.00','她是水 - 黄龄'],
['21.07','都说水中月不比那镜中花'],
['28.07','伸手捧一掬空掌心无他不见月牙'],
['34.02','量 一寸 一尺 一丈 无极无量'],
['38.02','我叹 我想 我情不自禁观想观想'],
['42.08','舞起霓裳合十手掌 她在身旁'],
['50.08','她说爱在水中 一笑一场梦'],
['54.08','我与谁相拥'],
['57.08','淌过身旁的人 呢喃自称'],
['61.06','吝啬这湿吻'],
['64.06','太美我不敢看 窥探这一半'],
['68.04','多情又如何'],
['71.04','低声唱啊唱啊 尝不尽这牵挂'],
['76.09','涉险为你蒸发'],
['93.07','她似水中月唤我去温柔乡'],
['100.06','我似梦一场唏嘘这光年只去不往'],
['106.02','春 了夏 了秋 冬雪悄悄落下'],
['110.02','冻结 埋藏 冰下的誓言是真吗'],
['114.03','是假的吗'],
['116.03','那些话 融化只不过是刹那'],
['121.03','她忘了回答'],
['122.09','她说爱在水中 一笑一场梦'],
['126.08','我与谁相拥'],
['129.08','淌过身旁的人 呢喃自称'],
['133.06','吝啬这湿吻'],
['136.07','太美我不敢看 窥探这一半'],
['140.04','多情又如何'],
['143.04','低声唱啊唱啊 尝不尽这牵挂'],
['148.09','涉险为你蒸发'],
['151.04','嘿呀 嘿呀 怎么让她留下'],
['158.05','嘿呀 嘿呀 怎么把她遗忘'],
['163.09','她在水中看江南繁华落尽'],
['166.00','曲中叹 伊人魂影若泣 淡然从容'],
['168.06','在夜的朦胧里赏月色落寂'],
['171.01','她说相濡以沫 分秒必争'],
['173.00','承君此诺 必守一生'],
['174.06','在烟雨笼罩水中月'],
['176.05','涟漪荡漾落松叶'],
['181.02','她说爱在水中 一笑一场梦'],
['185.01','我与谁相拥'],
['188.00','淌过身旁的人 呢喃自称'],
['191.08','吝啬这湿吻'],
['194.08','太美我不敢看 窥探这一半'],
['198.07','多情又如何'],
['201.07','低声唱啊唱啊 尝不尽这牵挂'],
['207.01','涉险为你蒸发']
];
let aud = new Audio(), lw = prog.getTotalLength(), txtAr = document.querySelectorAll('.text');
aud.src = 'https://music.163.com/song/media/outer/url?id=1975659253.mp3';
aud.autoplay = true;
aud.loop = true;
track.onclick = (e) => aud.currentTime = aud.duration * (e.offsetX - prog.getAttribute('x1')) / lw;
btnwrap.onclick = btnpause.onclick = btnplay.onclick = () => aud.paused ? aud.play() : aud.pause();
aud.addEventListener('playing', ()=> btnstate());
aud.addEventListener('pause', ()=> btnstate());
aud.addEventListener('timeupdate', () => {
prog.style.strokeDashoffset = lw - lw * aud.currentTime / aud.duration + 'px';
tmsg.textContent = toMin(aud.currentTime) + ' | ' + toMin(aud.duration);
for(j = 0; j < lrcAr.length;j ++) {
if(aud.currentTime >= lrcAr) {
for(ele of txtAr) ele.textContent = lrcAr;
}
}
});
let btnstate = () => aud.paused ? (btnplay.style.display = 'block', btnpause.style.display = 'none') : (btnplay.style.display = 'none', btnpause.style.display = 'block');
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>
首次使用 svg 开发播放器。播放器采用控制器与lrc歌词分离的实现形式,各用一个 svg 标签做成。
CSS可以有限设定 svg 内部元素,结合 svg 丰富的属性,做按钮、进度条、文本显示绰绰有余。
进度条的实现,歌词的动态描边效果,都是利用 svg 的虚线线段偏移属性完成,该属性名称为 stroke-dashoffset,JS表示为 strokeDashoffset,它会将线段(stroke-dasharray)偏移从而隐藏,通过设置 offset 值,恢复部分乃至全部显现,达到进度条与文本描边的动态效果。进度条使用JS控制,文本描边用CSS关键帧动画实现。
svg 对多数人来说是个陌生的领域,它是一种图形文件格式,英文全称为 Scalable Vector Graphics,意为可缩放的矢量图形。svg 可以直接当成像 div 一样的 HTML 标签在 web 页中使用,不同的是,它有自己一整套规范,使用者需要熟知相关知识才能应用。 等会来学习。 全部用SVG实现的播放器和歌词同步,这个太赞了,黑黑好棒{:4_199:} 好奇妙的歌词效果。这个是stroke文字,不知道fill的效果是怎样的{:4_173:} .text { font: bold 3em sans-serif; text-anchor: middle; dominant-baseline: middle; letter-spacing: 4px; fill: none; stroke-width: 1.5px; stroke-dasharray: 90 310; animation: stroke 6s infinite linear; }
这句里面有好多没见过的命令。 红影 发表于 2022-9-4 09:25
.text { font: bold 3em sans-serif; text-anchor: middle; dominant-baseline: middle; letter-spacing: 4 ...
svg 文本水平居中:text-anchor: middle;
svg 文本垂直居中:dominant-baseline: middle;
svg虚线线段(数组):stroke-dasharray: 90 310; 表示:90为一节线段,310为线段间距
红影 发表于 2022-9-4 09:17
全部用SVG实现的播放器和歌词同步,这个太赞了,黑黑好棒
原理在那里,只要了解了相关指令,组织一下代码就行 红影 发表于 2022-9-4 09:19
好奇妙的歌词效果。这个是stroke文字,不知道fill的效果是怎样的
fill 没有相应的 dash 指令 这个字体效果漂亮 黑黑先生真是才华横溢啊! 学习代码,感谢老师的精彩分享!{:4_187:} 马黑黑 发表于 2022-9-4 07:55
首次使用 svg 开发播放器。播放器采用控制器与lrc歌词分离的实现形式,各用一个 svg 标签做成。
CSS可以 ...
感觉有点深奥了 黑黑精彩不断{:4_178:} 来学习! 绿叶清舟 发表于 2022-9-4 11:03
这个字体效果漂亮
还行 小辣椒 发表于 2022-9-4 11:50
黑黑精彩不断
谢顶 小辣椒 发表于 2022-9-4 11:49
感觉有点深奥了
你可能一点都木有接触过 svg