工作之余查了很多資料一步步摸索出來的欺旧,先感謝各位大佬胸嘁。
仿QQ音樂做的,不是100%還原榆鼠,不過基本原理都在這了震鹉,結(jié)合之前的歌詞滾動(dòng)組件一起使用
持續(xù)更新
效果圖
頁面布局拆分
總體分為
1頂部歌名+歌手的top-bar模塊+模糊背景
2專輯+歌詞滾動(dòng)模塊
3進(jìn)度條+時(shí)間模塊
4播放控件模塊俱笛,播放,暫停传趾,上一曲迎膜,下一曲。
需要注意音頻初始化audioInit
mounted() {
const audio = document.getElementById("audio");
this.init();
this.audioInit();
this.swiper = new Swiper(this.activeClass, {
autoplay: false, //自動(dòng)播放
loop: false, //循環(huán)播放
});
},
method: {
audioInit() {
let that = this;
// 音頻或視頻文件已經(jīng)就緒可以開始
audio.addEventListener("canplay", () => {
console.log("canplay");
that.audioDuration = that.TimeToString(audio.duration);
let buffered = audio.buffered.end(0)
});
let progressL = this.$refs.track.offsetWidth; // 進(jìn)度條總長
audio.addEventListener("timeupdate", () => {
// 當(dāng)前播放時(shí)間
let compareTime = audio.currentTime;
let buffered = audio.buffered.end(0)
for (let i = 0; i < that.lyricInfo.length; i++) {
if (compareTime > parseInt(that.lyricInfo[i].time)) {
const index = that.lyricInfo[i].index;
if (i === parseInt(index)) {
that.lyricIndex = i; //獲取當(dāng)前播放歌曲的歌詞index
}
}
}
that.currentTime = that.TimeToString(audio.currentTime);
that.audioPercent =
audio.currentTime / audio.duration.toFixed(3);
that.thumbTranslateX = that.audioPercent * 4;
});
audio.addEventListener("ended", () => {
that.playIndex =
that.playIndex + 1 >= that.songList.length
? 0
: that.playIndex + 1;
that.songInfo = that.songList[that.playIndex];
that.GetLyric(that.songInfo.id);
setTimeout(() => {
audio.play();
}, 100);
});
},
}
第一部分
需要定一個(gè)固定在頁面的div浆兰,采用fixed布局
<template>
<div class="main-page" ref="MainRef">
<div
class="background-flitter"
:style="`background-image: url(${songInfo.cover})`"
></div>
<div class="top-bar">
<p>{{ songInfo.name }}</p>
<p style="font-size: 0.24rem;font-weight: 500">{{ songInfo.artistsName }}</p>
</div>
</div>
</template>
具體的歌曲信息如下
data: (){
return {
{
albumId: 93162249,
albumTitle: "STRAY SHEEP",
artistsName: "米津玄師",
cover:"https://p1.music.126.net/6mhlWCOOQkT0xDjjuCLW7g==/109951165181187586.jpg",
id: 1466598056,
index: 7,
name: "Lemon",
url:"https://music.163.com/song/media/outer/url?id=1466598056.mp3",
},
}
}
<style lang="less" scoped>
.main-page {
width: 100%;
height: 100vh;
position: relative;
background: rgba(15, 15, 15, 0.3);
.background-flitter {
position: fixed;
z-index: -2;
background-repeat: no-repeat;
width: 100%;
height: 100vh;
top: 0;
left: 0;
background-size: cover;
background-position: 50%;
filter: blur(0.16rem);
opacity: 0.7;
overflow: hidden;
box-sizing: border-box;
}
.top-bar {
width: 100%;
height: 1.2rem;
text-align: center;
color: #fff;
font-size: 0.32rem;
line-height: 0.6rem;
font-weight: 600;
}
}
</style>
至此完成整體背景和top-bar部分
第二部分磕仅,專輯+歌詞滾動(dòng)
左右滑動(dòng)切換用到了swiper組件,專輯+歌詞和純歌詞分別放在兩個(gè)slide就可以了簸呈。
先固定一個(gè)歌詞容器榕订,與top-bar同級(jí),具體代碼可以參考歌詞組件封裝來看,都是復(fù)用的代碼蜕便。
<div class="lyric-container">
<div class="swiper-contain">
<div class="swiper-wrapper">
<div class="swiper-slide">
<div class="disc-cover">
<div class="disc" ref="rotate">//ref="rotate"
<img :src="songInfo.cover" alt="" />
</div>
</div>
<div class="text-container">
<div class="text-list" :style="lyricMiniTop">
<p
v-for="(item, index) in lyricInfo"
:key="index"
:style="{
color:
lyricIndex === index
? colorLight
: color,
}"
>
{{ item.lyric }}
</p>
</div>
</div>
</div>
<div class="swiper-slide">
<l-scroll
ref="lyric"
:color="color"
:colorLight="colorLight"
:lineHeight="lineHeight"
:paddingTop="paddingTop"
:fontSize="fontSize"
:lyricIndex="lyricIndex"
:lyricsList="lyricInfo"
></l-scroll>
</div>
</div>
</div>
</div>
歌詞滾動(dòng)之前文章有講過了劫恒,提一下這個(gè)唱片旋轉(zhuǎn)的動(dòng)畫吧,給旋轉(zhuǎn)部分添加ref="rotate"
.disc {
width: 5rem;
height: 5rem;
border-radius: 50%;
box-shadow: 0 0 0 0.2rem rgba(255, 255, 255, 0.4);
animation: animations1 12s linear infinite forwards;
animation-play-state: paused;
overflow: hidden;
img {
width: 100%;
height: 100%;
}
}
@keyframes animations1 {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
在播放事件中添加
//播放與暫停
play() {
if (this.playing) {
// 播放中,點(diǎn)擊則為暫停
this.playing = false;
this.$refs.rotate.style.animationPlayState = "paused";
audio.pause();
} else {
// 暫停中,點(diǎn)擊則為播放
this.playing = true;
this.$refs.rotate.style.animationPlayState = "running";
audio.play();
}
},
到這完成了歌詞滾動(dòng)和切換
第三部分,進(jìn)度條點(diǎn)擊和拖拽控制播放進(jìn)度(重點(diǎn))
樣式轿腺,時(shí)長比這些都講爛了两嘴,講講點(diǎn)擊和拖拽控制播放進(jìn)度吧
拖拽功能:給拖拽按鈕,也就是示例圖的白色進(jìn)度按鈕添加三個(gè)事件
<div
class="play-point"
@touchstart.stop.prevent="touchstart"
@touchmove.stop.prevent="touchmove"
@touchend.stop.prevent="touchend"
:style="{
transform: 'translateX(' + thumbTranslateX + 'rem)',
}"
></div>
//控制播放進(jìn)度
SetProgress(t) {
audio.currentTime = audio.duration * t;
},
touchstart(event) {
let progressL = this.$refs.track.offsetWidth ; // 進(jìn)度條總長
let allL = this.$refs.MainRef.offsetWidth ; // 頁面總長
let half = (allL-progressL)/2
console.log("開始", progressL, allL, half)
//記錄開始的X軸坐標(biāo)
if (this.startXFirst) {
this.startX = half;
this.startXFirst = false;
}
},
touchmove(event) {
let moveX = parseInt(
(event.changedTouches[0].clientX - this.startX)
);
let progressL = this.$refs.track.offsetWidth; // 進(jìn)度條總長
let percent = (moveX / progressL).toFixed(2); //拖動(dòng)%比
if(percent>1){
percent = 1
}
this.audioPercent = percent; //音頻播放%
this.thumbTranslateX = this.audioPercent * 4; //計(jì)算滑塊位移
},
touchend(event) {
console.log("結(jié)束",event)
this.finalX = parseInt(
event.changedTouches[0].clientX - this.startX
);
let progressL = this.$refs.track.offsetWidth; // 進(jìn)度條總長
let time = (this.finalX / progressL).toFixed(2);
if(time>1){
time = 1
}
this.SetProgress(time);
},
點(diǎn)擊實(shí)現(xiàn)進(jìn)度則簡(jiǎn)單多了吃溅,
<div
class="progress"
@click="HandleProgressClick($event)"
ref="track"
>
<div
class="progress_box"
:style="{ width: audioProgressPercent }"
></div>
<div
class="play-point"
@touchstart.stop.prevent="touchstart"
@touchmove.stop.prevent="touchmove"
@touchend.stop.prevent="touchend"
:style="{
transform: 'translateX(' + thumbTranslateX + 'rem)',
}"
></div>
</div>
//點(diǎn)擊進(jìn)度條
HandleProgressClick(event) {
let progressL = this.$refs.track.offsetWidth; // 進(jìn)度條總長
let clickX = event.offsetX;
let time = (clickX / progressL).toFixed(2);
this.SetProgress(time);
},
OK 溶诞,第三部分完成