🎵 音乐胶囊模块使用说明
本仓库提供了一个可集成到 Hexo 主题 liushen 的音乐胶囊播放器模块。模块包含四个主要部分:
- 主题配置文件
- 布局模板文件
- 样式文件
- 前端脚本
本文档将详细介绍它们的放置位置、引用方式和变量配置。
1️⃣ 配置文件(根目录)
文件: _config.liushen.yml
位置: 项目根目录(blog/_config.liushen.yml
),不是主题根目录。
该文件用于存放音乐模块的相关配置(如是否启用、播放列表、API 接口等)。 示例(按需调整):
# 页脚音乐胶囊
capsule:
enable: true
# 歌单 ID / 单曲 ID
id: 13597135963
# 服务商:netease / qq / xiami / kugou / baidu
server: netease
# 类型:playlist / song
type: playlist
meting_api: https://met.example.com/api?server=:server&type=:type&id=:id&r=:r
volume: 0.8
2️⃣ 布局模板文件
文件: music.pug
位置: blog/themes/liushen/layout/includes/third-party/music.pug
该文件用于渲染音乐胶囊播放器 UI,需要在 layout.pug
中手动引入。
在 blog/themes/liushen/layout/includes/layout.pug
底部 添加以下代码(保留现有内容,仅在末尾追加):
if theme.capsule && theme.capsule.enable
include ./third-party/music.pug
📌 该判断会根据
_config.liushen.yml
中的capsule.enable
来决定是否加载播放器。
3️⃣ 样式文件
文件: music-capsule.styl
位置: blog/themes/liushen/source/css/__layout/music-capsule.styl
该位置的styl
样式表会被自动整合到main.css
💡 样式变量对照表
音乐胶囊使用了一些 CSS 变量,这些变量在亮色与暗色模式下分别定义。
🌞 亮色模式(默认)
/* nav */
--liushen-nav-bg: rgba(255, 255, 255, 0.8);
--liushen-nav-shadow: rgba(133, 133, 133, 0.6) 0px 5px 6px -5px;
/* ai_summary */
--liushen-title-font-color: #0883b7;
--liushen-maskbg: rgba(255, 255, 255, 0.85);
--liushen-ai-bg: conic-gradient(from 1.5708rad at 50% 50%, #d6b300 0%, #42A2FF 54%, #d6b300 100%);
/* card */
--liushen-card-bg: #fff;
--liushen-card-secondbg: #f1f3f8;
--liushen-card-border: 1px solid #e3e8f7;
/* button */
--liushen-button-bg: #f1f3f8;
--liushen-button-hover-bg: $theme-color;
/* text */
--liushen-text: #4c4948;
--liushen-secondtext: #3c3c43cc;
/* snackbar */
--snackbar-bg: rgba(255, 255, 255, 0.809);
--snackbar-text: #4c4948;
--snackbar-border: 1px solid rgba(126, 126, 126, 0.527);
/* menu child */
--menu-child-bg: rgba(255,255,255,0.8);
/* fancybox */
--liushen-fancybox-bg: rgba(255,255,255,0.5);
--liushen-fancybox-button-color: #000000;
🌙 暗色模式
[data-theme='dark'] {
/* nav */
--liushen-nav-bg: rgba(18,18,18,.8);
--liushen-nav-shadow: rgba(133, 133, 133, 0) 0px 5px 6px -5px;
/* ai_summary */
--liushen-maskbg: rgba(0, 0, 0, 0.85);
--liushen-ai-bg: conic-gradient(from 1.5708rad at 50% 50%,rgba(214, 178, 0, 0.46) 0%,rgba(66, 161, 255, 0.53) 54%,rgba(214, 178, 0, 0.49) 100%);
/* card */
--liushen-card-bg: #2d2d2d;
--liushen-card-secondbg: #3e3f41;
--liushen-card-border: 1px solid #42444a;
/* button */
--liushen-button-bg: #30343f;
--liushen-button-hover-bg: $theme-color;
/* text */
--liushen-text: #ffffffb3;
--liushen-secondtext: #a1a2b8;
/* snackbar */
--snackbar-bg: rgba(48, 48, 48, 0.809);
--snackbar-text: #dfdfdf;
--snackbar-border: 1px solid #cfd4dc40;
/* menu child */
--menu-child-bg: rgba(18,18,18,0.8);
/* fancybox */
--liushen-fancybox-bg: rgba(0,0,0,0.5);
--liushen-fancybox-button-color: #ffffff;
}
你可以将这些变量整合到主题的全局变量文件中,或根据配色需求自行调整。
4️⃣ 前端脚本
文件: music.js
位置: 可放在主题的任意 JS 文件中(例如 blog/themes/liushen/source/js/music.js
)或合并到 main.js
中。
引入方式一(单独文件)
在主题 layout.pug
的底部或 additional-js.pug
中添加:
script(src='/js/music.js')
引入方式二(合并代码)
将 music.js
中的代码直接复制到 main.js
或其他已加载的脚本文件中。
🔗 集成流程总结
- 将
_config.liushen.yml
添加到 项目根目录 并配置capsule.enable: true
。 - 将
music.pug
放入blog/themes/liushen/layout/includes/third-party/
。 - 编辑
layout.pug
,在底部添加include ./third-party/music.pug
(带条件判断)。 - 将
music-capsule.styl
放入blog/themes/liushen/source/css/__layout/
。 - 将
music.js
放入 JS 目录并在页面加载时引入,或者可以添加到任意js文件中。 - 根据需要修改 CSS 变量配色。
1 | # 页脚音乐胶囊 |
2 | capsule: |
3 | enable: true |
4 | # 歌单 ID / 单曲 ID |
5 | id: 13597135963 |
6 | # 服务商:netease / qq / xiami / kugou / baidu |
7 | server: netease |
8 | # 类型:playlist / song |
9 | type: playlist |
10 | meting_api: https://met.example.com/api?server=:server&type=:type&id=:id&r=:r |
11 | volume: 0.8 |
1 | @keyframes changeright |
2 | 0%, 50%, 100% |
3 | transform: rotate(0deg) scale(1.1) |
4 | box-shadow: 0 0 2px #ffffff00 |
5 | 25%, 75% |
6 | transform: rotate(90deg) scale(1.1) |
7 | box-shadow: 0 0 14px #ffffff |
8 | |
9 | @keyframes playingShadow |
10 | 0%, 100% |
11 | box-shadow: 0 0px 12px -3px #00000000 |
12 | 50% |
13 | box-shadow: 0 0px 12px 0px var(--default-bg-color) |
14 | |
15 | @keyframes lightBar |
16 | 0%, 100% |
17 | opacity: 0.1 |
18 | 60% |
19 | opacity: 0.3 |
20 | |
21 | .aplayer.aplayer-narrow |
22 | .aplayer-body, |
23 | .aplayer-pic |
24 | height: 66px |
25 | width: 66px |
26 | |
27 | #nav-music |
28 | display: flex |
29 | align-items: center |
30 | position: fixed |
31 | z-index: 10000 |
32 | bottom: 10px |
33 | left: 10px |
34 | cursor: pointer |
35 | transition: all 0.5s, left 0s |
36 | transform-origin: left bottom |
37 | box-shadow: var(--liushen-nav-shadow) |
38 | border-radius: 40px |
39 | overflow: hidden |
40 | |
41 | &:active |
42 | transform: scale(0.97) |
43 | |
44 | &.playing |
45 | border: var(--liushen-card-border) |
46 | box-shadow: 0 0px 12px -3px #00000000 |
47 | animation: playingShadow 5s linear infinite |
48 | |
49 | .aplayer.aplayer-withlrc |
50 | .aplayer-pic |
51 | box-shadow: 0 0 14px #ffffffa6 |
52 | transform: rotate(0deg) scale(1.1) |
53 | border-color: white |
54 | animation-play-state: running |
55 | |
56 | .aplayer-info |
57 | color: white |
58 | |
59 | #nav-music-hoverTips |
60 | width: 0 |
61 | |
62 | .aplayer |
63 | background: var(--default-bg-color) |
64 | border: var(--liushen-card-border) |
65 | backdrop-filter: saturate(180%) blur(20px) |
66 | transform: translateZ(0) |
67 | |
68 | .aplayer .aplayer-info .aplayer-controller .aplayer-bar-wrap .aplayer-bar .aplayer-played |
69 | animation-play-state: running |
70 | |
71 | &:hover:not(.playing) #nav-music-hoverTips |
72 | opacity: 1 |
73 | |
74 | .aplayer.aplayer-withlrc |
75 | .aplayer-pic |
76 | height: 25px |
77 | width: 25px |
78 | border-radius: 40px |
79 | z-index: 1 |
80 | transition: 0.3s |
81 | transform: rotate(0deg) scale(1) |
82 | border: var(--liushen-card-border) |
83 | animation: changeright 24s linear infinite |
84 | animation-play-state: paused |
85 | |
86 | .aplayer-info |
87 | height: 100% |
88 | color: var(--liushen-text) |
89 | margin: 0 |
90 | padding: 0 |
91 | display: flex |
92 | align-items: center |
93 | |
94 | #nav-music-hoverTips |
95 | color: white |
96 | background: var(--default-bg-color) |
97 | width: 100% |
98 | height: 100% |
99 | position: absolute |
100 | top: 0 |
101 | left: 0 |
102 | align-items: center |
103 | justify-content: center |
104 | display: flex |
105 | border-radius: 40px |
106 | opacity: 0 |
107 | font-size: 12px |
108 | z-index: 2 |
109 | transition: 0.3s |
110 | |
111 | .aplayer |
112 | background: var(--liushen-card-bg) |
113 | border-radius: 60px |
114 | height: 41px |
115 | display: flex |
116 | margin: 0 |
117 | transition: 0.3s |
118 | border: var(--liushen-card-border) |
119 | box-shadow: none |
120 | |
121 | .aplayer-notice, |
122 | .aplayer-miniswitcher, |
123 | .aplayer-list |
124 | display: none |
125 | |
126 | .aplayer-body |
127 | position: relative |
128 | display: flex |
129 | align-items: center |
130 | min-width: 180px |
131 | |
132 | .aplayer-info |
133 | .aplayer-music |
134 | margin: 0 |
135 | display: flex |
136 | align-items: center |
137 | padding: 0 12px 0 8px |
138 | cursor: pointer |
139 | z-index: 1 |
140 | height: 100% |
141 | |
142 | .aplayer-title |
143 | cursor: pointer |
144 | line-height: 1 |
145 | display: inline-block |
146 | white-space: nowrap |
147 | max-width: 120px |
148 | overflow: hidden |
149 | text-overflow: ellipsis |
150 | transition: 0.3s |
151 | user-select: none |
152 | |
153 | .aplayer-controller |
154 | position: absolute |
155 | width: 100% |
156 | height: 100% |
157 | top: 0 |
158 | left: 0 |
159 | |
160 | .aplayer-bar-wrap |
161 | margin: 0 |
162 | padding: 0 |
163 | |
164 | .aplayer-bar |
165 | height: 100% |
166 | background: 0 0 |
167 | |
168 | .aplayer-loaded |
169 | display: none |
170 | |
171 | .aplayer-played |
172 | height: 100% |
173 | opacity: 0.1 |
174 | background-color: white !important |
175 | animation: lightBar 5s ease infinite |
176 | animation-play-state: paused |
177 | |
178 | .aplayer-pic |
179 | pointer-events: none |
180 | |
181 | .aplayer-button |
182 | bottom: 50% |
183 | right: 50% |
184 | transform: translate(50%, 50%) |
185 | margin: 0 |
186 | transition: 0.3s |
187 | pointer-events all |
188 | |
189 | &:has(.aplayer-button.aplayer-play) |
190 | animation-play-state: paused |
191 | transform: rotate(0deg) scale(1) !important |
192 | |
193 | margin-left: 8px |
194 | |
195 | .aplayer-info .aplayer-controller .aplayer-time, |
196 | .aplayer-info .aplayer-music .aplayer-author |
197 | display: none |
198 | |
199 | &.aplayer-withlist .aplayer-info |
200 | border: none |
201 | |
202 | .aplayer-lrc |
203 | width: 0 |
204 | opacity: 0 |
205 | transition: 0.3s |
206 | margin-bottom: -26px |
207 | |
208 | p.aplayer-lrc-current |
209 | color: white |
210 | border: none |
211 | min-height: 20px |
212 | filter: none |
213 | |
214 | &:after, |
215 | &:before |
216 | display: none |
217 | |
218 | p |
219 | color: #ffffffb3 |
220 | filter: blur(.8px) |
221 | |
222 | @media screen and (min-width: 600px) |
223 | #nav-music.stretch .aplayer.aplayer-withlrc .aplayer-lrc |
224 | width: 200px |
225 | margin-left: 8px |
226 | opacity: 1 |
227 | |
228 | .aplayer-thumb |
229 | width: 0 !important |
230 | height: 0 !important |
231 |
1 | const liuMusic = { |
2 | musicPlaying: false, |
3 | isMusicBind: false, |
4 | |
5 | musicToggle(isMeting = true) { |
6 | if (!this.isMusicBind) { |
7 | this.musicBind(); |
8 | } |
9 | |
10 | const $music = document.querySelector("#nav-music"); |
11 | const $meting = document.querySelector("meting-js"); |
12 | const $console = document.getElementById("consoleMusic"); |
13 | |
14 | this.musicPlaying = !this.musicPlaying; |
15 | $music?.classList.toggle("playing", this.musicPlaying); |
16 | $music?.classList.toggle("stretch", this.musicPlaying); |
17 | $console?.classList.toggle("on", this.musicPlaying); |
18 | |
19 | if (isMeting) { |
20 | this.musicPlaying ? $meting?.aplayer?.play() : $meting?.aplayer?.pause(); |
21 | } |
22 | }, |
23 | |
24 | musicBind() { |
25 | const $music = document.querySelector("#nav-music"); |
26 | const $name = document.querySelector("#nav-music .aplayer-music"); |
27 | const $button = document.querySelector("#nav-music .aplayer-button"); |
28 | |
29 | $name?.addEventListener("click", () => { |
30 | $music?.classList.toggle("stretch"); |
31 | }); |
32 | |
33 | $button?.addEventListener("click", () => { |
34 | this.musicToggle(false); |
35 | }); |
36 | |
37 | this.isMusicBind = true; |
38 | } |
39 | }; |
1 | div.needEndHide#nav-music |
2 | #nav-music-hoverTips(onclick='liuMusic.musicToggle()')= __('music.hit') |
3 | meting-js(id=theme.capsule.id server=theme.capsule.server type=theme.capsule.type mutex="true" preload="none" data-lrctype="0" order="random" volume=theme.capsule.volume api=theme.capsule.meting_api) |
4 |