清零
<style>#papa { left: -214px; width: 1024px; height: 640px; background: darkred url('/data/attachment/forum/202209/08/064641dlkn8iogkhrok2s6.jpg') no-repeat center/cover; display: grid; place-items: center; box-shadow: 3px 3px 20px #000; user-select: none; position: relative; z-index: 1;}
#mplayer { position: absolute;top: 260px; width: 120px; height: 120px; border-radius: 50%; overflow: hidden; z-index: 3; }
#track { stroke: url(#gradient); }
#lrc { position: absolute; top: 180px; display: block; }
#lrctxt { text-anchor: middle; dominant-baseline: text-after-edge; fill: url(#gradient); font: bold 2.2em sans-serif; text-shadow: -2px -2px 0px #fff, 2px 2px 3px #000; letter-spacing: 3px; }
#tmsg { fill: #ccc; font: bold 1em sans-serif; }
#btnwrap { display: block; fill: #ccc; cursor: pointer; }
#btnwrap:hover { fill: orange; }
</style>
<div id="papa">
<!-- 播放器 -->
<svg id="mplayer" width="120" height="120" shape-rendering="geometricPrecision">
<g id="mama" transform="rotate(-90, 60, 60)" style="cursor: pointer;">
<circle id="track" cx="60" cy="60" r="50" fill="none" stroke-width="10" stroke="rgba(0,0,0,0.35)" />
<circle id="prog" cx="60" cy="60" r="50" fill="none" stroke-width="4" stroke="rgba(255,255,255,0.55)" />
</g>
<path id="curPath" d="M 20 70 Q 60 0 100 70" fill="none" stroke="none"/>
<path id="durPath" d="M 20 55 Q 60 110 100 55" fill="none" stroke="none"/>
<g id="tmsg">
<text x="34" y="0"><textPath id="curMsg" xlink:href="#curPath" dominant-baseline="text-after-edge">00:00</textPath></text>
<text x="29" y="0"><textPath id="durMsg" xlink:href="#durPath" dominant-baseline="text-before-edge">00:00</textPath></text>
</g>
<g id="btnwrap">
<path id="btnplay" d="M 50 50,50 70,70, 60 z"></path>
<path id="btnpause" d="M 52 50,52 70,57 70,57 50,52 50 z M 60 50,60 70,65 70,65 50,60 50 z"></path>
</g>
</svg>
<!-- lrc歌词 -->
<svg id="lrc" width="560" height="150">
<defs><path id="lrcPath" fill="none" stroke="red" d="M 10 150 Q 305 -10,560 150" /></defs>
<defs>
<linearGradient id="gradient" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="blue"/>
<stop offset="35%" stop-color="yellow"/>
<stop offset="65%" stop-color="gray"/>
<stop offset="100%" stop-color="red"/>
</linearGradient>
</defs>
<textx="54%" y="0"><textPath id="lrctxt" xlink:href="#lrcPath">司南 - 清零</textPath></text>
</svg>
</div>
<script>
let lrcAr = [
['1.42','作词:千年小妖'],
['2.84','作曲:黄文文'],
['4.66','编曲:曾吴秋杰'],
['5.66','OP:深声文化 出品:网易飓风'],
['8.43','演唱:司南'],
['17.22','风划过黎明 泛白的天际'],
['20.79','阴沉的乌云 在聚集'],
['24.34','裹挟着雨滴 偏漫无声息'],
['27.97','跳跃在我的掌心里'],
['31.60','潮湿空气 席卷着旧日回忆'],
['35.22','身后的影子一瞬间藏匿'],
['38.59','难预测的天气 眼前的风景'],
['42.32','连同你 消失彻底'],
['44.43','混音:曾吴秋杰'],
['46.26','制作人:曾吴秋杰'],
['48.59','你在我 生命中全部清零'],
['52.64','寻你的踪迹 已下落不明'],
['56.16','我手写故事里的残缺可惜'],
['59.97','来不及 拥抱就离去'],
['63.31','我的爱 在你心里被清零'],
['66.86','我们又回到 陌生的关系'],
['70.62','许下的承诺你选择忘记'],
['74.15','这段爱 被遗憾定义'],
['92.49','潮湿空气 席卷着旧日回忆'],
['96.06','身后的影子一瞬间藏匿'],
['99.62','难预测的天气 眼前的风景'],
['103.20','连同你 消失彻底'],
['109.94','你在我 生命中全部清零'],
['113.58','寻你的踪迹 已下落不明'],
['117.22','我手写故事里的残缺可惜'],
['121.05','来不及 拥抱就离去'],
['124.30','我的爱 在你心里被清零'],
['128.07','我们又回到 陌生的关系'],
['131.50','许下的承诺你选择忘记'],
['135.01','这段爱 被遗憾定义'],
['138.61','你在我 生命中全部清零'],
['142.23','寻你的踪迹 已下落不明'],
['146.10','我手写故事里的残缺可惜'],
['149.73','来不及 拥抱就离去'],
['152.93','我的爱 在你心里被清零'],
['156.52','我们又回到 陌生的关系'],
['160.10','许下的承诺你选择忘记'],
['163.70','这段爱 被遗憾定义'],
['169.36','录音师:乌英剑'],
['171.31','和声:熊'],
['173.08','统筹:郭凯翌/吴桦/冯港'],
['174.83','录音棚:北京好听音乐录音棚']
];
let cc = { //圆环对象
x: 1*track.getAttribute('cx'),
y: 1*track.getAttribute('cy'),
r: 1*track.getAttribute('r'),
sw: 1*track.getAttribute('stroke-width'),
len: track.getTotalLength(),
};
let aud = new Audio();
aud.src = 'https://music.163.com/song/media/outer/url?id=1961264465.mp3';
aud.autoplay = true;
aud.loop = true;
prog.style.strokeDasharray = prog.style.strokeDashoffset = cc.len; //进度偏移
btnwrap.onclick = () => aud.paused ? aud.play() : aud.pause(); //按钮单击
mama.onclick = (e) => { //环单击
let deg = Math.atan2(e.offsetY - cc.y, e.offsetX - cc.x) * 180 / Math.PI;
deg += (e.offsetX < cc.x && e.offsetY < cc.y) ? 450 : 90;
aud.currentTime = aud.duration * deg / 360;
};
//audio空间各类监听
aud.addEventListener('pause', () => btnstate());
aud.addEventListener('play',() => btnstate());
aud.addEventListener('timeupdate', () => {
prog.style.strokeDashoffset = cc.len - cc.len * aud.currentTime / aud.duration + 'px';
curMsg.textContent = toMin(aud.currentTime);
durMsg.textContent = toMin(aud.duration);
for(j=0; j<lrcAr.length; j++) {
if(aud.currentTime >= lrcAr) lrctxt.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: darkred url('/data/attachment/forum/202209/08/064641dlkn8iogkhrok2s6.jpg') no-repeat center/cover; display: grid; place-items: center; box-shadow: 3px 3px 20px #000; user-select: none; position: relative; z-index: 1;}
#mplayer { position: absolute;top: 260px; width: 120px; height: 120px; border-radius: 50%; overflow: hidden; z-index: 3; }
#track { stroke: url(#gradient); }
#lrc { position: absolute; top: 180px; display: block; }
#lrctxt { text-anchor: middle; dominant-baseline: text-after-edge; fill: url(#gradient); font: bold 2.2em sans-serif; text-shadow: -2px -2px 0px #fff, 2px 2px 3px #000; letter-spacing: 3px; }
#tmsg { fill: #ccc; font: bold 1em sans-serif; }
#btnwrap { display: block; fill: #ccc; cursor: pointer; }
#btnwrap:hover { fill: orange; }
</style>
<div id="papa">
<!-- 播放器 -->
<svg id="mplayer" width="120" height="120" shape-rendering="geometricPrecision">
<g id="mama" transform="rotate(-90, 60, 60)" style="cursor: pointer;">
<circle id="track" cx="60" cy="60" r="50" fill="none" stroke-width="10" stroke="rgba(0,0,0,0.35)" />
<circle id="prog" cx="60" cy="60" r="50" fill="none" stroke-width="4" stroke="rgba(255,255,255,0.55)" />
</g>
<path id="curPath" d="M 20 70 Q 60 0 100 70" fill="none" stroke="none"/>
<path id="durPath" d="M 20 55 Q 60 110 100 55" fill="none" stroke="none"/>
<g id="tmsg">
<text x="34" y="0"><textPath id="curMsg" xlink:href="#curPath" dominant-baseline="text-after-edge">00:00</textPath></text>
<text x="29" y="0"><textPath id="durMsg" xlink:href="#durPath" dominant-baseline="text-before-edge">00:00</textPath></text>
</g>
<g id="btnwrap">
<path id="btnplay" d="M 50 50,50 70,70, 60 z"></path>
<path id="btnpause" d="M 52 50,52 70,57 70,57 50,52 50 z M 60 50,60 70,65 70,65 50,60 50 z"></path>
</g>
</svg>
<!-- lrc歌词 -->
<svg id="lrc" width="560" height="150">
<defs><path id="lrcPath" fill="none" stroke="red" d="M 10 150 Q 305 -10,560 150" /></defs>
<defs>
<linearGradient id="gradient" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="blue"/>
<stop offset="35%" stop-color="yellow"/>
<stop offset="65%" stop-color="gray"/>
<stop offset="100%" stop-color="red"/>
</linearGradient>
</defs>
<textx="54%" y="0"><textPath id="lrctxt" xlink:href="#lrcPath">司南 - 清零</textPath></text>
</svg>
</div>
<script>
let lrcAr = [
['1.42','作词:千年小妖'],
['2.84','作曲:黄文文'],
['4.66','编曲:曾吴秋杰'],
['5.66','OP:深声文化 出品:网易飓风'],
['8.43','演唱:司南'],
['17.22','风划过黎明 泛白的天际'],
['20.79','阴沉的乌云 在聚集'],
['24.34','裹挟着雨滴 偏漫无声息'],
['27.97','跳跃在我的掌心里'],
['31.60','潮湿空气 席卷着旧日回忆'],
['35.22','身后的影子一瞬间藏匿'],
['38.59','难预测的天气 眼前的风景'],
['42.32','连同你 消失彻底'],
['44.43','混音:曾吴秋杰'],
['46.26','制作人:曾吴秋杰'],
['48.59','你在我 生命中全部清零'],
['52.64','寻你的踪迹 已下落不明'],
['56.16','我手写故事里的残缺可惜'],
['59.97','来不及 拥抱就离去'],
['63.31','我的爱 在你心里被清零'],
['66.86','我们又回到 陌生的关系'],
['70.62','许下的承诺你选择忘记'],
['74.15','这段爱 被遗憾定义'],
['92.49','潮湿空气 席卷着旧日回忆'],
['96.06','身后的影子一瞬间藏匿'],
['99.62','难预测的天气 眼前的风景'],
['103.20','连同你 消失彻底'],
['109.94','你在我 生命中全部清零'],
['113.58','寻你的踪迹 已下落不明'],
['117.22','我手写故事里的残缺可惜'],
['121.05','来不及 拥抱就离去'],
['124.30','我的爱 在你心里被清零'],
['128.07','我们又回到 陌生的关系'],
['131.50','许下的承诺你选择忘记'],
['135.01','这段爱 被遗憾定义'],
['138.61','你在我 生命中全部清零'],
['142.23','寻你的踪迹 已下落不明'],
['146.10','我手写故事里的残缺可惜'],
['149.73','来不及 拥抱就离去'],
['152.93','我的爱 在你心里被清零'],
['156.52','我们又回到 陌生的关系'],
['160.10','许下的承诺你选择忘记'],
['163.70','这段爱 被遗憾定义'],
['169.36','录音师:乌英剑'],
['171.31','和声:熊'],
['173.08','统筹:郭凯翌/吴桦/冯港'],
['174.83','录音棚:北京好听音乐录音棚']
];
let cc = { //圆环对象
x: 1*track.getAttribute('cx'),
y: 1*track.getAttribute('cy'),
r: 1*track.getAttribute('r'),
sw: 1*track.getAttribute('stroke-width'),
len: track.getTotalLength(),
};
let aud = new Audio();
aud.src = 'https://music.163.com/song/media/outer/url?id=1961264465.mp3';
aud.autoplay = true;
aud.loop = true;
prog.style.strokeDasharray = prog.style.strokeDashoffset = cc.len; //进度偏移
btnwrap.onclick = () => aud.paused ? aud.play() : aud.pause(); //按钮单击
mama.onclick = (e) => { //环单击
let deg = Math.atan2(e.offsetY - cc.y, e.offsetX - cc.x) * 180 / Math.PI;
deg += (e.offsetX < cc.x && e.offsetY < cc.y) ? 450 : 90;
aud.currentTime = aud.duration * deg / 360;
};
//audio空间各类监听
aud.addEventListener('pause', () => btnstate());
aud.addEventListener('play',() => btnstate());
aud.addEventListener('timeupdate', () => {
prog.style.strokeDashoffset = cc.len - cc.len * aud.currentTime / aud.duration + 'px';
curMsg.textContent = toMin(aud.currentTime);
durMsg.textContent = toMin(aud.duration);
for(j=0; j<lrcAr.length; j++) {
if(aud.currentTime >= lrcAr) lrctxt.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>
本帖最后由 马黑黑 于 2022-9-8 07:26 编辑
简单解释:
一、用两个 svg 分别做圆环播放控制器和lrc歌词。移动圆环播放器在CSS选择器 #mplayer 、移动lrc歌词在CSS选择器进行方位操作。
二、圆环播放器和lrc歌词共享 id="gradient" 的渐变设置(34行)。id是元素的唯一标识,只要在同一个页面,即便在不同区域设置,依然可以识别、共享特性。
三、lrc歌词路径设置为往上的弧形,路径设计在32行:
<defs><path id="lrcPath" fill="none" stroke="red" d="M 10 150 Q 305 -10,560 150" /></defs>
头尾的 defs 收尾标签隐藏路径(没必要把路径显示出来),41行文本路径标签绑定此路径,从而文本能按预设路径排列(播放器中的时间信息排列方法与此原理同)。
此处使用的是二次贝塞尔曲线,d="M 10 150 Q 305 -10,560 150" ,简单说一下:
M 10 150,表示移动(M=Move)画笔到(10,150),这也是曲线的开端;Q表示二次贝塞尔曲线,其所带的第一组xy坐标(300,-10)是曲线的线外控制点,这个点能够左右曲线凸面朝哪个方向(基于头尾两端的方向),第二组坐标(560,150)是曲线的末端,注意它的Y坐标和曲线开始点的值一致,它的X坐标值减去起始点X坐标值等于曲线经过的横向距离。
曲线的开始点、控制点、终点的xy坐标,共同构成曲线的最终样式。可以把defs标签去掉查看路径的真实样子。
四、lrc歌词文本如何在路径上居中
① 06行:#lrctxt { text-anchor: middle; dominant-baseline: text-after-edge; ......
② 41行:<textx="54%" y="0"><textPath id="lrctxt" xlink:href="#lrcPath">司南 - 清零</textPath>
这两行的共同作用,能达成目标。CSS中,设定文本居中的语句是 text-anchor: middle; 那句,但此处,svg的文本居中是基于X坐标的,即文字分散在X坐标的两边,所以有 41行 的 x="54%"的设置。为什么不是50%呢?我开始也设为50%,后面根据肉眼观测,微调为54%,这是根据路径样式做的微调,并不科学,或者,路径设计的不理想,不过大致效果可以的。 圆环播放器看着像手镯,估计很多人喜欢,那就算是俺的中秋礼物吧,送给大家。预祝中秋万事如意、过节开森! 等会来学习,现在在外面做核酸检测! 本帖最后由 马黑黑 于 2022-9-8 07:50 编辑
【附】二次贝塞尔曲线的手工设计方法:
分三个点坐标。点1 用 M 命令,移动画笔到 的意思;点2 和 点3 用 Q 命令带出:点2 是曲线之外的控制点,它是曲线曲率的决定点;点3 是曲线的终点。
例如我们要在区域 300*200 的地方设置一条横向走向、水平距离300的曲线,凸面朝上,则需要将画笔移动到 (0,y1),然后给出一个控制点(x2, y2),这里的y2要尽可能小,可以是负数,最后给出终点(300,y3)。写成 d 语句(各点的数值可以适当调整):
d="M 0,200 Q 100 -20,300 90"
完整的路径语句:
<path d="M 0,200 Q 100 -20,300 90" fill="red" stroke="none" id="myPath" />
路径也是一个实体元素,所以可以拥有自己的着色设定,fill默认黑色、不设计也有,stroke建议永远设置为none,否则可能会出现几何图案(设定stroke为实色时试着在 d 语句的末尾加入小写字母 z)。为确保路径不显示出来,可在路径前后加 <defs> 和 </defs> 标签。 加林森 发表于 2022-9-8 07:45
等会来学习,现在在外面做核酸检测!
早。我们也连续做了三天,小区有两位密接者,封闭了。 欣赏精美好帖,感谢老师的礼物!{:4_204:} 梦缘 发表于 2022-9-8 08:08
欣赏精美好帖,感谢老师的礼物!
{:5_108:} 马黑黑 发表于 2022-9-8 07:23
简单解释:
一、用两个 svg 分别做圆环播放控制器和lrc歌词。移动圆环播放器在CSS选择器 #mplayer 、移 ...
我昨天也在本地试了一下向上弯的,对这个305的数字有点不解d="M 10 150 Q 305 -10,560 150"
貌似应该取280,凸起的才是居中吧,后面的54%是不是也是因为这个原因?
当然我现在听不了歌,也看不到效果,只能是闭着眼睛瞎说了{:4_173:} 马黑黑 发表于 2022-9-8 07:48
【附】二次贝塞尔曲线的手工设计方法:
分三个点坐标。点1 用 M 命令,移动画笔到 的意思;点2 和 点3...
“stroke建议永远设置为none,否则可能会出现几何图案(设定stroke为实色时试着在 d 语句的末尾加入小写字母 z)。”
还有这样的事,记下了{:4_173:} 马黑黑 发表于 2022-9-8 07:29
圆环播放器看着像手镯,估计很多人喜欢,那就算是俺的中秋礼物吧,送给大家。预祝中秋万事如意、过节开森!
这个手镯太美了,谢谢黑黑的中秋礼物{:4_187:} 《清零》两字的寓意更好,虽然是讲情感的歌曲,我更愿意是说疫情的,清零了,就可以出去玩了,也不用有诸多限制了{:4_173:} 一进坛子看到清零两字真的好开心{:4_173:}
借黑黑好帖同祝大家中秋快乐,有圆圆的月亮,圆圆的月饼,还有圆圆的代码手镯{:4_199:} 马黑黑 发表于 2022-9-8 07:54
早。我们也连续做了三天,小区有两位密接者,封闭了。
终于查完了。我们这里基本上是全市都封闭管理了。 加林森 发表于 2022-9-8 09:25
终于查完了。我们这里基本上是全市都封闭管理了。
现在很麻烦了,哪儿都去不了了 红影 发表于 2022-9-8 08:55
一进坛子看到清零两字真的好开心
借黑黑好帖同祝大家中秋快乐,有圆圆的月亮,圆圆的月饼,还有 ...
一切都是团团圆圆 红影 发表于 2022-9-8 08:47
我昨天也在本地试了一下向上弯的,对这个305的数字有点不解d="M 10 150 Q 305 -10,560 150"
貌似应该取 ...
估计是吧。理论上x方向的控制点,应居中,做的时候,歌词显示不完,就随意调整了长度,然后其他数据也随意调整了一下。 马黑黑 发表于 2022-9-8 09:26
现在很麻烦了,哪儿都去不了了
就是。很麻烦的。 红影 发表于 2022-9-8 08:53
《清零》两字的寓意更好,虽然是讲情感的歌曲,我更愿意是说疫情的,清零了,就可以出去玩了,也不用有诸多 ...
这个难,无法清零了,将是一种与人类为伴的流感一样的东东。等待平民药价出现吧,那时,它和流感是一样的待遇