HIGH歌
<style>#papa { left: -214px; width: 1024px; height: 640px; background: #666 url('https://638183.freep.cn/638183/t22/hl/h59.jpg') no-repeat center/cover; box-shadow: 3px 3px 20px #000; user-select: none; position: relative; z-index: 1;}
#mplayer { position: absolute; padding: 0; margin: 20px; width: 120px; height: 120px; border-radius: 50%; overflow: hidden; }
#prog, #track { stroke: url(#gradient); }
#lrc { position: absolute; display: block; left: 20px; bottom: 0; }
#lrctxt { dominant-baseline: middle; fill: url(#gradient); font: bold 2em sans-serif; text-shadow: 0 4px 0 #000; letter-spacing: 2px; }
</style>
<div id="papa">
<div id="mplayer">
<svg width="100%" height="100%" shape-rendering="geometricPrecision">
<g transform="rotate(-90, 60, 60)">
<circle id="track" cx="60" cy="60" r="55" fill="none" stroke="rgba(255,255,255,.5)" stroke-width="10" stroke-dasharray="2" stroke-opacity="0.45" />
<circle id="prog" cx="60" cy="60" r="55" fill="none" stroke="red" stroke-width="10" />
</g>
<text fill="orange">
<tspan id="cur" x="40" y="55">00:00</tspan>
<tspan id="dur" x="40" y="75">00:00</tspan>
</text>
</svg>
</div>
<svg id="lrc" width="400" height="50">
<defs>
<linearGradient id="gradient" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="green"/>
<stop offset="50%" stop-color="orange"/>
<stop offset="100%" stop-color="red"/>
</linearGradient>
</defs>
<text id="lrctxt" x="0" y="25">黄龄 - HIGH歌</text>
</svg>
</div>
<script>
let lrcAr = [
['0.00','黄龄 - High歌'],
['6.00','作词/作曲:常石磊'],
['9.00','歌词编辑:孟德良'],
['17.04','Mountain top'],
['19.05','就跟着一起来'],
['21.09','没有什么 阻挡着未来'],
['26.04','Deeping night'],
['28.05','就你和我的爱'],
['30.07','没有什么 阻挡着未来'],
['35.03','Mountain top'],
['37.03','就跟着一起来'],
['39.06','没有什么 阻挡着未来'],
['44.01','Deeping night'],
['46.01','就你和我的爱'],
['48.05','没有什么 阻挡着未来'],
['53.00','Mountain top'],
['54.09','就跟着一起来'],
['57.04','没有什么 阻挡着未来'],
['61.10','Deeping night'],
['63.09','就你和我的爱'],
['66.03','没有什么 阻挡着未来'],
['70.08','Yi Yi Yi 你不在 我不在'],
['75.03','Yi Yi Yi 谁还会在'],
['79.07','Yi Yi Yi 你不在 我不在'],
['84.02','Yi Yi Yi 谁还会在'],
['124.02','Mountain top'],
['126.00','就跟着一起来'],
['128.05','没有什么 阻挡着未来'],
['132.10','Deeping night'],
['135.00','就你和我的爱'],
['137.04','没有什么 阻挡着未来'],
['141.09','Yi Yi Yi 你不在 我不在'],
['146.04','Yi Yi Yi 谁还会在'],
['150.07','Yi Yi Yi 你不在 我不在'],
['155.03','Yi Yi Yi 谁还会在'],
['204.03','Mountain top'],
['208.04','就一起来'],
['213.00','Mountain top'],
['217.03','就一起来'],
['221.08','Mountain top'],
['225.10','就一起来'],
['230.07','Mountain top'],
['235.01','就一起来']
];
let aud = new Audio();
aud.src = 'https://music.163.com/song/media/outer/url?id=238634.mp3';
aud.autoplay = true;
aud.loop = true;
//设置圆环进度偏移
let girth = prog.getTotalLength();
prog.style.strokeDasharray = prog.style.strokeDashoffset = girth+ 'px';
//圆环鼠标经过
mplayer.onmousemove = (e) => {
if (isHover(e.offsetX, e.offsetY))mplayer.style.cursor = 'pointer';
}
//圆环点击
mplayer.onclick = (e) => {
if (isHover(e.offsetX, e.offsetY)) { //轨道
let deg = Math.atan2(e.offsetY - 60, e.offsetX - 60) * 180 / Math.PI;
deg += (e.offsetX < 60 && e.offsetY < 60) ?450 : 90;
aud.currentTime = aud.duration * deg / 360;
} else { //内区域
aud.paused ? aud.play() : aud.pause();
}
}
//监听播放进度
aud.addEventListener('timeupdate', () => {
prog.style.strokeDashoffset = girth - girth * aud.currentTime / aud.duration + 'px';
cur.textContent = toMin(aud.currentTime);
dur.textContent = toMin(aud.duration);
for(j=0; j<lrcAr.length; j++) {
if(aud.currentTime >= lrcAr) lrctxt.textContent = lrcAr;
}
});
//圆检测鼠标经过
let isHover = (x,y) => Math.pow(x - 60, 2) + Math.pow(y - 60, 2) >= Math.pow(45, 2);
//时间信息格式化
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: #666 url('https://638183.freep.cn/638183/t22/hl/h59.jpg') no-repeat center/cover; box-shadow: 3px 3px 20px #000; user-select: none; position: relative; z-index: 1;}
#mplayer { position: absolute; padding: 0; margin: 20px; width: 120px; height: 120px; border-radius: 50%; overflow: hidden; }
#prog, #track { stroke: url(#gradient); }
#lrc { position: absolute; display: block; left: 20px; bottom: 0; }
#lrctxt { dominant-baseline: middle; fill: url(#gradient); font: bold 2em sans-serif; text-shadow: 0 4px 0 #000; letter-spacing: 2px; }
</style>
<div id="papa">
<div id="mplayer">
<svg width="100%" height="100%" shape-rendering="geometricPrecision">
<g transform="rotate(-90, 60, 60)">
<circle id="track" cx="60" cy="60" r="55" fill="none" stroke="rgba(255,255,255,.5)" stroke-width="10" stroke-dasharray="2" stroke-opacity="0.45" />
<circle id="prog" cx="60" cy="60" r="55" fill="none" stroke="red" stroke-width="10" />
</g>
<text fill="orange">
<tspan id="cur" x="40" y="55">00:00</tspan>
<tspan id="dur" x="40" y="75">00:00</tspan>
</text>
</svg>
</div>
<svg id="lrc" width="400" height="50">
<defs>
<linearGradient id="gradient" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="green"/>
<stop offset="50%" stop-color="orange"/>
<stop offset="100%" stop-color="red"/>
</linearGradient>
</defs>
<text id="lrctxt" x="0" y="25">黄龄 - HIGH歌</text>
</svg>
</div>
<script>
let lrcAr = [
['0.00','黄龄 - High歌'],
['6.00','作词/作曲:常石磊'],
['9.00','歌词编辑:孟德良'],
['17.04','Mountain top'],
['19.05','就跟着一起来'],
['21.09','没有什么 阻挡着未来'],
['26.04','Deeping night'],
['28.05','就你和我的爱'],
['30.07','没有什么 阻挡着未来'],
['35.03','Mountain top'],
['37.03','就跟着一起来'],
['39.06','没有什么 阻挡着未来'],
['44.01','Deeping night'],
['46.01','就你和我的爱'],
['48.05','没有什么 阻挡着未来'],
['53.00','Mountain top'],
['54.09','就跟着一起来'],
['57.04','没有什么 阻挡着未来'],
['61.10','Deeping night'],
['63.09','就你和我的爱'],
['66.03','没有什么 阻挡着未来'],
['70.08','Yi Yi Yi 你不在 我不在'],
['75.03','Yi Yi Yi 谁还会在'],
['79.07','Yi Yi Yi 你不在 我不在'],
['84.02','Yi Yi Yi 谁还会在'],
['124.02','Mountain top'],
['126.00','就跟着一起来'],
['128.05','没有什么 阻挡着未来'],
['132.10','Deeping night'],
['135.00','就你和我的爱'],
['137.04','没有什么 阻挡着未来'],
['141.09','Yi Yi Yi 你不在 我不在'],
['146.04','Yi Yi Yi 谁还会在'],
['150.07','Yi Yi Yi 你不在 我不在'],
['155.03','Yi Yi Yi 谁还会在'],
['204.03','Mountain top'],
['208.04','就一起来'],
['213.00','Mountain top'],
['217.03','就一起来'],
['221.08','Mountain top'],
['225.10','就一起来'],
['230.07','Mountain top'],
['235.01','就一起来']
];
let aud = new Audio();
aud.src = 'https://music.163.com/song/media/outer/url?id=238634.mp3';
aud.autoplay = true;
aud.loop = true;
//设置圆环进度偏移
let girth = prog.getTotalLength();
prog.style.strokeDasharray = prog.style.strokeDashoffset = girth+ 'px';
//圆环鼠标经过
mplayer.onmousemove = (e) => {
if (isHover(e.offsetX, e.offsetY))mplayer.style.cursor = 'pointer';
}
//圆环点击
mplayer.onclick = (e) => {
if (isHover(e.offsetX, e.offsetY)) { //轨道
let deg = Math.atan2(e.offsetY - 60, e.offsetX - 60) * 180 / Math.PI;
deg += (e.offsetX < 60 && e.offsetY < 60) ?450 : 90;
aud.currentTime = aud.duration * deg / 360;
} else { //内区域
aud.paused ? aud.play() : aud.pause();
}
}
//监听播放进度
aud.addEventListener('timeupdate', () => {
prog.style.strokeDashoffset = girth - girth * aud.currentTime / aud.duration + 'px';
cur.textContent = toMin(aud.currentTime);
dur.textContent = toMin(aud.duration);
for(j=0; j<lrcAr.length; j++) {
if(aud.currentTime >= lrcAr) lrctxt.textContent = lrcAr;
}
});
//圆检测鼠标经过
let isHover = (x,y) => Math.pow(x - 60, 2) + Math.pow(y - 60, 2) >= Math.pow(45, 2);
//时间信息格式化
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 实现。svg用一个 div 包裹,其尺寸即为圆环 svg 的尺寸;用 div 包裹 svg 也是 DOM 的常用做法,目的在便捷控制。本例,不需要 div 包裹时,JS 控制进度的点击范围会扩展到四个角,div 的 border-radius + overflow 设置能简单将四个角屏蔽掉,无需额外编程。
二、lrc歌词使用另外一个 svg 部署,使用 fill 填充着色,且定制一个线性渐变+CSS阴影。svg 文本支持CSS阴影,很方便,但渐变需要另外设置,线性渐变的指令为 linearGradient,它至少需要两种 stop 颜色参与渐变,具体语句请参阅23至29行。将 linearGradient 语句组放入 <defs> ... </defs> 中,是将其定义为可反复调用的属性设定。然后,lrc歌词的CSS盒子要调用它,就在对应的 #lrctxt 选择器绑定 linearGradient 的 id:
#lrctxt { dominant-baseline: middle; fill: url(#gradient); ... }
同样的,另一组 svg,就是圆环播放器,两个圆环也跟它绑定:
#prog, #track { stroke: url(#gradient); }
圆环轨迹 #track,需要淡一点的色彩,所以,又在HTML代码(也可以在CSS代码)中加入一个透明设置的属性:
<circle id="track" ... stroke-opacity="0.45" />
三、圆环的旋转
圆环的起始默认的起始点在三点钟方向,而我们希望圆环进度从12点钟方向开始,故需要逆时针旋转圆环90度。svg 直接支持 transform,可以直接使用。两个圆环放在 <g> ... </g> 标签里(12-15行),g标签是分组的意思,其内的元素可以通过 g 定制一些属性:
<g transform="rotate(-90, 60, 60)"> ... </g>
-90 就是逆转 90 度,60和60是 transform 的圆点,与 circle (圆)的圆心(60,60)相一致,这样就不会转到找不着了。
编程除了需要宏大的构思,还需要逐一细化每一个小细节,实属不易,建议使用播放器代码者在成功使用后给俺账号打5毛钱,以资鼓励。{:4_170:} PS:轨迹圆环的实现原理
利用 svg 的 stroke-dasharray 即绘制虚线线段组 的指令,可以将线条(含各种stroke出来的边框)线段画。dash有破折号的意思,虚线化的线段,就像是一根一根的破折号。13行代码,
<circle id="track" ... stroke-dasharray="2" ... />
stroke-dashoffset="2" 表示虚线线段的长度,也是虚线间的间距,就得出帖子中CPU散热片的效果。
stroke-dasharray 可以接受两个参数,例如,
stroke-dasharray="20,5"
它表示,虚线线段长为 20,间距为 5。
还可以接受多个参数,例如:
stroke-dasharray="12, 2, 6"
这就难以描述了,大概是酱紫:
虚线长12 间距2 虚线长6 间距12 虚线长2 间距6 …… PS2:圆圈进度的实现
利用 stroke-dashoffset 进行基于 stroke 的线条位移。我再JS里进行动态控制。首先,初始化位移:
//设置圆环进度偏移
let girth = prog.getTotalLength(); //获得圆周长(比使用 2*r*π 精准
prog.style.strokeDasharray = prog.style.strokeDashoffset = girth+ 'px'; //设置虚线线长、全偏移
偏移量等于其线长时,表示完全偏移,偏移量为 0 时,表示不偏移,偏移量为负数时,表示逆向偏移。
然后在 aud 的播放进度事件中,根据上述偏移原理,进度条的长度公式如下:
圆周长 - 当前偏移量
具体代码在 103 行。
@红影 3、4和本楼就是 stroke-dasharray 和 stroke-dashoffset 的具体解释。昨天实际上在线编辑了一个实例,中途掉电,论坛能恢复的数据不及 1/4,量大没有重写,借此帖解释一下。
等会来学习! 欣赏老师的精彩代码,问好!{:4_171:} 欣赏黑黑先生的精美制作,问候朋友早晨好! 这个很漂亮,黑黑辛苦了{:4_187:} 有两个地方没看懂,为什么用2个圆环,看两个半径一样的啊。
还有这句:进度条的长度公式如下:圆周长 - 当前偏移量,不是应该就是当前偏移么,为什么用圆周去减? 再看了一下,两个圆环好像知道了,一个是散热片的底,一个是进度去覆盖的颜色。
散热片好像还是白色,为什么显示出来是红的? 这个播放器漂亮 梦缘 发表于 2022-9-5 08:44
欣赏老师的精彩代码,问好!
{:5_108:} 绿叶清舟 发表于 2022-9-5 11:46
这个播放器漂亮
可以一看 梦油 发表于 2022-9-5 10:13
欣赏黑黑先生的精美制作,问候朋友早晨好!
{:5_108:} 红影 发表于 2022-9-5 11:34
这个很漂亮,黑黑辛苦了
{:5_104:} 红影 发表于 2022-9-5 11:37
再看了一下,两个圆环好像知道了,一个是散热片的底,一个是进度去覆盖的颜色。
散热片好像还是白色,为什 ...
底圆就像轨道一样,这样没有进度时依然能看到播放器的圆环。
设置了颜色是为了以防万一吧。圆环都与 linearGradient 绑定了,会使用渐变色上色,如果渐变色失效,则会使用原来定义的颜色。渐变色有三种参与,不单单是红色。 红影 发表于 2022-9-5 11:36
有两个地方没看懂,为什么用2个圆环,看两个半径一样的啊。
还有这句:进度条的长度公式如下:圆周长 - 当 ...
二个圆环:一个做衬底,一个做进度显示。
关于进度显示:stroke-dashoffset 的参数,如果等于圆周(或line的线长)长度,表示全偏移,是 0 时表示不偏移。偏移部分是看不到的部分,而我们要的是它能显示进度。偏移量与进度不是等量的,它们是反向关系,所以需要用周长减去偏移量才能得出进度量。 马黑黑 发表于 2022-9-5 12:33
二个圆环:一个做衬底,一个做进度显示。
关于进度显示:stroke-dashoffset 的参数,如果等于圆周(或 ...
嗯,stroke-dashoffset 正值的时候是往反向,这个和以往的习惯不同。现在知道了{:4_204:} 马黑黑 发表于 2022-9-5 12:28
底圆就像轨道一样,这样没有进度时依然能看到播放器的圆环。
设置了颜色是为了以防万一吧。圆环都与 lin ...
哦,设置颜色是为了以防万一啊,嗯,它实际的颜色就被渐进色覆盖的,这里的设置在正常时候是用不上的。谢谢黑黑解答{:4_187:}