MusicPlayer 音乐播放器
基于 Meting API 的音乐播放器。三种形态、四角停靠、全局歌词。
三种形态
| 形态 | 收起态 | 收起时能做什么 |
|---|---|---|
full | —— 常驻完整面板 | —— |
attach 吸附 | 贴边小圆钮(只有封面) | 只能展开 |
mini 精简 | 贴边胶囊条(封面 + 歌名 - 歌手) | 可直接播放,展开是备选 |
关键差别在「收起态能做什么」:
attach想暂停得「移入展开 → 点暂停」两步;mini一步到位——封面本身就是播放开关。
完整模式
<SrMusicPlayer :api="API" :id="PLAYLIST_ID" mode="full" />完整模式在文档流里,宽度自适应父容器,不会浮动。
吸附模式
收起态是贴着屏幕边缘的小圆钮,鼠标移入即展开完整播放器。
<SrMusicPlayer :api="API" :id="PLAYLIST_ID" mode="attach" placement="bottomRight" />演示请到 playground 查看
吸附与精简模式的收起态是固定定位的,会贴在浏览器视口边缘—— 在本页内演示会和文档站的侧边栏打架。
请到 playground 的 /music-player 页面查看实机效果。
精简模式
收起态是贴边常驻的胶囊条:封面 + 歌名 / 歌手。
- 点封面直接播放 / 暂停,不用先展开面板;
- 点歌名才展开完整播放器;
- 播放时文字区变成当前歌词,随播放逐行切换。
<SrMusicPlayer :api="API" :id="PLAYLIST_ID" mode="mini" placement="bottomRight" />停靠位置
placement 控制收起态与展开面板停在哪个角——三者共用同一组坐标, 不会出现「胶囊在右下、展开后跑到左上」的跳变。
<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 在屏幕底部固定一条当前歌词,与页面内容无关—— 滚到哪都能看到,适合做「字幕」。
<SrMusicPlayer :api="API" :id="PLAYLIST_ID" mode="attach" global-lyrics />两种歌词显示可以并存:胶囊条上显示一行(贴边),底部再固定一条(居中)。 两者取值同源,内容一致。
Meting API
组件通过 Meting API 获取歌单, 不直连网易云——那样会受跨域与反爬限制。
<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
| 名称 | 类型 | 默认值 | 说明 |
|---|---|---|---|
api | string | — | Meting API 服务地址(必填) |
id | string | number | — | 歌单 id |
mode | 'full' | 'attach' | 'mini' | 'full' | 形态 |
placement | 'topLeft' | 'topRight' | 'bottomLeft' | 'bottomRight' | 'bottomRight' | 收起态停靠角落 |
offset | number | string | 16 | 距边缘的距离 |
globalLyrics | boolean | false | 是否显示底部全局歌词 |
showLyrics | boolean | true | 面板内是否显示歌词区 |
autoplay | boolean | false | 是否自动播放 |
历史缺陷
三个已修复的问题
如果你用的是旧版本,可能遇到以下情况:
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>,键盘可聚焦与触发