Vue3中實現(xiàn)歌詞滾動顯示效果
??前言
在這篇博客中,我將分享如何在 Vue 3 中實現(xiàn)一個簡單的歌詞滾動效果。我將從歌詞數(shù)據(jù)的處理開始,一步步介紹布局的搭建和事件的實現(xiàn)。
??整體布局
1.實現(xiàn)歌詞的滾動,首先應該給歌詞設(shè)置一個特定大小的盒子里,然后可以使用overflow:hidden;來對溢出的歌詞進行隱藏。
2.控制當前該高亮的歌詞的樣式,我是使用transform:scale(1.2)來控制文字變大的。
3.過度動畫,給高亮顯示的歌詞加上過度動畫效果,還有整個歌詞區(qū)域移動的動畫效果,以及黑膠唱片的旋轉(zhuǎn)動畫。
記住要給video標簽的width和height設(shè)置為0,雖然默認情況下不顯示,但是還是會影響布局的
<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; //添加一個動畫效果,旋轉(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ù),我把它放在了一個ts文件中,我們可以在這里對數(shù)據(jù)進行處理好后導出給組件使用
const dataStr = ` 00:00 淘汰 – 陳奕迅 (Eason Chan) 00:08 詞:周杰倫 00:17 曲:周杰倫 00:26 編曲:C.Y.Kong 00:35 我說了所有的謊 00:39 你全都相信 00:43 簡單的我愛你 00:46 你卻老不信 00:51 你書里的劇情 00:55 我不想上演 00:58 因為我喜歡 01:01 喜劇收尾 01:08 我試過完美放棄 01:12 的確很踏實 01:15 醒來了夢散了 01:19 你我都走散了 01:23 情歌的詞何必押韻 01:27 就算我是K歌之王 01:31 也不見得把 01:33 愛情唱得完美 01:38 只能說我輸了 01:42 也許是你怕了 01:46 我們的回憶沒有皺褶 01:51 你卻用離開燙下句點 01:54 只能說我認了 01:58 你的不安贏得你信任 02:03 我卻得到你安慰的淘汰 02:25 我試過完美放棄 02:29 的確很踏實 02:32 醒來了夢散了 02:36 你我都走散了 02:40 情歌的詞何必押韻 02:44 就算我是K歌之王 02:48 也不見得把 02:50 愛情唱得完美 02:55 只能說我輸了 02:59 也許是你怕了 03:03 我們的回憶沒有皺褶 03:08 你卻用離開燙下句點 03:11 只能說我認了 03:15 你的不安贏得你信任 03:21 我卻得到你安慰的淘汰 03:44 只能說我輸了 03:48 也許是你怕了 03:52 我們的回憶沒有皺褶 03:57 你卻用離開燙下句點 04:00 只能說我認了 04:04 你的不安贏得你信任 04:09 我卻得到你安慰的淘汰`
現(xiàn)在這樣看著是一整個字符串,因為不好操作,所以我們要把它變成我們想要的形式。
我想要的是這種感覺,解析成一個數(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 // 將時間戳轉(zhuǎn)換為秒 const timestamp = parseInt(minutes) * 60 + parseInt(seconds) lyrics.push({ timestamp, content }) } } return lyrics } // 使用解析函數(shù)獲取歌詞數(shù)組 const lyricsArray: Lyric[] = parseLyrics(dataStr) //導出 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>
第一個問題,哪句歌詞得高亮
在video的dom元素上我們可以通過 currentTime 來獲取它此時的播放時間位置,我們可以監(jiān)聽它的位置變化,然后得到需要顯示的歌詞的 Index,然后再li標簽上對其active類名進行動態(tài)顯示就行了。
第二個問題,偏移量
既然我們之前有給li標簽設(shè)置line-height ,那么我們就可以再Index變化的時候一起進行計算出來。
可以再添加一個交互,就是用戶點擊某句歌詞,跳動那個地方去
<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() { //預處理完成,可以開始播放 console.log('可以開始播放'); } //處理歌詞點擊事件 const audioChange = (i: any) => { audioElement.value.currentTime = i.timestamp + 1; } </script>
這樣點擊按鈕后就能看到完美的效果了
??完整代碼
處理歌詞:
//導出歌詞數(shù)據(jù) const dataStr = ` 00:00 淘汰 – 陳奕迅 (Eason Chan) 00:08 詞:周杰倫 00:17 曲:周杰倫 00:26 編曲:C.Y.Kong 00:35 我說了所有的謊 00:39 你全都相信 00:43 簡單的我愛你 00:46 你卻老不信 00:51 你書里的劇情 00:55 我不想上演 00:58 因為我喜歡 01:01 喜劇收尾 01:08 我試過完美放棄 01:12 的確很踏實 01:15 醒來了夢散了 01:19 你我都走散了 01:23 情歌的詞何必押韻 01:27 就算我是K歌之王 01:31 也不見得把 01:33 愛情唱得完美 01:38 只能說我輸了 01:42 也許是你怕了 01:46 我們的回憶沒有皺褶 01:51 你卻用離開燙下句點 01:54 只能說我認了 01:58 你的不安贏得你信任 02:03 我卻得到你安慰的淘汰 02:25 我試過完美放棄 02:29 的確很踏實 02:32 醒來了夢散了 02:36 你我都走散了 02:40 情歌的詞何必押韻 02:44 就算我是K歌之王 02:48 也不見得把 02:50 愛情唱得完美 02:55 只能說我輸了 02:59 也許是你怕了 03:03 我們的回憶沒有皺褶 03:08 你卻用離開燙下句點 03:11 只能說我認了 03:15 你的不安贏得你信任 03:21 我卻得到你安慰的淘汰 03:44 只能說我輸了 03:48 也許是你怕了 03:52 我們的回憶沒有皺褶 03:57 你卻用離開燙下句點 04:00 只能說我認了 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 // 將時間戳轉(zhuǎn)換為秒 const timestamp = parseInt(minutes) * 60 + parseInt(seconds) lyrics.push({ timestamp, content }) } } return lyrics } // 使用解析函數(shù)獲取歌詞數(shù)組 const lyricsArray: Lyric[] = parseLyrics(dataStr) //導出 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('可以開始播放事件'); } //處理歌詞點擊事件 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; //添加一個動畫效果,旋轉(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ù)。
組件布局
在布局方面,我們設(shè)計了一個簡單的頁面結(jié)構(gòu),包括音樂播放器、CD封面和歌詞展示區(qū)域。通過 ref
獲取音頻元素的引用,并使用 watch
監(jiān)聽播放位置的變化,實現(xiàn)了歌詞的滾動效果。
事件處理
我們處理了兩個主要事件:timeupdate
(播放位置變化事件)和 canplay
(可以開始播放事件)。通過監(jiān)聽播放位置變化,實時更新歌詞的高亮位置,并在點擊歌詞時跳轉(zhuǎn)到對應的播放位置。
功能擴展
我這只是實現(xiàn)了小功能,也不一定是最好的解決方案,大家可以增加更多的功能,讓交互變得更加的有趣,便捷。
到此這篇關(guān)于Vue3中實現(xiàn)歌詞滾動顯示效果的文章就介紹到這了,更多相關(guān)Vue3歌詞滾動顯示內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue使用Echarts設(shè)置數(shù)據(jù)無效問題記錄及解決方法
這篇文章主要介紹了vue使用Echarts設(shè)置數(shù)據(jù)無效問題記錄,本文通過場景分析給大家分享解決方法,需要的朋友可以參考下2022-08-08vue用復選框?qū)崿F(xiàn)組件且支持單選和多選操作方式
這篇文章主要介紹了vue用復選框?qū)崿F(xiàn)組件且支持單選和多選操作方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-04-04Element中Upload組件上傳功能實現(xiàn)(圖片和文件的默認上傳及自定義上傳)
這篇文章主要介紹了Element中Upload組件上傳功能實現(xiàn)包括圖片和文件的默認上傳及自定義上傳,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友參考下吧2024-01-01vue項目發(fā)布有緩存正式環(huán)境不更新的解決方案
vue項目每次發(fā)布新版本后,測試人員都要強制刷新才能更新瀏覽器代碼來驗證bug,下面這篇文章主要給大家介紹了關(guān)于vue項目發(fā)布有緩存正式環(huán)境不更新的解決方案,需要的朋友可以參考下2024-03-03vue3基于elementplus 簡單實現(xiàn)表格二次封裝過程
公司渲染表格數(shù)據(jù)時需要將空數(shù)據(jù)顯示‘-’,并且對于每一列數(shù)據(jù)的顯示也有一定的要求,基于這個需求對element-plus簡單進行了二次封裝,這篇文章主要介紹了vue3基于elementplus 簡單實現(xiàn)表格二次封裝過程,需要的朋友可以參考下2024-05-05