Skip to content

MusicPlayer 音乐播放器

基于 Meting API 的音乐播放器。三种形态、四角停靠、全局歌词。

三种形态

形态收起态收起时能做什么
full—— 常驻完整面板——
attach 吸附贴边小圆钮(只有封面)只能展开
mini 精简贴边胶囊条(封面 + 歌名 - 歌手)可直接播放,展开是备选

关键差别在「收起态能做什么」

  • attach 想暂停得「移入展开 → 点暂停」两步;
  • mini 一步到位——封面本身就是播放开关。

完整模式

正在加载歌单…
vue
<SrMusicPlayer :api="API" :id="PLAYLIST_ID" mode="full" />

完整模式在文档流里,宽度自适应父容器,不会浮动。

吸附模式

收起态是贴着屏幕边缘的小圆钮,鼠标移入即展开完整播放器。

vue
<SrMusicPlayer :api="API" :id="PLAYLIST_ID" mode="attach" placement="bottomRight" />

演示请到 playground 查看

吸附与精简模式的收起态是固定定位的,会贴在浏览器视口边缘—— 在本页内演示会和文档站的侧边栏打架。

请到 playground/music-player 页面查看实机效果。

精简模式

收起态是贴边常驻的胶囊条:封面 + 歌名 / 歌手。

  • 点封面直接播放 / 暂停,不用先展开面板;
  • 点歌名才展开完整播放器;
  • 播放时文字区变成当前歌词,随播放逐行切换。
vue
<SrMusicPlayer :api="API" :id="PLAYLIST_ID" mode="mini" placement="bottomRight" />

停靠位置

placement 控制收起态与展开面板停在哪个角——三者共用同一组坐标, 不会出现「胶囊在右下、展开后跑到左上」的跳变。

vue
<SrMusicPlayer mode="mini" placement="topLeft" />
<SrMusicPlayer mode="mini" placement="topRight" />
<SrMusicPlayer mode="mini" placement="bottomLeft" />
<SrMusicPlayer mode="mini" placement="bottomRight" />
<SrMusicPlayer mode="mini" placement="bottomRight" :offset="24" />

offset 是距边缘的距离,数字按 px 处理,也可以传 '2rem' 这类 CSS 长度。

为什么用 placement 而不是 edge + 垂直偏移

停靠位置是一个整体概念。拆成两个字段会出现「左侧 + 底部距离 200px」 这种表达不了角落的组合,调用方还得自己保证两者不冲突。

从旧版本迁移:把 edge="right" 改成 placement="bottomRight" 即可。 若原来用的是 mode="mini",注意它在早期版本里等同于现在的 attach—— 想保持原行为应改为 mode="attach"

全局歌词

global-lyrics屏幕底部固定一条当前歌词,与页面内容无关—— 滚到哪都能看到,适合做「字幕」。

vue
<SrMusicPlayer :api="API" :id="PLAYLIST_ID" mode="attach" global-lyrics />

两种歌词显示可以并存:胶囊条上显示一行(贴边),底部再固定一条(居中)。 两者取值同源,内容一致。

Meting API

组件通过 Meting API 获取歌单, 不直连网易云——那样会受跨域与反爬限制。

vue
<script setup>
// `api` 是 Meting 服务地址,`id` 是歌单 id
const API = 'https://music.3e0.cn/'
const PLAYLIST_ID = '2585465480'   // music.163.com/playlist?id=2585465480 里的数字
</script>

<template>
  <SrMusicPlayer :api="API" :id="PLAYLIST_ID" />
</template>

需要自备 API 服务

api 必须是可用的 Meting 服务实例。公共实例随时可能不可用, 生产环境请自行部署——这类服务需要代理请求音乐平台的接口, 长期稳定运行需要自己维护。

组件不内置任何默认地址:那会让所有使用者共享同一个第三方服务, 既不可靠也不合适。

层级

收起态与展开面板都通过 Teleport 挂到 body,配合最高的 --sr-z-popover 层级。

两者缺一不可

  • 只用高层级但留在祖先链里——任意一个带 transform / filter 的祖先都会创建 stacking context 把元素困住, 此时 z-index 只在那个局部语境里比较,页面外层随便一个定位元素都能压住它;
  • Teleport 但层级不够——会被同为浮层的组件盖住。

Props

名称类型默认值说明
apistringMeting API 服务地址(必填
idstring | number歌单 id
mode'full' | 'attach' | 'mini''full'形态
placement'topLeft' | 'topRight' | 'bottomLeft' | 'bottomRight''bottomRight'收起态停靠角落
offsetnumber | string16距边缘的距离
globalLyricsbooleanfalse是否显示底部全局歌词
showLyricsbooleantrue面板内是否显示歌词区
autoplaybooleanfalse是否自动播放

历史缺陷

三个已修复的问题

如果你用的是旧版本,可能遇到以下情况:

1. 封面播放时不转 旋转动画原先挂在 ::after 的径向渐变上——那是轴对称图形, 旋转它肉眼完全看不出变化,而真正的封面图压根没转。现已改为旋转 img 本身。

2. 精简模式圆钮看起来是方的 圆钮原先是「半圆」(贴边那侧切平),而封面播放时会自转, 方形封面转到直角边时直边与弧边形成强对比,整个钮看起来就是方的。 现已改为完整正圆,靠圆心外移来「咬住」屏幕边缘。

3. 面板内歌词不滚动 滚动居中的目标位置用 offsetTop 计算,而它是相对最近的定位祖先的。 歌词容器没有 position: relative,基准就上溯到了面板本身—— 算出来的位置整整偏出一块面板的高度。

现已改为:容器补 position: relative,且目标位置改用两个 getBoundingClientRect 的差值计算(只关心「当前行在可视区里的位置」, 与祖先是谁无关)。

无障碍

  • 播放 / 暂停按钮带 aria-label,且随状态变化(「播放」/「暂停」)
  • 进度条用 role="slider",带 aria-valuenow / aria-valuemin / aria-valuemax, 键盘可用方向键调整
  • 封面自转、胶囊条呼吸等动画在 prefers-reduced-motion 下全部停掉
  • 收起态的圆钮与胶囊都是真正的 <button>,键盘可聚焦与触发

自用组件库 · 源码分发 · 不发布 npm