为什么要在个人网站里加一个播放器?

音乐不是必需,但它能让体验更完整,我也希望网站能更有一些氛围感。

于是我决定在网站上加入一个悬浮音乐播放器,支持播放网易云歌单,不干扰浏览,且能在页面切换时持续播放。经过一番搜索,我发现了开源项目 NeteaseMiniPlayer V2(简称 NMPv2),由 BHCN STUDIO & 北海的佰川 开发。它轻量、美观、功能完整,而且提供了非常灵活的配置方式。

NeteaseMiniPlayerV2官网

在我的网站中的最终效果:网站左下角有一个圆形的迷你播放器(可展开),支持播放、暂停、上一首/下一首、音量调节、歌词显示、播放列表,甚至还有黑胶唱片的旋转动画。最关键的是 —— 在无刷新导航(AJAX 页面切换)时,音乐不会中断

这篇文章我会分享如何将它集成到自己的网站中,包括动态加载、样式适配、与现有主题联动,以及解决常见的坑。


一、选定播放器:为什么是 NeteaseMiniPlayer?

市面上有很多第三方音乐插件,但大多数要么太重(需要引入整个 UI 库),要么依赖外部服务器,要么无法自定义样式。NeteaseMiniPlayer 的设计理念打动了我:

  1. 通过调用网易云音乐的公开 API 获取歌单和歌曲 URL,无需后端。
  2. 一个 <div> 加上 CSS/JS 就可以工作,支持 data-* 属性配置。
  3. 支持固定位置和悬浮位置:可以放在页面角落,也可以嵌入内容中。
  4. 歌词显示、播放列表、循环/随机模式 一应俱全。

项目的 GitHub 地址:https://github.com/numakkiyu/NeteaseMiniPlayer

作者还提供了一个短代码解析器,可以在 Markdown 中通过 {nmpv2:playlist=xxx} 插入播放器。不过我的需求是全站统一悬浮播放器,所以只需要在全局加载一次。


二、集成方案:动态加载 + 全局单例

我的网站使用了 SPA 风格的 AJAX 导航(自己写的 router.js),点击内部链接时通过 fetch 替换 main 内容,而不是整页刷新。这就要求播放器不能被页面替换时销毁,也不能重复创建。

因此,我设计了一个独立模块 global-music-player.js,它的职责是:

  1. 检查播放器是否已存在。
  2. 动态加载 NMPv2 的 CSS 和 JS(仅在需要时加载,减少首屏负担)。
  3. 创建播放器 DOM 并插入 body
  4. 监听 ajax:navigation 事件,确保无刷新导航后播放器依然存在(如果意外丢失则重建)。

核心代码大致如下(简化版):

// /js/vendor/global-music-player.js
const PLAYER_CONFIG = {
  playlistId: '18022003523',
  position: 'bottom-right',
  lyric: 'true',
  theme: 'auto'
};

async function ensureMusicPlayer() {
  if (document.querySelector('.netease-mini-player')) return;
  await loadResources(); // 动态加载 CSS 和 JS
  const playerDiv = createPlayerElement();
  document.body.appendChild(playerDiv);
  window.NeteaseMiniPlayer.initPlayer(playerDiv);
}

// 监听页面加载和无刷新导航
window.addEventListener('DOMContentLoaded', ensureMusicPlayer);
window.addEventListener('ajax:navigation', ensureMusicPlayer);

这样,无论用户从哪个页面进入,播放器都会自动出现在右下角,并且页面切换时不会被销毁或重复创建。


三、样式适配:让播放器融入网站主题

NeteaseMiniPlayer 的 CSS 使用了自己的一套颜色变量,但我的网站已经定义了一套完整的深色/浅色主题变量(如 --accent-color--surface-color--border-color 等)。如果直接使用原始样式,播放器会和网站视觉风格产生割裂。

于是我重写了播放器的样式文件,将它的颜色变量全部映射到我的主题变量

/* 适配后的 netease-mini-player-v2.css 片段 */
.netease-mini-player {
    --player-primary-bg: var(--surface-color);
    --player-secondary-bg: var(--surface-color-secondary);
    --player-accent-gradient: linear-gradient(135deg, var(--accent-color) 0%, var(--accent-light) 100%);
    --player-shadow-sm: var(--shadow-sm);
    --player-shadow-md: var(--shadow-md);
    --border-color: var(--border-color);
    /* ... */
}

同时,为了让播放器的流动背景动画(播放时出现的光晕效果)也跟随主题色,我使用了 color-mix 函数:

.netease-mini-player::before {
    background:
        radial-gradient(circle at 15% 20%, color-mix(in srgb, var(--accent-color) 25%, transparent) 0%, transparent 60%),
        /* ... */
}

这样,当用户通过主题切换按钮切换深色/浅色模式时,播放器会自动跟随变化(因为 data-theme 属性变化后,CSS 变量重新计算)。播放器本身也支持 data-theme="auto",它会检测宿主主题,但我选择完全交给网站的全局主题系统。

NeteaseMiniPlayer原样式: NeteaseMiniPlayer原样式 NeteaseMiniPlayer原样式-浅色

适配后的NeteaseMiniPlayer样式: 适配后的NeteaseMiniPlayer样式


四、与 AJAX 无刷新导航的深度集成

前面提到播放器在页面切换时需要保持状态。但还有一个细节:如果播放器处于展开状态(非迷你模式),在 AJAX 加载新页面后,可能会因为新页面中的某些 DOM 操作而意外关闭或丢失事件。我的解决方法是:

  • 播放器的 DOM 元素固定挂在 body 下,不在任何被替换的容器内。
  • 每次 ajax:navigation 事件触发后,不重建播放器,而是检查播放器是否存在。如果存在,什么都不做;如果不存在(极端情况),则重新创建。
  • 播放器的内部状态(当前播放歌曲、进度、音量)由 NMPv2 自己维护,无刷新导航不会影响 Audio 元素的运行。

另外,为了不重复加载脚本,loadResources 函数使用了幂等性设计:

let resourcesLoaded = false;
let loadingPromise = null;

function loadResources() {
  if (resourcesLoaded) return Promise.resolve();
  if (loadingPromise) return loadingPromise;
  loadingPromise = Promise.all([loadCSS(), loadJS()]).then(() => {
    resourcesLoaded = true;
    loadingPromise = null;
  });
  return loadingPromise;
}

这样多个并发调用只会加载一次。


五、最终体验与一些自定义优化

  • 右下角悬浮,鼠标移入展开为完整控件,移出一段时间后自动半透明停靠,不遮挡内容。
  • 歌词滚动:支持原歌词和翻译歌词,当前行高亮并自动滚动。
  • 播放列表:可以查看歌单内所有歌曲,点击切换。
  • 响应式:在手机访问时,音量滑条会被隐藏,按钮大小调整,依然可用。

我还额外做了两个优化:

1. 主题切换时刷新播放器颜色

因为播放器的流动背景动画使用了 CSS 变量,而主题切换时 document.documentElementdata-theme 变化,CSS 变量会自动更新。但某些浏览器可能需要强制重绘,我简单监听了 themeChanged 事件,重新设置一下播放器的 data-theme 属性(虽然实际上变量已经变了,但为了保险)。

2. 避免自动播放策略拦截

浏览器通常禁止未经用户交互的自动播放。我的策略是:初始化播放器时,不自动播放;如果用户点击了播放按钮,正常播放。如果希望首屏有音乐,可以引导用户点击一次任意位置后启用。NMPv2 内部已经有处理 autoplay 属性的逻辑,但需要用户与页面有过交互。我没有启用自动播放,尊重用户的选择。


六、开源与致谢

这个播放器不是我写的,但我很感激能站在巨人的肩膀上。NeteaseMiniPlayer V2 的作者 北海的佰川 不仅写出了优雅的代码,还提供了详细的文档。如果你也想给自己的网站加一个音乐播放器,强烈推荐去他的 GitHub 仓库看看。

我的所有修改(样式适配、动态加载模块)也开源在个人网站的仓库中,你可以从以下位置找到:

  • 播放器样式:/css/netease-mini-player-v2.css
  • 播放器脚本:/js/vendor/netease-mini-player-v2.js
  • 全局加载器:/js/vendor/global-music-player.js

七、最后

音乐能传递温度。当你读到我那些关于成长、雪夜、考试的随笔时,如果恰好有一首轻柔的钢琴曲在背景里流淌,或许你就能更贴近我当时的心境。

当然,播放器默认是关闭的,用户需要主动点击播放。我不希望音乐成为干扰 —— 它只是一个可选的、小小的陪伴。

现在,你可以试试点击右下角的播放器,选一首歌,然后继续浏览网站……希望你喜欢。


如果你也想集成,可以这样开始:

可以看看 官网Github 仓库

NeteaseMiniPlayerV2-Github

最后,感谢开源作者 北海的佰川 的贡献。祝你玩得开心 🎵