一、觀前說明:
1.本人為新手,很多地方可能寫得不好,歡迎指正,
2.本人仍在學習CSS中,在本篇中若有寫得不好的地方,歡迎指正,
3.本人尚未系統性的學習過JS(還沒學到),在這里用到的JS全是靠以前學其他語言積累下的基礎,因此在很多地方也會寫得不夠好,歡迎指正,
4.因HTML部分和CSS部分較為簡單,本篇文章會更注重于講JS部分,其中一些說明我會寫在代碼里面,我認為這比我寫在外面更好理解,
5.我是將整個播放器寫完了才寫的這篇文章,所以你們在看代碼的時候,會看到一些提前寫了的代碼,比如播放暫停還沒講到修改時間間距,但在代碼處已經有了,
二、最終效果
2.1、包含功能
1.播放暫停
2.歌詞影片,顯示當前歌詞
3.拖拽歌詞,調整歌詞進度
4.進度條調整歌詞進度
5.音量控制
2.2、圖片展示

三、現在開始
1.搭建基礎框架
框架圖:

按照框架圖,搭建框架:
其中歌詞是后面由JS創建的,因此只需要有一個div包住就行
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="css/music.css" />
<link rel="icon" href="img/icon.jpg" />
<title>音樂播放器</title>
</head>
<body>
<!-- 音樂 -->
<audio src="audio/韓安旭 - 不在.mp3" preload=""></audio>
<!-- 背景板 -->
<div class="background">
<!-- 音樂區 -->
<div class="music-background">
<!-- 左側圖片 -->
<div class="img-back">
<img src="img/musicCover2.jpg" alt="韓安旭 - 不在" />
</div>
<!-- 右側歌詞 -->
<div class="lyrics-back">
<!-- 歌曲資訊 -->
<div class="div-title">
<h1></h1>
<p style="margin-right: 100px">
專輯:
<span></span>
</p>
<p>
歌手:
<span></span>
</p>
</div>
<!-- 歌詞資訊 -->
<div class="lyrics-time">
<a href="#" class="goto"></a>
<a href="#" class="goto-time">00:00</a>
</div>
<div class="div-lyrics"></div>
</div>
</div>
<!-- 控制區 -->
<div class="control-back">
<!-- 進度條 -->
<div class="progress-bar">
<div class="progress-all"></div>
<div class="progress-now"></div>
</div>
<span class="time">00:00/00:00</span>
<!-- 控制按鈕 -->
<div class="control">
<!-- 上一首、下一首、暫停播放 -->
<div class="control-btn">
<a href="#" class="up"></a>
<a href="#" class="play-pause" onclick="setPlay()"></a>
<a href="#" class="down"></a>
</div>
<!-- 播放模式、聲音 -->
<div class="control-right">
<a href="#" class="mode"></a>
<a href="#" class="volume" onclick="setMuted()"></a>
<div class="volume-back">
<div class="volume-all"></div>
<div class="volume-now"></div>
</div>
<span class="volume-text">100</span>
</div>
</div>
</div>
</div>
<script src="js/music.js"></script>
</body>
</html>
2.CSS美化
2.1清除所有邊距已經禁止選擇
* {
margin: 0;
padding: 0;
user-select: none;
}
h1 {
font-weight: 500;
}
a {
color: #000;
text-decoration: none;
}
2.2設定背景樣式(大盒子)
/* 背景樣式,相當于body */
.background {
width: 100vw;
height: 100vh;
background-color: #fff;
}
/* 上半部分樣式,即包含封面以及歌詞的盒子 */
.music-background {
width: 1000px;
height: calc(100vh - 200px);
margin: 0 auto;
overflow: hidden;
}
/* 圖片盒子 */
.img-back {
width: 300px;
padding: 0 50px;
margin-top: 150px;
margin-right: 50px;
overflow: hidden;
float: left;
}
/* 歌詞盒子 */
.lyrics-back {
width: 550px;
height: 100%;
overflow: hidden;
float: left;
}
/* 歌曲資訊盒子 */
.div-title {
width: 100%;
height: 100px;
margin-top: 150px;
}
/* 控制區域盒子 */
.control-back {
width: 100vw;
height: 80px;
bottom: 0;
position: absolute;
background-color: rgba(255, 255, 255, 0.8);
}
2.3設定圖片、字體樣式
只是簡簡單單的調整大小邊距等,甚至在歌曲資訊中的文欄位落標簽我都沒有設定字體大小,事實上這并不是一種好的習慣
.img-back img {
width: 300px;
height: 300px;
border-radius: 20px;
}
.div-title h1 {
width: 100%;
height: 50px;
}
.div-title p {
height: 50px;
display: inline-block;
color: rgba(0, 0, 0, 0.5);
}
.div-title span {
color: #000;
}
.div-lyrics p {
font-size: 15px;
line-height: 34px;
}
2.4設定控制按鈕、進度條、音量控制樣式
這里我的控制按鈕用的都是a標簽,因此需要將a標簽修改為行內塊標簽inline-block
其中".lyrics-time"是拖拽歌詞時顯示的調整進度的按鈕,因此在默認狀態下需要隱藏
.lyrics-time a {
display: inline-block;
position: absolute;
display: none;
}
.goto {
width: 20px;
height: 20px;
margin: 7px 15px;
background: url(../img/play.png) no-repeat;
background-position: 50% 50%;
background-size: 20px;
}
.goto-time {
width: 50px;
height: 34px;
margin-left: 550px;
font-size: 13px;
line-height: 34px;
text-align: center;
position: absolute;
color: #5192fe;
}
/* 進度條 */
.progress-bar {
width: calc(100vw - 100px);
margin: 5px 0;
padding: 5px 0;
cursor: pointer;
float: left;
}
/* 當前播放進度 */
.progress-now {
width: 0;
height: 1px;
margin-top: -1px;
background-color: #5192fe;
}
/* 總播放進度 */
.progress-all {
width: 100%;
height: 1px;
background-color: #dee2e6;
}
/* 播放進度 文本 */
.time {
display: inline-block;
width: 90px;
height: 21px;
margin-left: 5px;
text-align: center;
color: rgba(128, 130, 133, 0.8);
cursor: default;
overflow: hidden;
}
/* 控制按鈕 */
.control {
width: 100%;
height: 30px;
margin-top: 19px;
}
.control a {
margin: 0 5px;
display: inline-block;
}
.control a:hover {
opacity: 50%;
}
.control-btn {
width: 130px;
height: 30px;
margin: 0 auto;
}
/* 上一首 */
.up {
width: 30px;
height: 30px;
background: url(../img/up.png) no-repeat;
background-size: 20px;
background-position: 50% 50%;
}
/* 暫停播放 */
.play-pause {
width: 30px;
height: 30px;
background: url(../img/play.png) no-repeat;
background-size: 30px;
background-position: 50% 50%;
}
/* 下一首 */
.down {
width: 30px;
height: 30px;
background: url(../img/down.png) no-repeat;
background-size: 20px;
background-position: 50% 50%;
}
/* 右側控制按鈕 */
.control-right {
/* width: 180px; */
height: 30px;
margin-left: calc(50vw + 150px);
margin-top: -30px;
display: flex;
align-items: center;
}
/* 播放模式 */
.mode {
width: 30px;
height: 30px;
background: url(../img/sequence.png) no-repeat;
background-size: 20px;
background-position: left 50%;
cursor: pointer;
}
/* 聲音控制 */
.volume {
width: 30px;
height: 30px;
background: url(../img/volume.png) no-repeat;
background-size: 20px;
background-position: left 50%;
cursor: pointer;
}
.volume-back {
padding: 5px 0;
cursor: pointer;
}
.volume-all {
width: 100px;
height: 2px;
background-color: #e5e5e5;
}
.volume-now {
width: 100px;
height: 2px;
margin-top: -2px;
max-width: 100px;
background-color: #5192fe;
}
.volume-text {
margin-left: 10px;
font-size: 14px;
}
3.JS部分
3.1首先是宣告一些變量、陣列等,因為這一部分是要多處使用的,因此在最開頭就宣告了
var myAduio = document.getElementsByTagName('audio')[0];
var divLyrics = document.getElementsByClassName('div-lyrics')[0];
var divTitle = document.getElementsByClassName('div-title')[0];
var lyricsTime = document.getElementsByClassName('lyrics-time')[0];
var lyricsTime_a = lyricsTime.getElementsByTagName('a');
var progressTime = document.getElementsByClassName('time')[0];
var nowLine = 0;
var lyricsMove = false;
var playState = false;
var lyrics, lyricsStyle, lyricsFirst, rollT;
var timeArray1 = new Array();
var timeArray2 = new Array();
var timeInterval = new Array();
這里面分別是
audio控制元件,播放音樂用的;
歌詞div,到時候要在這里面創建歌詞
歌曲資訊div,也是在這里面修改歌曲資訊的內容
拖拽歌詞調整進度的div "lyricsTime"以及里面的按鈕 “lyricsTime_a”
進度條右側的時間"progressTime"
當前播放行"nowLine"
拖拽狀態"lyricsMove"
播放狀態"playState"
所有歌詞的p標簽"lyrics",第一行歌詞的樣式"lyricsStyle",第一行歌詞的p標簽"lyricsFirst",歌詞滾動的計時器"rollT"
以秒數記錄每一行歌詞所在時間的陣列"timeArray1"以及以分鐘:秒數的星石記錄每一行歌詞所在時間的陣列"timeArray2",用兩種方式記錄時間的原因是后面需要以這兩種形式交叉使用,也可以只記錄一種,在使用到另一種的時候將其算出來,但我感覺直接記錄兩種會更方便
記錄每一行歌詞時間間距的陣列"timeInterval"
3.2頁面加載完畢后就需要執行的方法
window.onload = function () {
initialLyrics();
lyricsStyle = getComputedStyle(lyricsFirst, null);
setLyrics(0);
setMouseEvent();
setTimeText();
};
3.3初始化歌詞(創建歌詞并存盤一些資訊)
sp、ar、ti分別為專輯、藝術家、歌名
function initialLyrics() {
let sp = divTitle.getElementsByTagName('span')[0];
let ar = divTitle.getElementsByTagName('span')[1];
let ti = divTitle.getElementsByTagName('h1')[0];
let lyricsData, timeString;
let lyricsArray = new Array();
// 清除陣列,先清除陣列可以保證每一次存盤的資訊無誤
timeArray1.splice(0, timeArray1.length);
timeArray2.splice(0, timeArray2.length);
lyricsArray.splice(0, lyricsArray.length);
// 按相同格式放入歌詞更換歌曲即可達到相同效果
lyricsData =
'[ar]韓安旭\n[ti]不在\n[sp]不在\n[00:00.74]韓安旭 - 不在\n[00:01.76]詞:尤雅琪\n[00:02.76]曲:勝嶼\n[00:15.62]我累了就緊緊鎖住情緒\n[00:18.11]不再放任它堆積\n[00:22.14]我痛了就靜靜屏住呼吸\n[00:26.02]不給想念留余地\n[00:28.88]只是下雨時會委屈\n[00:32.80]只是想起你會哭泣\n[00:36.77]沒關系 真沒關系\n[00:44.28]我終于學會一個人彈琴\n[00:47.12]只是彈琴沒有你\n[00:49.29]我終于學會一個人做夢\n[00:54.85]只是做夢沒有你\n[00:57.73]我依舊像從前粗心\n[01:01.09]時常會忘記星期幾\n[01:05.00]卻始終忘不掉你看我的眼睛\n[01:11.71]穿過了熙攘的人海\n[01:15.11]想找誰能把你取代\n[01:19.62]復制你曾給過我的\n[01:21.44]那種寵愛\n[01:26.32]掏空了回憶的腦海\n[01:30.75]寂寞卻狠狠撲過來\n[01:33.69]措手不及 無法躲開\n[01:41.52]我承認是我太依賴\n[01:44.92]像個不懂事的小孩\n[01:48.35]揮霍掉我們的未來\n[01:51.22]才醒過來\n[01:55.15]我承認后悔了傷害\n[01:59.06]拋開你的好我的壞\n[02:02.14]直到如今學會忍耐 你不在\n[02:26.95]我終于學會一個人彈琴\n[02:29.33]只是彈琴沒有你\n[02:33.26]我終于學會一個人做夢\n[02:36.64]只是做夢沒有你\n[02:39.53]我依舊像從前粗心\n[02:42.90]時常會忘記星期幾\n[02:46.82]卻始終忘不掉你看我的眼睛\n[02:53.62]穿過了熙攘的人海\n[02:57.09]想找誰能把你取代\n[03:00.98]復制你曾給過我的\n[03:05.43]那種寵愛\n[03:08.25]掏空了回憶的腦海\n[03:11.67]寂寞卻狠狠撲過來\n[03:15.56]措手不及 無法躲開\n[03:22.49]我承認是我太依賴\n[03:26.37]像個不懂事的小孩\n[03:30.38]揮霍掉我們的未來\n[03:33.80]才醒過來\n[03:37.81]我承認后悔了傷害\n[03:41.29]拋開你的好我的壞\n[03:44.73]直到如今學會忍耐 你不在';
// 文本.split('分隔符'),用于分割文本
lyricsArray = lyricsData.split('\n');
// 添加歌曲資訊
ar.innerText = lyricsArray[0].split(']')[1];
ti.innerText = lyricsArray[1].split(']')[1];
sp.innerText = lyricsArray[2].split(']')[1];
// 添加歌詞
for (var i = 3; i < lyricsArray.length; i++) {
//這里i=3即我們的第一行歌詞,將高度設定為100%,在頁面創建完畢時添加一個上滾的影片
if (i == 3) {
divLyrics.innerHTML +=
'<p style="margin-top: 100%;color:#5192fe;">' +
lyricsArray[i].split(']')[1] +
'</p>';
} else {
divLyrics.innerHTML +=
'<p>' + lyricsArray[i].split(']')[1] + '</p>';
}
}
// 獲取后續需要使用的變數
lyricsFirst = divLyrics.getElementsByTagName('p')[0];
lyrics = divLyrics.getElementsByTagName('p');
// 計算每局歌詞所在的秒數
timeArray1.push(0);
for (var i = 0; i < lyrics.length; i++) {
timeString = lyricsArray[i + 3].substring(1, 9).split(':');
timeArray1.push(
parseFloat(timeString[0]) * 60 + parseFloat(timeString[1])
);
}
// 計算時間間隔,將時間從秒數改為分鐘+秒數
for (var i = 0; i < timeArray1.length - 1; i++) {
timeInterval[i] = timeArray1[i + 1] - timeArray1[i];
// timeArray2陣列主要是顯示使用的,在秒數部分,若為個位數,十位數補零會更為美觀
if (Math.floor(timeArray1[i] % 60) < 10) {
timeArray2.push(
Math.floor(timeArray1[i] / 60) +
':0' +
Math.floor(timeArray1[i] % 60)
);
} else {
timeArray2.push(
Math.floor(timeArray1[i] / 60) +
':' +
Math.floor(timeArray1[i] % 60)
);
}
}
}
歌詞初始化完后,整個頁面就可以正常顯示了,接下來先寫控制播放暫停的功能
3.3播放暫停
// 設定播放狀態
function setPlay(state) {
var play_pause = document.getElementsByClassName('play-pause')[0];
if (state == null) {
// 如果歌曲為暫停狀態,那么獲取到的state則為true,將state設定為true我們就播放
state = myAduio.paused;
}
// 清除計時器,不然會出現多個計時器同時進行
clearTimeout(rollT);
if (state == true) {
myAduio.play();
play_pause.style.backgroundImage = 'url(../img/pause.png)';
playState = true;
// 開始播放的同時,同時開始設定時間進度文本,歌詞滾動以及進度條位置
setTimeText();
lyricsRoll();
setProgress();
} else {
myAduio.pause();
play_pause.style.backgroundImage = 'url(../img/play.png)';
playState = false;
// 暫停后重新修改計時器時間,這里后面會詳細講
timeInterval[nowLine] = timeArray1[nowLine + 1] - myAduio.currentTime;
}
}
3.4設定時間進度文本
// 設定進度文本
function setTimeText() {
var nowTime = myAduio.currentTime;
var allTime = myAduio.duration;
// 計算時間,若為個位數,補0
if (Math.floor(nowTime % 60) < 10) {
nowTime = Math.floor(nowTime / 60) + ':0' + Math.floor(nowTime % 60);
} else {
nowTime = Math.floor(nowTime / 60) + ':' + Math.floor(nowTime % 60);
}
if (Math.floor(allTime % 60) < 10) {
allTime = Math.floor(allTime / 60) + ':0' + Math.floor(allTime % 60);
} else {
allTime = Math.floor(allTime / 60) + ':' + Math.floor(allTime % 60);
}
progressTime.innerText = nowTime + '/' + allTime;
// 每0.1秒執行一次
if (myAduio.paused == false) {
setTimeout(setTimeText, 100);
}
}
3.5設定進度條位置
// 設定進度條進度
function setProgress() {
let progress_now = document.getElementsByClassName('progress-now')[0];
let progress_bar = document.getElementsByClassName('progress-bar')[0];
let progress = Math.floor(
(myAduio.currentTime / myAduio.duration) * progress_bar.clientWidth
);
progress_now.style.width = progress + 'px';
if (myAduio.paused == false) {
setTimeout(setProgress, 100);
}
}
到這里,除了歌詞滾動、調整進度等,一個最簡單的音樂播放器就已經完成了
3.6設定歌詞位置
// 設定歌詞位置
function setLyrics(line) {
// 將當前歌詞高亮,其余歌詞都改為黑色
for (let i = 0; i < lyrics.length; i++) {
lyrics[i].style.color = '#000';
}
lyrics[line].style.color = '#5192fe';
// 設定影片,這里只需要改變第一行歌詞的位置即可
// 這里是將第一行歌詞的marginTop從當前的位置修改為當前進度歌詞所在行的對應位置,因為我將p標簽的行高設定為了34px,而我要將當前歌詞在第五行顯示,即前面要有四行空的,可以得到34*4=136px的marginTop,line * (-34) + 136即我需要向上移動多少行
// 假設我的當前歌詞是在第一行顯示,當前歌詞在第一行(陣列從零開始,所以這里在陣列里是0),那么marginTop就是0,即0 * (-34),當前歌詞在第二行,那么marginTop就是-34px,即1 * (-34),往后以此類推
// 同上,比如當前歌詞在第一行,但是我需要將第一行歌詞顯示在第五行,即前面有四個空的位置,那么我第一行歌詞就需要將marginTop修改到第五行的位置,那么第一行歌詞的marginTop就是136px,即0 * (-34) +136
let lyrics_animation = lyrics[0].animate(
[
{
marginTop: lyricsStyle.marginTop,
},
{
marginTop: line * -34 + 136 + 'px',
},
],
{
duration: 100,
}
);
//設定監視器,在影片完成之后,修改第一行歌詞的marginTop,這里不用"fill:forwards"的原因是使用后,拖拽歌詞將無法使用
lyrics_animation.addEventListener(
'finish',
function () {
lyrics[0].style.marginTop = line * -34 + 136 + 'px';
},
false
);
}
設定歌詞寫好了,我們現在就需要考慮如何讓歌詞滾動起來,即讓歌詞自動到達當前行的位置
我想到的方法有兩種
第一種方法是根據每句歌詞之間的時間間隔,每過一個間隔,就觸發一次方法,我這里用的就是這種方法;
第二種方法是每過很短的一段時間,就觸發一次方法,就跟進度條類似;
我觀察過酷狗以及QQ音樂,其中酷狗用的應該就是第一種方法,是過一個間隔觸發一次的,而QQ音樂則是用的第二種方法,
我個人認為第二種方法寫起來更簡單,但是需要的性能更高,但是進度條、以及時間進度文本是必須使用第二種方法的(至少我想不到其他的),那么如果將進度條、時間進度文本、歌詞滾動這三個方法放在一個計時器中呼叫,而不是像我這樣分開三個計時器,哪一種是更優的方法就不得而知了,歡迎各位大佬給出自己的看法,
那么我們這里用第一種方法,首先我們先分析一下需要記錄的內容(其實前面已經記錄了,只是在這里才說)
第一個是要記錄歌詞,這個不需要多的解釋,顯示用的
第二個需要記錄時間間隔,這個是用來設定計時器的
第三個需要記錄每一行歌詞所在的時間,分兩個記錄,一個按秒數記錄,用來拖拽歌詞時調整進度;另一個按分:秒記錄,用于拖拽歌詞時顯示時間;兩個陣列可只記錄一個,用到另一個的時候算出來就好,我為了方便就記錄了兩個
前面三個都是要用陣列記錄,第四個則是非陣列,用于記錄當前歌詞到了哪一行,在歌詞滾動中,歌詞回彈等地方都會用到,
需要記錄的東西我們分析完了,那么接下來在畫一個邏輯圖來分析計時器要如何設定,即歌詞滾動要如何實作

分析完畢,那么繼續
3.7歌詞滾動
// 歌詞滾動
// 歌詞滾動的方法只需要按照時間間隔設定計時器即可
function lyricsRoll() {
rollT = setTimeout(function () {
if (nowLine < lyrics.length && myAduio.paused == false) {
if (lyricsMove == false) {
setLyrics(nowLine);
}
nowLine += 1;
lyricsRoll();
}
}, timeInterval[nowLine] * 1000);
}
// 以下代碼不在這個地方,在設定播放狀態處,只是放在這里做解釋
// 每當我們暫停歌曲時,我們就需要將當前行到下一行即從i到i+1的時間間距,從原來的,修改為剩余的,比如本來跳到下一行需要10s,而我是在第5s的時候暫停的,那么剩余時間就是5s,但我們無法直接知道在這一行歌詞我們用了多少時間,所以我們就要用下一行歌詞的時間(不是間距,而是在整首歌中這一行歌詞在哪一秒)減去當前的播放進度,這樣得到的值就是我們需要的剩余間距,所以代碼是
timeInterval[nowLine] = timeArray1[nowLine + 1] - myAduio.currentTime;
到這里,播放器的歌詞滾動部分已經寫好了,但現在的播放器只能播放暫停,看到歌詞在哪,不能調整進度等,接下來我們就來解決這個問題
3.8歌詞拖拽、音量控制、調整進度
function setMouseEvent() {
// 歌詞拖拽
let lyrics_Y, line;
// 此處是調整歌詞位置,以及計算用戶將歌詞拖拽到了哪一行
divLyrics.onmousedown = function (e) {
if (lyricsMove == false) {
lyricsTime_a[0].style.display = lyricsTime_a[1].style.display = lyricsTime.style.display =
'block';
lyricsMove = true;
}
lyrics_Y = parseInt(lyricsStyle.marginTop);
document.onmousemove = function (event) {
lyricsFirst.style.marginTop =
event.clientY - (e.clientY - lyrics_Y) + 'px';
line = Math.floor(-(parseInt(lyricsStyle.marginTop) - 170) / 34);
if (line < 0) {
line = 0;
} else if (line > lyrics.length - 1) {
line = lyrics.length - 1;
}
lyricsTime_a[1].innerText = timeArray2[line];
};
document.onmouseup = function () {
// Y1的作用是判斷用戶是否仍處于拖拽狀態,若一秒后歌詞位置仍等于Y1,則判斷為非拖拽狀態,但這樣寫有一個bug就是如果只是按住滑鼠不進行拖拽,則也會判斷為非拖拽狀態,暫時想不到解決方法
var lyrics_Y1 = parseInt(lyricsStyle.marginTop);
setTimeout(function () {
if (parseInt(lyricsStyle.marginTop) == lyrics_Y1) {
lyricsMove = false;
setLyrics(nowLine - 1);
lyricsTime_a[0].style.display = lyricsTime_a[1].style.display = lyricsTime.style.display =
'none';
}
}, 1000);
// 清除滑鼠移動方法的同時也一定要清除滑鼠彈起方法,不然每次點擊頁面都會呼叫這個方法
document.onmousemove = null;
document.onmouseup = null;
};
// 防止選中文字
return false;
};
// 音量控制
// 音量控制沒什么好說的,需要注意的是volume的音量是從0-1,所以在設定音量時要將其除以100
let volume_now = document.getElementsByClassName('volume-now')[0];
let volume_back = document.getElementsByClassName('volume-back')[0];
let volume_text = document.getElementsByClassName('volume-text')[0];
let volume_a = document.getElementsByClassName('volume')[0];
volume_back.onmousedown = function (e) {
volume_now.style.width = e.offsetX + 'px';
myAduio.volume = e.offsetX / 100;
volume_text.innerText = volume_now.clientWidth;
volume_back.onmousemove = function (ev) {
let volume = ev.offsetX;
if (volume > 100) {
volume = 100;
}
volume_now.style.width = volume + 'px';
myAduio.volume = volume / 100;
volume_text.innerText = volume_now.clientWidth;
};
document.onmouseup = function () {
// 如果音量為0,更換靜音圖片,否則更換非靜音圖片
if (myAduio.volume == 0) {
volume_a.style.backgroundImage = 'url(../img/mute.png)';
} else {
volume_a.style.backgroundImage = 'url(../img/volume.png)';
}
volume_back.onmousemove = null;
document.onmouseup = null;
};
return false;
};
// 進度控制
// 進度控制跟音量控制一樣,只需要注意演算法就好
// 從之前的設定進度條進度我們可用知道,進度條的寬度=當前播放進度/歌曲總時長*進度條總長
// 所以 當前播放進度=進度條寬度/進度條總長*歌曲總時長
let progress_now = document.getElementsByClassName('progress-now')[0];
let progress_bar = document.getElementsByClassName('progress-bar')[0];
progress_bar.onmousedown = function (e) {
progress_now.style.width = e.offsetX + 'px';
myAduio.pause();
myAduio.currentTime =
(e.offsetX * myAduio.duration) / progress_bar.clientWidth;
setTimeText();
progress_bar.onmousemove = function (ev) {
let progress = ev.offsetX;
if (progress > progress_bar.clientWidth) {
progress = progress_bar.clientWidth;
}
progress_now.style.width = progress + 'px';
myAduio.currentTime =
(progress * myAduio.duration) / progress_bar.clientWidth;
setTimeText();
};
document.onmouseup = function () {
myAduio.play();
for (var i = 0; i < timeArray1.length; i++) {
if (myAduio.currentTime < timeArray1[i]) {
// 獲取調整進度后當前處于哪一行歌詞,假設我調整后的進度是1s,那么在陣列中第一個大于1s的的上一個,就是當前行,所以需要-1
// nowLine是i - 1 但是設定歌詞位置卻是 i - 2 的原因,因為我們的timeArray1是在開頭多加了一個元素的,也就是說timeArray1陣列比歌詞陣列多了一個元素,所以要再-1,而在lyricsRoll方法中,我是先設定計時器再將nowLine+1的,所以這里的nowLine要+1,那么總體結果就是,nowLine = i -1 , 設定歌詞為 i - 2
nowLine = i - 1;
setLyrics(i - 2);
timeInterval[nowLine] =
timeArray1[nowLine + 1] - myAduio.currentTime;
setPlay(true);
break;
}
}
progress_bar.onmousemove = null;
document.onmouseup = null;
};
return false;
};
// 拖拽歌詞調整進度,需要注意的也只是算出用戶拖到了第幾行而已,在拖拽歌詞事件中(在上面)我們就已經將行數算出來了,這里直接呼叫就可用
// 也說一下演算法,因為我們是按照行來設定第一行歌詞的marginTop的,那么反推過來就是
// 行數=-(第一行歌詞的marginTop-136)/34
// 同樣的,因為timeArray2陣列多一個元素,所以顯示拖拽歌詞時顯示的歌曲進度就要+1,我這里設定歌詞位置是line - 1的原因是我在算行數的時候,將136改為了170,即我在算行數的時候就已經+1了
let goto = document.getElementsByClassName('goto')[0];
goto.onmouseup = function () {
nowLine = line;
myAduio.currentTime = timeArray1[line];
setLyrics(line - 1);
setPlay(true);
lyricsMove = false;
lyricsTime_a[0].style.display = lyricsTime_a[1].style.display = lyricsTime.style.display =
'none';
document.onmouseup = null;
};
}
3.9設定靜音
這個比較簡單,就不說了
// 設定靜音
function setMuted() {
let volume_now = document.getElementsByClassName('volume-now')[0];
let volume_text = document.getElementsByClassName('volume-text')[0];
let volume_a = document.getElementsByClassName('volume')[0];
if (myAduio.muted == true) {
myAduio.muted = false;
volume_a.style.backgroundImage = 'url(../img/volume.png)';
volume_now.style.width = myAduio.volume * 100 + 'px';
volume_text.innerText = myAduio.volume * 100;
} else {
myAduio.muted = true;
volume_a.style.backgroundImage = 'url(../img/mute.png)';
volume_now.style.width = '0';
volume_text.innerText = '0';
}
}
到這里,我們的整個播放器就已經全部完成了
以下是完整代碼
HTML
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="css/music.css" />
<link rel="icon" href="img/icon.jpg" />
<title>音樂播放器</title>
</head>
<body>
<!-- 音樂 -->
<audio src="audio/韓安旭 - 不在.mp3" preload=""></audio>
<!-- 背景板 -->
<div class="background">
<!-- 音樂區 -->
<div class="music-background">
<!-- 左側圖片 -->
<div class="img-back">
<img src="img/musicCover2.jpg" alt="韓安旭 - 不在" />
</div>
<!-- 右側歌詞 -->
<div class="lyrics-back">
<!-- 歌曲資訊 -->
<div class="div-title">
<h1></h1>
<p style="margin-right: 100px">
專輯:
<span></span>
</p>
<p>
歌手:
<span></span>
</p>
</div>
<!-- 歌詞資訊 -->
<div class="lyrics-time">
<a href="#" class="goto"></a>
<a href="#" class="goto-time">00:00</a>
</div>
<div class="div-lyrics"></div>
</div>
</div>
<!-- 控制區 -->
<div class="control-back">
<!-- 進度條 -->
<div class="progress-bar">
<div class="progress-all"></div>
<div class="progress-now"></div>
</div>
<span class="time">00:00/00:00</span>
<!-- 控制按鈕 -->
<div class="control">
<!-- 上一首、下一首、暫停播放 -->
<div class="control-btn">
<a href="#" class="up"></a>
<a href="#" class="play-pause" onclick="setPlay()"></a>
<a href="#" class="down"></a>
</div>
<!-- 播放模式、聲音 -->
<div class="control-right">
<a href="#" class="mode"></a>
<a href="#" class="volume" onclick="setMuted()"></a>
<div class="volume-back">
<div class="volume-all"></div>
<div class="volume-now"></div>
</div>
<span class="volume-text">100</span>
</div>
</div>
</div>
</div>
<script src="js/music.js"></script>
</body>
</html>
CSS
* {
margin: 0;
padding: 0;
user-select: none;
}
h1 {
font-weight: 500;
}
a {
color: #000;
text-decoration: none;
}
/* 背景板 */
.background {
width: 100vw;
height: 100vh;
background-color: #fff;
}
/* 歌詞封面背景 */
.music-background {
width: 1000px;
height: calc(100vh - 200px);
margin: 0 auto;
overflow: hidden;
}
/* 封面背景 */
.img-back {
width: 300px;
padding: 0 50px;
margin-top: 150px;
margin-right: 50px;
overflow: hidden;
float: left;
}
.img-back img {
width: 300px;
height: 300px;
border-radius: 20px;
}
/* 歌詞背景 */
.lyrics-back {
width: 550px;
height: 100%;
overflow: hidden;
float: left;
}
/* 歌曲資訊 */
.div-title {
width: 100%;
height: 100px;
margin-top: 150px;
}
.div-title h1 {
width: 100%;
height: 50px;
}
.div-title p {
height: 50px;
display: inline-block;
color: rgba(0, 0, 0, 0.5);
}
.div-title span {
color: #000;
}
.div-lyrics {
height: calc(100vh - 450px);
overflow: hidden;
position: relative;
}
.div-lyrics p {
font-size: 15px;
line-height: 34px;
}
/* 歌詞進度 */
.lyrics-time {
width: 650px;
height: 34px;
margin-top: 136px;
margin-left: -50px;
position: absolute;
display: none;
}
.lyrics-time a {
display: inline-block;
position: absolute;
display: none;
}
.goto {
width: 20px;
height: 20px;
margin: 7px 15px;
background: url(../img/play.png) no-repeat;
background-position: 50% 50%;
background-size: 20px;
}
.goto-time {
width: 50px;
height: 34px;
margin-left: 550px;
font-size: 13px;
line-height: 34px;
text-align: center;
position: absolute;
color: #5192fe;
}
/* 控制背景 */
.control-back {
width: 100vw;
height: 80px;
bottom: 0;
position: absolute;
background-color: rgba(255, 255, 255, 0.8);
}
/* 進度條 */
.progress-bar {
width: calc(100vw - 100px);
margin: 5px 0;
padding: 5px 0;
cursor: pointer;
float: left;
}
/* 當前播放進度 */
.progress-now {
width: 0;
height: 1px;
margin-top: -1px;
background-color: #5192fe;
}
/* 總播放進度 */
.progress-all {
width: 100%;
height: 1px;
background-color: #dee2e6;
}
/* 播放進度 文本 */
.time {
display: inline-block;
width: 90px;
height: 21px;
margin-left: 5px;
text-align: center;
color: rgba(128, 130, 133, 0.8);
cursor: default;
overflow: hidden;
}
/* 控制按鈕 */
.control {
width: 100%;
height: 30px;
margin-top: 19px;
}
.control a {
margin: 0 5px;
display: inline-block;
}
.control a:hover {
opacity: 50%;
}
.control-btn {
width: 130px;
height: 30px;
margin: 0 auto;
}
/* 上一首 */
.up {
width: 30px;
height: 30px;
background: url(../img/up.png) no-repeat;
background-size: 20px;
background-position: 50% 50%;
}
/* 暫停播放 */
.play-pause {
width: 30px;
height: 30px;
background: url(../img/play.png) no-repeat;
background-size: 30px;
background-position: 50% 50%;
}
/* 下一首 */
.down {
width: 30px;
height: 30px;
background: url(../img/down.png) no-repeat;
background-size: 20px;
background-position: 50% 50%;
}
/* 右側控制按鈕 */
.control-right {
/* width: 180px; */
height: 30px;
margin-left: calc(50vw + 150px);
margin-top: -30px;
display: flex;
align-items: center;
}
/* 播放模式 */
.mode {
width: 30px;
height: 30px;
background: url(../img/sequence.png) no-repeat;
background-size: 20px;
background-position: left 50%;
cursor: pointer;
}
/* 聲音控制 */
.volume {
width: 30px;
height: 30px;
background: url(../img/volume.png) no-repeat;
background-size: 20px;
background-position: left 50%;
cursor: pointer;
}
.volume-back {
padding: 5px 0;
cursor: pointer;
}
.volume-all {
width: 100px;
height: 2px;
background-color: #e5e5e5;
}
.volume-now {
width: 100px;
height: 2px;
margin-top: -2px;
max-width: 100px;
background-color: #5192fe;
}
.volume-text {
margin-left: 10px;
font-size: 14px;
}
JS
var myAduio = document.getElementsByTagName('audio')[0];
var divLyrics = document.getElementsByClassName('div-lyrics')[0];
var divTitle = document.getElementsByClassName('div-title')[0];
var lyricsTime = document.getElementsByClassName('lyrics-time')[0];
var lyricsTime_a = lyricsTime.getElementsByTagName('a');
var progressTime = document.getElementsByClassName('time')[0];
var nowLine = 0;
var lyricsMove = false;
var playState = false;
var lyrics, lyricsStyle, lyricsFirst, rollT;
var timeArray1 = new Array();
var timeArray2 = new Array();
var timeInterval = new Array();
window.onload = function () {
initialLyrics();
lyricsStyle = getComputedStyle(lyricsFirst, null);
setLyrics(0);
setMouseEvent();
setTimeText();
};
// 設定事件
function setMouseEvent() {
// 歌詞拖拽
let lyrics_Y, line;
divLyrics.onmousedown = function (e) {
if (lyricsMove == false) {
lyricsTime_a[0].style.display = lyricsTime_a[1].style.display = lyricsTime.style.display =
'block';
lyricsMove = true;
}
lyrics_Y = parseInt(lyricsStyle.marginTop);
document.onmousemove = function (event) {
lyricsFirst.style.marginTop =
event.clientY - (e.clientY - lyrics_Y) + 'px';
line = Math.floor(-(parseInt(lyricsStyle.marginTop) - 170) / 34);
if (line < 0) {
line = 0;
} else if (line > lyrics.length - 1) {
line = lyrics.length - 1;
}
lyricsTime_a[1].innerText = timeArray2[line];
};
document.onmouseup = function () {
var lyrics_Y1 = parseInt(lyricsStyle.marginTop);
setTimeout(function () {
if (parseInt(lyricsStyle.marginTop) == lyrics_Y1) {
lyricsMove = false;
setLyrics(nowLine);
lyricsTime_a[0].style.display = lyricsTime_a[1].style.display = lyricsTime.style.display =
'none';
}
}, 1000);
document.onmousemove = null;
document.onmouseup = null;
};
// 防止選中文字
return false;
};
// 音量控制
let volume_now = document.getElementsByClassName('volume-now')[0];
let volume_back = document.getElementsByClassName('volume-back')[0];
let volume_text = document.getElementsByClassName('volume-text')[0];
let volume_a = document.getElementsByClassName('volume')[0];
volume_back.onmousedown = function (e) {
volume_now.style.width = e.offsetX + 'px';
myAduio.volume = e.offsetX / 100;
volume_text.innerText = volume_now.clientWidth;
volume_back.onmousemove = function (ev) {
let volume = ev.offsetX;
if (volume > 100) {
volume = 100;
}
volume_now.style.width = volume + 'px';
myAduio.volume = volume / 100;
volume_text.innerText = volume_now.clientWidth;
};
document.onmouseup = function () {
if (myAduio.volume == 0) {
volume_a.style.backgroundImage = 'url(../img/mute.png)';
} else {
volume_a.style.backgroundImage = 'url(../img/volume.png)';
}
volume_back.onmousemove = null;
document.onmouseup = null;
};
return false;
};
// 進度控制
let progress_now = document.getElementsByClassName('progress-now')[0];
let progress_bar = document.getElementsByClassName('progress-bar')[0];
progress_bar.onmousedown = function (e) {
progress_now.style.width = e.offsetX + 'px';
myAduio.pause();
myAduio.currentTime =
(e.offsetX * myAduio.duration) / progress_bar.clientWidth;
setTimeText();
progress_bar.onmousemove = function (ev) {
let progress = ev.offsetX;
if (progress > progress_bar.clientWidth) {
progress = progress_bar.clientWidth;
}
progress_now.style.width = progress + 'px';
myAduio.currentTime =
(progress * myAduio.duration) / progress_bar.clientWidth;
setTimeText();
};
document.onmouseup = function () {
myAduio.play();
for (var i = 0; i < timeArray1.length; i++) {
if (myAduio.currentTime < timeArray1[i]) {
nowLine = i - 1;
setLyrics(i - 2);
timeInterval[nowLine] =
timeArray1[nowLine + 1] - myAduio.currentTime;
setPlay(true);
break;
}
}
progress_bar.onmousemove = null;
document.onmouseup = null;
};
return false;
};
let goto = document.getElementsByClassName('goto')[0];
goto.onmouseup = function () {
nowLine = line;
myAduio.currentTime = timeArray1[line];
setLyrics(line - 1);
setPlay(true);
lyricsMove = false;
lyricsTime_a[0].style.display = lyricsTime_a[1].style.display = lyricsTime.style.display =
'none';
document.onmouseup = null;
};
}
// 設定播放狀態
function setPlay(state) {
var play_pause = document.getElementsByClassName('play-pause')[0];
if (state == null) {
state = myAduio.paused;
}
clearTimeout(rollT);
if (state == true) {
myAduio.play();
play_pause.style.backgroundImage = 'url(../img/pause.png)';
playState = true;
setTimeText();
lyricsRoll();
setProgress();
// 開始播放后要重新將時間間距改回來,不然下次播放計時器會出錯
timeInterval[nowLine] = timeArray1[nowLine + 1] - timeArray1[nowLine];
} else {
myAduio.pause();
play_pause.style.backgroundImage = 'url(../img/play.png)';
playState = false;
timeInterval[nowLine] = timeArray1[nowLine + 1] - myAduio.currentTime;
}
}
// 設定音量
function setVolume(volume) {
myAduio.volume = volume;
}
// 設定靜音
function setMuted() {
let volume_now = document.getElementsByClassName('volume-now')[0];
let volume_text = document.getElementsByClassName('volume-text')[0];
let volume_a = document.getElementsByClassName('volume')[0];
if (myAduio.muted == true) {
myAduio.muted = false;
volume_a.style.backgroundImage = 'url(../img/volume.png)';
volume_now.style.width = myAduio.volume * 100 + 'px';
volume_text.innerText = myAduio.volume * 100;
} else {
myAduio.muted = true;
volume_a.style.backgroundImage = 'url(../img/mute.png)';
volume_now.style.width = '0';
volume_text.innerText = '0';
}
}
// 歌詞回彈
function lyricsRebound(lyricsTop) {
if (parseInt(lyricsStyle.marginTop) != nowLine * -34 + 136) {
if (lyricsTop == null) {
lyricsTop = nowLine * -34 + 136;
}
let lyrics_animation = lyricsFirst.animate(
[
{
marginTop: lyricsStyle.marginTop,
},
{
marginTop: lyricsTop + 'px',
},
],
{
duration: 500,
}
);
lyrics_animation.addEventListener(
'finish',
function () {
lyricsFirst.style.marginTop = lyricsTop + 'px';
},
false
);
}
}
// 初始化歌詞
function initialLyrics() {
let sp = divTitle.getElementsByTagName('span')[0];
let ar = divTitle.getElementsByTagName('span')[1];
let ti = divTitle.getElementsByTagName('h1')[0];
let lyricsData, timeString;
let lyricsArray = new Array();
// 清除陣列
timeArray1.splice(0, timeArray1.length);
timeArray2.splice(0, timeArray2.length);
lyricsArray.splice(0, lyricsArray.length);
// 按相同格式放入歌詞更換歌曲即可達到相同效果
lyricsData =
'[ar]韓安旭\n[ti]不在\n[sp]不在\n[00:00.74]韓安旭 - 不在\n[00:01.76]詞:尤雅琪\n[00:02.76]曲:勝嶼\n[00:15.62]我累了就緊緊鎖住情緒\n[00:18.11]不再放任它堆積\n[00:22.14]我痛了就靜靜屏住呼吸\n[00:26.02]不給想念留余地\n[00:28.88]只是下雨時會委屈\n[00:32.80]只是想起你會哭泣\n[00:36.77]沒關系 真沒關系\n[00:44.28]我終于學會一個人彈琴\n[00:47.12]只是彈琴沒有你\n[00:49.29]我終于學會一個人做夢\n[00:54.85]只是做夢沒有你\n[00:57.73]我依舊像從前粗心\n[01:01.09]時常會忘記星期幾\n[01:05.00]卻始終忘不掉你看我的眼睛\n[01:11.71]穿過了熙攘的人海\n[01:15.11]想找誰能把你取代\n[01:19.62]復制你曾給過我的\n[01:21.44]那種寵愛\n[01:26.32]掏空了回憶的腦海\n[01:30.75]寂寞卻狠狠撲過來\n[01:33.69]措手不及 無法躲開\n[01:41.52]我承認是我太依賴\n[01:44.92]像個不懂事的小孩\n[01:48.35]揮霍掉我們的未來\n[01:51.22]才醒過來\n[01:55.15]我承認后悔了傷害\n[01:59.06]拋開你的好我的壞\n[02:02.14]直到如今學會忍耐 你不在\n[02:26.95]我終于學會一個人彈琴\n[02:29.33]只是彈琴沒有你\n[02:33.26]我終于學會一個人做夢\n[02:36.64]只是做夢沒有你\n[02:39.53]我依舊像從前粗心\n[02:42.90]時常會忘記星期幾\n[02:46.82]卻始終忘不掉你看我的眼睛\n[02:53.62]穿過了熙攘的人海\n[02:57.09]想找誰能把你取代\n[03:00.98]復制你曾給過我的\n[03:05.43]那種寵愛\n[03:08.25]掏空了回憶的腦海\n[03:11.67]寂寞卻狠狠撲過來\n[03:15.56]措手不及 無法躲開\n[03:22.49]我承認是我太依賴\n[03:26.37]像個不懂事的小孩\n[03:30.38]揮霍掉我們的未來\n[03:33.80]才醒過來\n[03:37.81]我承認后悔了傷害\n[03:41.29]拋開你的好我的壞\n[03:44.73]直到如今學會忍耐 你不在';
// 文本.split('分隔符'),用于分割文本
lyricsArray = lyricsData.split('\n');
// 添加歌曲資訊
ar.innerText = lyricsArray[0].split(']')[1];
ti.innerText = lyricsArray[1].split(']')[1];
sp.innerText = lyricsArray[2].split(']')[1];
// 添加歌詞
for (var i = 3; i < lyricsArray.length; i++) {
if (i == 3) {
divLyrics.innerHTML +=
'<p style="margin-top: 100%;color:#5192fe;">' +
lyricsArray[i].split(']')[1] +
'</p>';
} else {
divLyrics.innerHTML +=
'<p>' + lyricsArray[i].split(']')[1] + '</p>';
}
}
// 獲取后續需要使用的變數
lyricsFirst = divLyrics.getElementsByTagName('p')[0];
lyrics = divLyrics.getElementsByTagName('p');
// 計算每局歌詞所在的秒數
timeArray1.push(0);
for (var i = 0; i < lyrics.length; i++) {
timeString = lyricsArray[i + 3].substring(1, 9).split(':');
timeArray1.push(
parseFloat(timeString[0]) * 60 + parseFloat(timeString[1])
);
}
// 計算時間間隔,將時間從秒數改為分鐘+秒數
for (var i = 0; i < timeArray1.length - 1; i++) {
timeInterval[i] = timeArray1[i + 1] - timeArray1[i];
if (Math.floor(timeArray1[i] % 60) < 10) {
timeArray2.push(
Math.floor(timeArray1[i] / 60) +
':0' +
Math.floor(timeArray1[i] % 60)
);
} else {
timeArray2.push(
Math.floor(timeArray1[i] / 60) +
':' +
Math.floor(timeArray1[i] % 60)
);
}
}
}
// 設定歌詞位置
function setLyrics(line) {
for (let i = 0; i < lyrics.length; i++) {
lyrics[i].style.color = '#000';
}
lyrics[line].style.color = '#5192fe';
let lyrics_animation = lyrics[0].animate(
[
{
marginTop: lyricsStyle.marginTop,
},
{
marginTop: line * -34 + 136 + 'px',
},
],
{
duration: 100,
}
);
lyrics_animation.addEventListener(
'finish',
function () {
lyrics[0].style.marginTop = line * -34 + 136 + 'px';
},
false
);
}
// 歌詞滾動
function lyricsRoll() {
rollT = setTimeout(function () {
if (nowLine < lyrics.length && myAduio.paused == false) {
if (lyricsMove == false) {
setLyrics(nowLine);
}
nowLine += 1;
lyricsRoll();
}
}, timeInterval[nowLine] * 1000);
}
// 設定進度文本
function setTimeText() {
var nowTime = myAduio.currentTime;
var allTime = myAduio.duration;
// 計算時間,若為個位數,補0
if (Math.floor(nowTime % 60) < 10) {
nowTime = Math.floor(nowTime / 60) + ':0' + Math.floor(nowTime % 60);
} else {
nowTime = Math.floor(nowTime / 60) + ':' + Math.floor(nowTime % 60);
}
if (Math.floor(allTime % 60) < 10) {
allTime = Math.floor(allTime / 60) + ':0' + Math.floor(allTime % 60);
} else {
allTime = Math.floor(allTime / 60) + ':' + Math.floor(allTime % 60);
}
progressTime.innerText = nowTime + '/' + allTime;
// 每0.1秒執行一次
if (myAduio.paused == false) {
setTimeout(setTimeText, 100);
}
}
// 設定進度條進度
function setProgress() {
let progress_now = document.getElementsByClassName('progress-now')[0];
let progress_bar = document.getElementsByClassName('progress-bar')[0];
let progress = Math.floor(
(myAduio.currentTime / myAduio.duration) * progress_bar.clientWidth
);
progress_now.style.width = progress + 'px';
if (myAduio.paused == false) {
setTimeout(setProgress, 100);
}
}
// 獲取網頁屬性
function getDocument(attribute) {
if (attribute == 'sT') {
return document.documentElement.scrollTop;
} else if (attribute == 'sL') {
return document.documentElement.scrollLeft;
} else if (attribute == 'sH') {
return document.documentElement.scrollHeight;
} else if (attribute == 'sW') {
return document.documentElement.scrollWidth;
} else if (attribute == 'cH') {
return document.documentElement.clientHeight;
} else if (attribute == 'cW') {
return document.documentElement.clientWidth;
}
}
其中JS中,獲取頁面屬性跟歌詞回彈是沒有用到的,獲取頁面屬性我寫是為了方便,但結果發現沒用上,歌詞回彈是后來發現直接用設定歌詞方法來進行回彈即可,也就是說歌詞回彈寫了是多余的
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/260078.html
標籤:其他
上一篇:觀察者模式實作表單驗證
