闲来无事用Gemini和GD音乐台搓了一个在线音乐播放器觅音,UI风格很合我意,功能上马马虎虎吧,平时用一用还是不错的。已适配电脑端和移动端。
放一个Gemini写的总结吧:
一、 视觉设计:新中式与拟态的碰撞
配色哲学:全站以“纸白” (#F7F5F0) 为底,辅以“墨黑” (#1a1a1a) 和“朱红” (#C41E3A) 点缀。字体选用了开源的「马善政毛笔字」做标题,营造古风韵味。
响应式布局:利用 Flexbox 和 CSS Media Queries,实现了从移动端到 PC 端的无缝适配。
移动端:侧重单手操作,Mini 播放器悬浮底部。
PC 端:全屏播放页采用左右分栏,左侧黑胶旋转,右侧歌词滚动。
动态交互:背景使用了 SVG 噪点滤镜叠加,模拟宣纸的质感;黑胶唱片通过 CSS animation 实现旋转,并与播放状态同步(播放转动,暂停停止)。
二、 核心功能实现
本项目未使用任何前端框架(Vue/React),完全基于 HTML5 + 原生 JavaScript (ES6+) + CSS3 开发。
- 集中式状态管理 (Centralized State Management) 为了在无框架环境下有效管理应用状态,代码采用了一个全局的 state 对象作为单一数据源。所有的 DOM 更新(如播放暂停、切歌、歌词高亮)均基于该状态的变化进行响应。
const state = {
playlist: [], // 播放队列
playIndex: -1, // 当前索引
isPlaying: false, // 播放状态
audio: new Audio(), // 核心 Audio 对象
lyrics: [], Lyric data
mode: 0, // 0:列表循环 1:单曲 2:随机
// ...
};
通过 localStorage 对关键状态(播放队列、收藏列表)进行本地持久化,确保用户刷新页面后能恢复之前的播放现场。
- 音频控制与异步竞态处理
播放器的核心基于 HTML5 Audio 对象,通过监听 timeupdate、ended、error 等事件实现进度驱动和自动切歌。
在开发过程中,针对异步网络请求导致的竞态问题(Race Condition)进行了专门处理。由于获取音频 URL 是异步操作,在快速切换歌曲时,旧的请求可能会晚于新的请求返回,导致播放错误的音频。 解决方案是引入 currentRequestId 计数器闭包: 每次触发播放时,全局计数器自增 (++currentRequestId)。 将当前的 ID 锁定在本次请求的闭包中 (const thisRequestId = currentRequestId)。 在 fetch 回调中比对 ID,仅当 thisRequestId === currentRequestId 时才执行音频加载和播放操作,否则丢弃过期的响应。
- 歌词解析与交互优化
主要包含解析、同步与滚动交互三个环节:
正则解析:通过正则表达式 [(\d{2}):(\d{2}).(\d{2,3})] 提取 LRC 格式的时间戳,将其转化为秒数,生成 {time, text} 格式的对象数组。
同步定位:在 timeupdate 事件中,通过遍历比对当前播放时间 (currentTime),计算出当前应高亮的歌词行索引。
智能滚动与防抖: 为了解决“自动滚动”与“用户手动查看”的冲突,实现了滚动防抖机制。监听歌词容器的 scroll 和 touchstart 事件,当检测到用户操作时,设置 isUserScrolling = true 并暂停自动滚动;用户停止操作 3 秒后,通过 setTimeout 自动恢复跟随模式。
- 视觉渲染:CSS 变量与 SVG 噪点
UI 层面采用 CSS Variables 定义全局主题色(如 —ink-main, —bg-paper),便于统一调整风格。
为了模拟“纸张”的粗糙质感,避免纯色背景的单调,利用 Data URI 嵌入了一段 SVG 噪点滤镜 (feTurbulence),通过 mix-blend-mode 叠加在背景上。这种方式无需加载外部图片资源,既保证了页面体积最小化,又提升了视觉层次感。
- 发现机制:伪随机推荐算法
“闲庭信步”功能通过前端预设的关键词库(包含风格、场景、情感等 30+ 个 Tag),结合随机分页参数向搜索API发起请求。这种前端伪随机 + 后端分页的组合,在不依赖复杂推荐算法的前提下,以最低成本实现了内容发现的随机性和丰富度。