秒转00:00:00
* 秒值转时分秒
* @name TimeToString
* @example 秒值转时分秒
* @param {String} seconds 秒
*/
const TimeToString = (seconds) => {
console.log(seconds)
let param = parseInt(seconds);
let hh = "",
mm = "",
ss = "";
if (param >= 0 && param < 60) {
param < 10 ? (ss = "0" + param) : (ss = param);
return "00:" + ss;
} else if (param >= 60 && param < 3600) {
mm = parseInt(param / 60);
mm < 10 ? (mm = "0" + mm) : mm;
param - parseInt(mm * 60) < 10 ?
(ss = "0" + String(param - parseInt(mm * 60))) :
(ss = param - parseInt(mm * 60));
return mm + ":" + ss;
}
}
页面
<style lang="less" scoped>
// 我的移动端换算设置为100px=1rem
.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;
}
.lyric-container {
width: 100%;
height: 9rem;
position: relative;
overflow: hidden;
.swiper-contain {
position: relative;
width: 100%;
height: 100%;
.swiper-wrapper {
width: 100%;
height: 100%;
position: relative;
.swiper-slide {
width: 100%;
height: 100%;
position: relative;
.disc-cover {
width: 100%;
height: 6rem;
display: flex;
align-items: center;
justify-content: center;
.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);
}
}
}
.text-container {
position: relative;
padding-top: 1rem;
box-sizing: border-box;
overflow: hidden;
mask-image: linear-gradient(
to bottom,
rgba(255, 255, 255, 0) 0,
rgba(255, 255, 255, 0.5) 2%,
rgba(255, 255, 255, 1) 5%,
rgba(255, 255, 255, 0) 100%
);
.text-list {
width: 100%;
text-align: center;
line-height: 1rem;
color: #fff;
font-size: 0.28rem;
transform: translate3d(0, 0, 0);
transition: transform 0.6s ease-out;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
}
}
}
.swiper-pagination {
width: 100%;
height: 0.4rem;
}
}
}
.progress-bar {
margin-top: 0.3rem;
width: 100%;
padding: 0 0.75rem;
box-sizing: border-box;
display: flex;
align-items: center;
position: relative;
height: 0.6rem;
color: #fff;
line-height: 0.6rem;
font-size: 0.24rem;
.time {
width: 1rem;
text-align: center;
}
.progress {
width: 4rem;
height: 0.06rem;
background: rgba(255, 255, 255, 0.3);
border-radius: 0.03rem;
position: relative;
margin: 0 0.15rem;
.progress_box {
width: 4rem;
height: 0.06rem;
border-radius: 0.03rem;
background: #40ce8f;
}
.play-point {
position: absolute;
transition: -webkit-transform 0.2s linear;
transition: transform 0.2s linear;
transition: transform 0.2s linear, -webkit-transform 0.2s linear;
top: -0.12rem;
left: -0.15rem;
width: 0.3rem;
height: 0.3rem;
border-radius: 50%;
background: #fff;
}
}
}
.play-icon {
margin-top: 0.6rem;
position: relative;
width: 100%;
display: flex;
justify-content: center;
.icon {
width: 0.92rem;
img {
width: 0.92rem;
}
}
.play-icon {
width: 0.92rem;
margin: 0 1.2rem;
}
}
}
</style>
<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 class="lyric-container">
<div class="swiper-contain">
<div class="swiper-wrapper">
<div class="swiper-slide">
<div class="disc-cover">
<div class="disc" 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>
<div class="progress-bar">
<div class="time">{{ currentTime }}</div>
<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>
<div class="time">{{ audioDuration }}</div>
</div>
<div class="play-icon">
<div class="icon" @click="SkipBack">
<img :src="arrow_01" alt="" />
</div>
<img
class="play-icon"
@click="PlayMusic"
v-show="!playing"
:src="play_01"
alt=""
/>
<img
class="play-icon"
@click="PlayMusic"
v-show="playing"
:src="play_02"
alt=""
/>
<div class="icon" @click="SkipForward">
<img :src="arrow_02" alt="" />
</div>
</div>
<audio :src="songInfo.url" preload="preload" id="audio"></audio>
</div>
</template>
<script>
import LScroll from "../../../components/lyricScroll/scroll.vue";
const Swiper = require("swiper");
import "swiper/dist/css/swiper.min.css";
export default {
data: function () {
return {
playing: false,
playIndex: 0,
play_01:
"../src/image/play_icon.png",
play_02:
"../src/image/play-02.png",
arrow_01:
"../src/image/arrow_01.png",
arrow_02:
"../src/image/mechanism/arrow_02.png",
songInfo: {},
songList: [
{
albumId: 75612550,
albumTitle: "辞.九门回忆",
artistsName: "解忧草/冰幽",
cover:
"https://p1.music.126.net/pWJXXU4kbhsk1HhXCPUMag==/109951163879149420.jpg",
id: 557640761,
index: 0,
name: "辞.九门回忆",
url:
"https://music.163.com/song/media/outer/url?id=1347524822.mp3",
},
{
albumId: 9333,
albumTitle: "翻唱合集",
artistsName: "等什么君",
cover:
"https://y.gtimg.cn/music/photo_new/T002R300x300M000000vtKmF3OEIbF.jpg?max_age=2592000",
id: 1462624041,
index: 1,
name: "春庭雪",
url:
"http://aqqmusic.tc.qq.com/amobile.music.tc.qq.com/C4000036hiOc1PzcKd.m4a?guid=7184359043&vkey=E887F187BAEE434C584843A53C72142C4DFF181C3CC1831F94B3E5AD910C21F178E84BDD7D04572DC2048D4FF9EDAC742D3E2256F69226EC&uin=0&fromtag=38",
},
{
albumId: 75612550,
albumTitle: "20岁30岁40岁",
artistsName: "曾婕Joey.Z",
cover:
"https://p2.music.126.net/-HrOFDdHaCg-_bl6MlqFQw==/109951163753269739.jpg?param=120y120",
id: 1335968312,
index: 3,
name: "20岁30岁40岁",
url:
"https://music.163.com/song/media/outer/url?id=1335968312.mp3",
},
{
albumId: 85511857,
albumTitle: "谪仙",
artistsName: "伊格赛听/叶里",
cover:
"https://p1.music.126.net/X-ZQ6wkyaL9cJTiyxmDhuw==/109951164680974796.jpg",
id: 1421256202,
index: 4,
name: "谪仙",
url:
"https://music.163.com/song/media/outer/url?id=1421256202.mp3",
},
{
albumId: 35172219,
albumTitle: "君の名は - 黄昏之时",
artistsName: "Frank_Jiang",
cover:
"https://p1.music.126.net/YppJiMHyrLc7tDkj6jUttg==/109951162858188597.jpg",
id: 459116892,
index: 5,
name: "黄昏之时(FRANKOWO Bootleg)",
url:
"https://music.163.com/song/media/outer/url?id=459116892.mp3",
},
{
albumId: 85474719,
albumTitle: "等什么君翻唱合集",
artistsName: "等什么君",
cover:
"https://p1.music.126.net/dDF57j70YTq6isZTIqQNWg==/109951164678301220.jpg",
id: 1421028283,
index: 6,
name: "冬眠(翻自 司南) ",
url:
"https://music.163.com/song/media/outer/url?id=1421028283.mp3",
},
{
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",
},
],
currentTime: "00:00",
audioDuration: 0,
audioPercent: 0,
thumbTranslateX: 0, // 进度条滑块位置
activeClass: ".swiper-contain",
lyricInfo: [],
lyricIndex: 0,
startX: 0, //拖动开始前的X
finalX: 0, //拖动结束X的位移距离
startXFirst: true,
top: 0,
color: "#fff", //文案默认颜色
colorLight: "#40ce8f", //文案高亮色
fontSize: "0.18rem", //文案字体大小
lineHeight: "0.6", //每段行高
paddingTop: "4.2rem", //高亮文案部分居中
};
},
components: {
LScroll,
},
computed: {
lyricMiniTop() {
return `transform :translate3d(0, ${
(0 - 1) * (this.lyricIndex - this.top)
}rem, 0);color: ${this.colorLight}`;
},
audioProgressPercent() {
return `${this.audioPercent * 100}%`;
},
},
created() {},
mounted() {
const audio = document.getElementById("audio");
this.init();
this.audioInit();
this.swiper = new Swiper(this.activeClass, {
autoplay: false, //自动播放
loop: false, //循环播放
});
},
methods: {
init() {
this.songInfo = this.songList[0];
this.GetLyric(this.songInfo.id);
},
//播放与暂停
PlayMusic() {
if (this.playing) {
// 播放中,点击则为暂停
this.playing = false;
this.$refs.rotate.style.animationPlayState = "paused";
audio.pause();
} else {
// 暂停中,点击则为播放
this.playing = true;
this.$refs.rotate.style.animationPlayState = "running";
audio.play();
}
},
audioInit() {
let that = this;
// 音频或视频文件已经就绪可以开始
audio.addEventListener("canplay", () => {
console.log("canplay");
that.audioDuration = that.TimeToString(audio.duration);
let buffered = audio.buffered.end(0)
});
let progressL = this.$refs.track.offsetWidth; // 进度条总长
audio.addEventListener("timeupdate", () => {
// 当前播放时间
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;
}
}
}
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);
});
},
// 上一首
SkipBack() {
this.playIndex = this.playIndex - 1 >= 0 ? this.playIndex - 1 : 0;
this.songInfo = this.songList[this.playIndex];
this.GetLyric(this.songInfo.id);
this.playing = true;
this.lyricIndex = 0;
this.$refs.rotate.style.animationPlayState = "running";
setTimeout(() => {
audio.play();
}, 100);
},
// 下一首
SkipForward() {
this.playIndex =
this.playIndex + 1 >= this.songList.length
? 0
: this.playIndex + 1;
this.songInfo = this.songList[this.playIndex];
this.GetLyric(this.songInfo.id);
this.playing = true;
this.lyricIndex = 0;
this.$refs.rotate.style.animationPlayState = "running";
setTimeout(() => {
audio.play();
}, 100);
},
//点击进度条
HandleProgressClick(event) {
let progressL = this.$refs.track.offsetWidth; // 进度条总长
let clickX = event.offsetX;
let time = (clickX / progressL).toFixed(2);
this.SetProgress(time);
},
SetProgress(t) {
audio.currentTime = audio.duration * t;
},
TouchStart(event) {
let progressL = this.$refs.track.offsetWidth ; // 进度条总长
let allL = this.$refs.MainRef.offsetWidth ; // 进度条总长
let half = (allL-progressL)/2
//记录开始的X轴坐标
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; // 进度条总长
let percent = (moveX / progressL).toFixed(2); //拖动%比
if(percent>1){
percent = 1
}
this.audioPercent = percent; //音频播放%
this.thumbTranslateX = this.audioPercent * 4; //计算滑块位移
},
TouchEnd(event) {
this.finalX = parseInt(
event.changedTouches[0].clientX - this.startX
);
let progressL = this.$refs.track.offsetWidth; // 进度条总长
let time = (this.finalX / progressL).toFixed(2);
if(time>1){
time = 1
}
this.SetProgress(time);
},
GetLyric(id) {
let that = this;
axios
.get("https://api.mtnhao.com/lyric", {
params: {
id: id,
},
})
.then(function (response) {
let lrc = response.data.lrc.lyric;
that.GetLyricList(lrc);
});
},
GetLyricList(lrc) {
let lyricsObjArr = [];
const regNewLine = /\n/;
const lineArr = lrc.split(regNewLine); // 每行歌词的数组
const regTime = /\[\d{2}:\d{2}.\d{2,3}\]/;
lineArr.forEach((item) => {
if (item === "") return;
const obj = {};
const time = item.match(regTime);
obj.lyric =
item.split("]")[1].trim() === ""
? ""
: item.split("]")[1].trim();
obj.time = time
? this.TimeToSeconds(time[0].slice(1, time[0].length - 1))
: 0;
obj.uid = Math.random().toString().slice(-6);
if (obj.lyric === "") {
} else {
lyricsObjArr.push(obj);
}
});
this.lyricInfo = lyricsObjArr.map((item, index) => {
item.index = index;
return {
...item,
};
});
console.log("歌词信息", this.lyricInfo);
},
},
};
</script>
歌词组件代码---LScroll
<template>
<!--歌词-->
<div
ref="musicLyric"
class="music-lyric"
:style="{ 'padding-top': paddingTop }"
>
<div class="music-lyric-items" :style="lyricTop">
<template v-if="lyricsList.length > 0">
<p
v-for="(item, index) in lyricsList"
:key="index"
:data-index="index"
ref="lyric"
:style="{
color: lyricIndex === index ? colorLight : color,
'font-size': fontSize,
}"
>
{{ item.lyric }}
</p>
</template>
<p style="color: #fff" v-else>文案加载失败!</p>
</div>
</div>
</template>
<script>
const COMPONENT_NAME = "scroll";
export default {
name: COMPONENT_NAME,
props: {
// 歌词列表
lyricsList: {
type: Array,
default: () => [],
},
// 当前歌词下标索引
lyricIndex: {
type: Number,
default: 0,
},
// 歌词默认色
color: {
type: String,
default: "#fff",
},
// 歌词高亮色
colorLight: {
type: String,
default: "#40ce8f",
},
fontSize: {
type: String,
default: "0.16rem",
},
lineHeight: {
type: String,
default: "0.48",
},
paddingTop: {
type: String,
default: "3rem",
},
},
data() {
return {
top: 0, // 文案居中
//文案list示例
lyricListDemo: [
{
index: 0,
lyric: "作曲 : CMJ",
time: 0,
},
{
index: 1,
lyric: "作词 : 桃玖",
time: 0.29,
},
{
index: 2,
lyric: "你听啊秋末的落叶",
time: 0.89,
},
{
index: 3,
lyric: "你听它叹息着离别",
time: 5.14,
},
{
index: 4,
lyric: "只剩我独自领略",
time: 9.39,
},
{
index: 5,
lyric: "海与山 风和月",
time: 13.14,
},
],
};
},
mounted() {},
watch: {
lyricIndex(newVal, oldVal) {
console.log(newVal, oldVal);
},
},
computed: {
lyricTop() {
return `transform :translate3d(0, ${
(0 - this.lineHeight) * (this.lyricIndex - this.top)
}rem, 0);color: ${this.color};line-height: ${this.lineHeight}rem`;
},
},
methods: {},
watch: {},
};
</script>
<style lang="less" scoped>
/*歌词部分*/
.music-lyric {
// position: absolute;
// top: 1.9rem;
// right: 0;
// bottom: 0;
// left: 0;
padding-top: 4.1rem;
box-sizing: border-box;
overflow: hidden;
text-align: center;
mask-image: linear-gradient(
to bottom,
rgba(255, 255, 255, 0) 0,
rgba(255, 255, 255, 0.6) 15%,
rgba(255, 255, 255, 1) 25%,
rgba(255, 255, 255, 1) 75%,
rgba(255, 255, 255, 0.6) 85%,
rgba(255, 255, 255, 0) 100%
);
.music-lyric-items {
text-align: center;
font-size: 0.16rem;
color: #fff;
transform: translate3d(0, 0, 0);
transition: transform 0.6s ease-out;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
.on {
color: #40ce8f;
}
}
}
</style>