Vue3中實(shí)現(xiàn)歌詞滾動(dòng)顯示效果
??前言
在這篇博客中,我將分享如何在 Vue 3 中實(shí)現(xiàn)一個(gè)簡(jiǎn)單的歌詞滾動(dòng)效果。我將從歌詞數(shù)據(jù)的處理開始,一步步介紹布局的搭建和事件的實(shí)現(xiàn)。

??整體布局
1.實(shí)現(xiàn)歌詞的滾動(dòng),首先應(yīng)該給歌詞設(shè)置一個(gè)特定大小的盒子里,然后可以使用overflow:hidden;來對(duì)溢出的歌詞進(jìn)行隱藏。
2.控制當(dāng)前該高亮的歌詞的樣式,我是使用transform:scale(1.2)來控制文字變大的。
3.過度動(dòng)畫,給高亮顯示的歌詞加上過度動(dòng)畫效果,還有整個(gè)歌詞區(qū)域移動(dòng)的動(dòng)畫效果,以及黑膠唱片的旋轉(zhuǎn)動(dòng)畫。
記住要給video標(biāo)簽的width和height設(shè)置為0,雖然默認(rèn)情況下不顯示,但是還是會(huì)影響布局的
<template>
<div class="box">
<button @click="audioElement.play()" class="btn">播放</button>
<!-- 音樂播放器 -->
<video ref="audioElement" class="video" src="./assets/陳奕迅 - 淘汰.ogg" @timeupdate="timeUpdateHandler"
@canplay="canPlayHandler">
</video>
<div class="cd">
<img src="./assets/CD.jpg" alt="">
<div class="msg">
<ul>
<li>
歌詞
</li>
</ul>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
.box {
width: 100%;
display: flex;
align-items: center;
background-color: #0E0A0D;
flex-direction: column;
height: 100vh;
.btn {
padding: 10px 15px;
border: none;
border-radius: 10%;
margin-top: 20px;
background-color: #84CCE6;
border-color: #84CCE6;
color: #000;
cursor: pointer;
}
.video {
width: 0;
height: 0;
}
.cd {
width: 400px;
img {
width: 200px;
height: 200px;
border-radius: 50%;
margin: 100px 0 20px 100px;
//添加一個(gè)動(dòng)畫效果,旋轉(zhuǎn)
animation: rotate 5s linear infinite;
@keyframes rotate {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
}
}
.msg {
width: 100%;
height: 300px;
overflow: hidden;
ul {
width: 100%;
display: flex;
flex-direction: column;
//居中
align-items: center;
margin-top: 150px;
transition: ease .5s;
li {
list-style: none;
color: white;
line-height: 30px;
transition: all .5s;
cursor: pointer;
&.active {
color: greenyellow;
transform: scale(1.2);
}
}
}
}
}
</style>??處理歌詞數(shù)據(jù)
這是歌詞數(shù)據(jù),我把它放在了一個(gè)ts文件中,我們可以在這里對(duì)數(shù)據(jù)進(jìn)行處理好后導(dǎo)出給組件使用

const dataStr = ` 00:00 淘汰 – 陳奕迅 (Eason Chan) 00:08 詞:周杰倫 00:17 曲:周杰倫 00:26 編曲:C.Y.Kong 00:35 我說了所有的謊 00:39 你全都相信 00:43 簡(jiǎn)單的我愛你 00:46 你卻老不信 00:51 你書里的劇情 00:55 我不想上演 00:58 因?yàn)槲蚁矚g 01:01 喜劇收尾 01:08 我試過完美放棄 01:12 的確很踏實(shí) 01:15 醒來了夢(mèng)散了 01:19 你我都走散了 01:23 情歌的詞何必押韻 01:27 就算我是K歌之王 01:31 也不見得把 01:33 愛情唱得完美 01:38 只能說我輸了 01:42 也許是你怕了 01:46 我們的回憶沒有皺褶 01:51 你卻用離開燙下句點(diǎn) 01:54 只能說我認(rèn)了 01:58 你的不安贏得你信任 02:03 我卻得到你安慰的淘汰 02:25 我試過完美放棄 02:29 的確很踏實(shí) 02:32 醒來了夢(mèng)散了 02:36 你我都走散了 02:40 情歌的詞何必押韻 02:44 就算我是K歌之王 02:48 也不見得把 02:50 愛情唱得完美 02:55 只能說我輸了 02:59 也許是你怕了 03:03 我們的回憶沒有皺褶 03:08 你卻用離開燙下句點(diǎn) 03:11 只能說我認(rèn)了 03:15 你的不安贏得你信任 03:21 我卻得到你安慰的淘汰 03:44 只能說我輸了 03:48 也許是你怕了 03:52 我們的回憶沒有皺褶 03:57 你卻用離開燙下句點(diǎn) 04:00 只能說我認(rèn)了 04:04 你的不安贏得你信任 04:09 我卻得到你安慰的淘汰`
現(xiàn)在這樣看著是一整個(gè)字符串,因?yàn)椴缓貌僮?,所以我們要把它變成我們想要的形式?/p>
我想要的是這種感覺,解析成一個(gè)數(shù)組就方便我們渲染以及處理事件。

所以我們這里就使用一下分割匹配得到我們想要的效果
// 定義歌詞數(shù)據(jù)類型
type Lyric = {
timestamp: number
content: string
}
//解析歌詞數(shù)據(jù)
const parseLyrics = (dataStr: string): Lyric[] => {
const lines = dataStr.split('\n')
const lyrics: Lyric[] = []
for (const line of lines) {
const match = line.match(/(\d{2}):(\d{2}) (.+)/)
if (match) {
const [, minutes, seconds, content] = match
// 將時(shí)間戳轉(zhuǎn)換為秒
const timestamp = parseInt(minutes) * 60 + parseInt(seconds)
lyrics.push({ timestamp, content })
}
}
return lyrics
}
// 使用解析函數(shù)獲取歌詞數(shù)組
const lyricsArray: Lyric[] = parseLyrics(dataStr)
//導(dǎo)出
export default lyricsArray這樣子,就得到我們想要的結(jié)果了
??處理事件
首先我們得思考需要處理哪些事件
1.那句歌詞得高亮?
2.偏移量為多少?
那么我們先得記得把歌詞渲染上去
<li v-for="(item, index) in lyricsArray" :key="item.timestamp">{{ item.content }}</li>
<script setup lang="ts">
import lyricsArray from './data.ts'
</script>第一個(gè)問題,哪句歌詞得高亮
在video的dom元素上我們可以通過 currentTime 來獲取它此時(shí)的播放時(shí)間位置,我們可以監(jiān)聽它的位置變化,然后得到需要顯示的歌詞的 Index,然后再li標(biāo)簽上對(duì)其active類名進(jìn)行動(dòng)態(tài)顯示就行了。
第二個(gè)問題,偏移量
既然我們之前有給li標(biāo)簽設(shè)置line-height ,那么我們就可以再Index變化的時(shí)候一起進(jìn)行計(jì)算出來。
可以再添加一個(gè)交互,就是用戶點(diǎn)擊某句歌詞,跳動(dòng)那個(gè)地方去
<template>
<div class="box">
<button @click="audioElement.play()" class="btn">播放</button>
<!-- 音樂播放器 -->
<video ref="audioElement" class="video" src="./assets/陳奕迅 - 淘汰.ogg" @timeupdate="timeUpdateHandler"
@canplay="canPlayHandler">
</video>
<div class="cd">
<img src="./assets/CD.jpg" alt="">
<div class="msg">
<ul :style="transformStyle">
<li v-for="(item, index) in lyricsArray" :key="item.timestamp" :class="activeIndex === index ? 'active' : ''"
@click="audioChange(item)">
{{ item.content }}
</li>
</ul>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import lyricsArray from './data.ts'
import { ref, watch } from 'vue';
const audioElement = ref<any>(null); //dom元素
const currentTime = ref<string>('') //播放位置
const activeIndex = ref<number>(0) //高亮Index
const transformStyle = ref<string>('') //偏移量
function timeUpdateHandler() {
// 處理播放位置變化事件
console.log('播放位置變化事件');
//獲取播放的位置
currentTime.value = audioElement.value.currentTime;
}
//監(jiān)聽播放的位置變化
watch(currentTime, (newVal) => {
for (let i = 0; i < lyricsArray.length; i++) {
if (lyricsArray[i].timestamp < +newVal) {
activeIndex.value = i
transformStyle.value = `transform: translateY(${-activeIndex.value * 30}px)`
}
}
})
function canPlayHandler() {
//預(yù)處理完成,可以開始播放
console.log('可以開始播放');
}
//處理歌詞點(diǎn)擊事件
const audioChange = (i: any) => {
audioElement.value.currentTime = i.timestamp + 1;
}
</script>這樣點(diǎn)擊按鈕后就能看到完美的效果了
??完整代碼
處理歌詞:
//導(dǎo)出歌詞數(shù)據(jù)
const dataStr = `
00:00 淘汰 – 陳奕迅 (Eason Chan)
00:08 詞:周杰倫
00:17 曲:周杰倫
00:26 編曲:C.Y.Kong
00:35 我說了所有的謊
00:39 你全都相信
00:43 簡(jiǎn)單的我愛你
00:46 你卻老不信
00:51 你書里的劇情
00:55 我不想上演
00:58 因?yàn)槲蚁矚g
01:01 喜劇收尾
01:08 我試過完美放棄
01:12 的確很踏實(shí)
01:15 醒來了夢(mèng)散了
01:19 你我都走散了
01:23 情歌的詞何必押韻
01:27 就算我是K歌之王
01:31 也不見得把
01:33 愛情唱得完美
01:38 只能說我輸了
01:42 也許是你怕了
01:46 我們的回憶沒有皺褶
01:51 你卻用離開燙下句點(diǎn)
01:54 只能說我認(rèn)了
01:58 你的不安贏得你信任
02:03 我卻得到你安慰的淘汰
02:25 我試過完美放棄
02:29 的確很踏實(shí)
02:32 醒來了夢(mèng)散了
02:36 你我都走散了
02:40 情歌的詞何必押韻
02:44 就算我是K歌之王
02:48 也不見得把
02:50 愛情唱得完美
02:55 只能說我輸了
02:59 也許是你怕了
03:03 我們的回憶沒有皺褶
03:08 你卻用離開燙下句點(diǎn)
03:11 只能說我認(rèn)了
03:15 你的不安贏得你信任
03:21 我卻得到你安慰的淘汰
03:44 只能說我輸了
03:48 也許是你怕了
03:52 我們的回憶沒有皺褶
03:57 你卻用離開燙下句點(diǎn)
04:00 只能說我認(rèn)了
04:04 你的不安贏得你信任
04:09 我卻得到你安慰的淘汰`
// 定義歌詞數(shù)據(jù)類型
type Lyric = {
timestamp: number
content: string
}
//解析歌詞數(shù)據(jù)
const parseLyrics = (dataStr: string): Lyric[] => {
const lines = dataStr.split('\n')
const lyrics: Lyric[] = []
for (const line of lines) {
const match = line.match(/(\d{2}):(\d{2}) (.+)/)
if (match) {
const [, minutes, seconds, content] = match
// 將時(shí)間戳轉(zhuǎn)換為秒
const timestamp = parseInt(minutes) * 60 + parseInt(seconds)
lyrics.push({ timestamp, content })
}
}
return lyrics
}
// 使用解析函數(shù)獲取歌詞數(shù)組
const lyricsArray: Lyric[] = parseLyrics(dataStr)
//導(dǎo)出
export default lyricsArray組件代碼:
<template>
<div class="box">
<button @click="audioElement.play()" class="btn">播放</button>
<!-- 音樂播放器 -->
<video ref="audioElement" class="video" src="./assets/陳奕迅 - 淘汰.ogg" @timeupdate="timeUpdateHandler"
@canplay="canPlayHandler">
</video>
<div class="cd">
<img src="./assets/CD.jpg" alt="">
<div class="msg">
<ul :style="transformStyle">
<li v-for="(item, index) in lyricsArray" :key="item.timestamp" :class="activeIndex === index ? 'active' : ''"
@click="audioChange(item)">
{{ item.content }}
</li>
</ul>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import lyricsArray from './data.ts'
import { ref, watch } from 'vue';
const audioElement = ref<any>(null);
const currentTime = ref<string>('')
const activeIndex = ref<number>(0)
const transformStyle = ref<string>('')
console.log(lyricsArray);
function timeUpdateHandler() {
// 處理播放位置變化事件
console.log('播放位置變化事件');
//獲取播放的位置
currentTime.value = audioElement.value.currentTime;
}
//監(jiān)聽播放的位置變化
watch(currentTime, (newVal) => {
for (let i = 0; i < lyricsArray.length; i++) {
if (lyricsArray[i].timestamp < +newVal) {
activeIndex.value = i
transformStyle.value = `transform: translateY(${-activeIndex.value * 30}px)`
}
}
})
function canPlayHandler() {
// 處理可以開始播放事件
console.log('可以開始播放事件');
}
//處理歌詞點(diǎn)擊事件
const audioChange = (i: any) => {
audioElement.value.currentTime = i.timestamp + 1;
}
</script>
<style scoped lang="scss">
.box {
width: 100%;
display: flex;
align-items: center;
background-color: #0E0A0D;
flex-direction: column;
height: 100vh;
.btn {
padding: 10px 15px;
border: none;
border-radius: 10%;
margin-top: 20px;
background-color: #84CCE6;
border-color: #84CCE6;
color: #000;
cursor: pointer;
}
.video {
width: 0;
height: 0;
}
.cd {
width: 400px;
img {
width: 200px;
height: 200px;
border-radius: 50%;
margin: 100px 0 20px 100px;
//添加一個(gè)動(dòng)畫效果,旋轉(zhuǎn)
animation: rotate 5s linear infinite;
@keyframes rotate {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
}
}
.msg {
width: 100%;
height: 300px;
overflow: hidden;
ul {
width: 100%;
display: flex;
flex-direction: column;
//居中
align-items: center;
margin-top: 150px;
transition: ease .5s;
li {
list-style: none;
color: white;
line-height: 30px;
transition: all .5s;
cursor: pointer;
&.active {
color: greenyellow;
transform: scale(1.3);
// font-size: 30px;
}
}
}
}
}
</style>??總結(jié)
歌詞數(shù)據(jù)處理
首先,我們定義了歌詞數(shù)據(jù)的結(jié)構(gòu) Lyric,并通過 parseLyrics 函數(shù)將歌詞字符串解析為該類型的數(shù)組。這使得我們能夠更方便地處理歌詞數(shù)據(jù)。
組件布局
在布局方面,我們?cè)O(shè)計(jì)了一個(gè)簡(jiǎn)單的頁面結(jié)構(gòu),包括音樂播放器、CD封面和歌詞展示區(qū)域。通過 ref 獲取音頻元素的引用,并使用 watch 監(jiān)聽播放位置的變化,實(shí)現(xiàn)了歌詞的滾動(dòng)效果。
事件處理
我們處理了兩個(gè)主要事件:timeupdate(播放位置變化事件)和 canplay(可以開始播放事件)。通過監(jiān)聽播放位置變化,實(shí)時(shí)更新歌詞的高亮位置,并在點(diǎn)擊歌詞時(shí)跳轉(zhuǎn)到對(duì)應(yīng)的播放位置。
功能擴(kuò)展
我這只是實(shí)現(xiàn)了小功能,也不一定是最好的解決方案,大家可以增加更多的功能,讓交互變得更加的有趣,便捷。
到此這篇關(guān)于Vue3中實(shí)現(xiàn)歌詞滾動(dòng)顯示效果的文章就介紹到這了,更多相關(guān)Vue3歌詞滾動(dòng)顯示內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue使用Echarts設(shè)置數(shù)據(jù)無效問題記錄及解決方法
這篇文章主要介紹了vue使用Echarts設(shè)置數(shù)據(jù)無效問題記錄,本文通過場(chǎng)景分析給大家分享解決方法,需要的朋友可以參考下2022-08-08
vue用復(fù)選框?qū)崿F(xiàn)組件且支持單選和多選操作方式
這篇文章主要介紹了vue用復(fù)選框?qū)崿F(xiàn)組件且支持單選和多選操作方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-04-04
vue3表單參數(shù)校驗(yàn)及正則表達(dá)式舉例詳解
最近項(xiàng)目中有一個(gè)校驗(yàn)身份證號(hào)手機(jī)號(hào)的業(yè)務(wù),索性給大家總結(jié)下,這篇文章主要給大家介紹了關(guān)于vue3表單參數(shù)校驗(yàn)及正則表達(dá)式的相關(guān)資料,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-04-04
Element中Upload組件上傳功能實(shí)現(xiàn)(圖片和文件的默認(rèn)上傳及自定義上傳)
這篇文章主要介紹了Element中Upload組件上傳功能實(shí)現(xiàn)包括圖片和文件的默認(rèn)上傳及自定義上傳,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2024-01-01
vue實(shí)現(xiàn)廣告欄上下滾動(dòng)效果
這篇文章主要介紹了vue實(shí)現(xiàn)廣告欄上下滾動(dòng)效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-11-11
vue項(xiàng)目發(fā)布有緩存正式環(huán)境不更新的解決方案
vue項(xiàng)目每次發(fā)布新版本后,測(cè)試人員都要強(qiáng)制刷新才能更新瀏覽器代碼來驗(yàn)證bug,下面這篇文章主要給大家介紹了關(guān)于vue項(xiàng)目發(fā)布有緩存正式環(huán)境不更新的解決方案,需要的朋友可以參考下2024-03-03
vue3基于elementplus 簡(jiǎn)單實(shí)現(xiàn)表格二次封裝過程
公司渲染表格數(shù)據(jù)時(shí)需要將空數(shù)據(jù)顯示‘-’,并且對(duì)于每一列數(shù)據(jù)的顯示也有一定的要求,基于這個(gè)需求對(duì)element-plus簡(jiǎn)單進(jìn)行了二次封裝,這篇文章主要介紹了vue3基于elementplus 簡(jiǎn)單實(shí)現(xiàn)表格二次封裝過程,需要的朋友可以參考下2024-05-05

