Vue3中实现歌词滚动显示效果

news2024/11/25 16:52:23

目录

🎉前言

 🎉整体布局

🎉处理歌词数据

🎉处理事件

🎉完整代码

🎉总结


🎉前言

在这篇博客中,我将分享如何在 Vue 3 中实现一个简单的歌词滚动效果。我将从歌词数据的处理开始,一步步介绍布局的搭建和事件的实现。

 🎉整体布局

1.实现歌词的滚动,首先应该给歌词设置一个特定大小的盒子里,然后可以使用overflow:hidden;来对溢出的歌词进行隐藏。

2.控制当前该高亮的歌词的样式,我是使用transform:scale(1.2)来控制文字变大的。

3.过度动画,给高亮显示的歌词加上过度动画效果,还有整个歌词区域移动的动画效果,以及黑胶唱片的旋转动画。

记住要给video标签的width和height设置为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;
      //添加一个动画效果,旋转
      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>

🎉处理歌词数据

这是歌词数据,我把它放在了一个ts文件中,我们可以在这里对数据进行处理好后导出给组件使用

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 我却得到你安慰的淘汰`

现在这样看着是一整个字符串,因为不好操作,所以我们要把它变成我们想要的形式。

我想要的是这种感觉,解析成一个数组就方便我们渲染以及处理事件。

 所以我们这里就使用一下分割匹配得到我们想要的效果

// 定义歌词数据类型
type Lyric = {
  timestamp: number
  content: string
}

//解析歌词数据
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
      // 将时间戳转换为秒
      const timestamp = parseInt(minutes) * 60 + parseInt(seconds)
      lyrics.push({ timestamp, content })
    }
  }

  return lyrics
}
// 使用解析函数获取歌词数组
const lyricsArray: Lyric[] = parseLyrics(dataStr)

//导出
export default lyricsArray

这样子,就得到我们想要的结果了

🎉处理事件

首先我们得思考需要处理哪些事件

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 来获取它此时的播放时间位置,我们可以监听它的位置变化,然后得到需要显示的歌词的 Index,然后再li标签上对其active类名进行动态显示就行了。

第二个问题,偏移量

既然我们之前有给li标签设置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;
}
//监听播放的位置变化
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>

这样点击按钮后就能看到完美的效果了

🎉完整代码

处理歌词:

//导出歌词数据
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 我却得到你安慰的淘汰`

// 定义歌词数据类型
type Lyric = {
  timestamp: number
  content: string
}

//解析歌词数据
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
      // 将时间戳转换为秒
      const timestamp = parseInt(minutes) * 60 + parseInt(seconds)
      lyrics.push({ timestamp, content })
    }
  }

  return lyrics
}
// 使用解析函数获取歌词数组
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;
}
//监听播放的位置变化
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;
      //添加一个动画效果,旋转
      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>

🎉总结

歌词数据处理

首先,我们定义了歌词数据的结构 Lyric,并通过 parseLyrics 函数将歌词字符串解析为该类型的数组。这使得我们能够更方便地处理歌词数据。

组件布局

在布局方面,我们设计了一个简单的页面结构,包括音乐播放器、CD封面和歌词展示区域。通过 ref 获取音频元素的引用,并使用 watch 监听播放位置的变化,实现了歌词的滚动效果。

事件处理

我们处理了两个主要事件:timeupdate(播放位置变化事件)和 canplay(可以开始播放事件)。通过监听播放位置变化,实时更新歌词的高亮位置,并在点击歌词时跳转到对应的播放位置。

功能扩展

我这只是实现了小功能,也不一定是最好的解决方案,大家可以增加更多的功能,让交互变得更加的有趣,便捷。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1426474.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Spring 学习1

1、什么是Spring Spring 是一款主流的 Java EE 轻量级开源框架 &#xff0c;Spring 由“Spring 之父”Rod Johnson 提出并创立&#xff0c;其目的是用于简化 Java 企业级应用的开发难度和开发周期。Spring的用途不仅限于服务器端的开发。从简单性、可测试性和松耦合的角度而言…

HTML+CSS+JS的3D进度条

<!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>HTMLCSSJS的3D进度条</title><style>…

C#,斯特林数(Stirling Number)的算法与源代码

1 斯特林数 在组合数学&#xff0c;斯特林数可指两类数&#xff0c;第一类斯特林数和第二类斯特林数&#xff0c;都是由18世纪数学家James Stirling提出的。它们自18世纪以来一直吸引许多数学家的兴趣&#xff0c;如欧拉、柯西、西尔沃斯特和凯莱等。后来哥本哈根&#xff08;…

JUC并发编程01——进程,线程(详解),并发和并行

目录 1.进程和线程的概念及对比1.进程概述 2.线程3.对比 2.并行与并发1.并发2.并行 3.线程详解3.1.创建和运行线程3.1.1.Thread3.1.2.Runnable结合Thread 创建线程3.1.3.Callable 3.2线程方法APIrun startsleep yieldjoininterrupt打断线程打断 park终止模式 daemon不推荐使用的…

MATLAB中hilb函数用法

MATLAB中的hilb函数用于生成希尔伯特矩阵。 语法为: H hilb(n) 其中: n: 生成的希尔伯特矩阵的阶数 H: 生成的n阶希尔伯特矩阵 希尔伯特矩阵又称为希尔伯特运算矩阵,它是一种测试矩阵,元素H(i,j) 1/(ij-1),i和j表示矩阵的行号和列号。 例如: H hilb(7)会生成一个7阶…

SPI指数计算(Standardized Precipitation Index,标准化降水指数) 附完整MATLAB代码

SPI指数(Standardized Precipitation Index,标准化降水指数)是反映干湿状况的一个指标,主要计算步骤如下: 收集研究区域过去30年或以上时间尺度(一般选取30年)的月降水量资料。 对月降水量资料进行统计分析,拟合出最适合的概率分布函数。常用的有Pearson III 分布、Gamma分布…

【Docker】使用VS创建、运行、打包、部署.net core 6.0 webapi

欢迎来到《小5讲堂》&#xff0c;大家好&#xff0c;我是全栈小5。 这是《Docker容器》系列文章&#xff0c;每篇文章将以博主理解的角度展开讲解&#xff0c; 特别是针对知识点的概念进行叙说&#xff0c;大部分文章将会对这些概念进行实际例子验证&#xff0c;以此达到加深对…

【DDD】学习笔记-限界上下文对架构的影响

通信边界对架构的影响 限界上下文的通信边界会对系统的架构产生直接的影响&#xff0c;在此之前&#xff0c;我们需要理清几个和边界有关的概念。如前所述&#xff0c;我提出了限界上下文的通信边界的概念&#xff0c;并将其分为进程内通信与进程间通信两种方式。在 Toby Clem…

文生图提示词:城市景观

场景描述 --城市景观 Urban Landscapes 涵盖了多种城市景观元素&#xff0c;可以用于精确地表达 AI 生成图像中所需的城市环境。 Cityscape 城市景观 Downtown 市中心 Skyline 天际线 Skyscraper 摩天大楼 Street 街道 Avenue 大道 Boulevard 林荫大道 Plaza 广场 Park 公园 Si…

STM32——看门狗

STM32——看门狗 1.独立看门狗IWDG 独立看门狗介绍 什么是看门狗&#xff1f; 在由单片机构成的微型计算机系统中&#xff0c;由于单片机的工作常常会受到来自外界电磁场的干扰&#xff0c;造成程序的跑飞&#xff0c;而陷入死循环&#xff0c;程序的正常运行被打断&#x…

kubekey网页版安装k8s集群操作流程

kubekey可以一键拉起k8s集群并完成kubesphere的部署&#xff0c;以后kubekey简称kk。kk 3.2版本以前都是在宿主机上完成对应的创建集群、添加节点、升级等操作的&#xff0c;3.2版本后开始往页面操作的方向演进&#xff0c;kk 3.2版本现在还是alpha&#xff0c;所以不推荐在生产…

Unity3d Shader篇(一)— 顶点漫反射着色器解析

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、顶点漫反射着色器是什么&#xff1f;1. 顶点漫反射着色器的工作原理 二、编写顶点漫反射着色器1. 定义属性2. 创建 SubShader3. 编写着色器程序段4. 完成顶…

面试宝典之深谈JVM

面试宝典之深谈JVM 1.为什么需要JVM&#xff0c;不要JVM可以吗&#xff1f; 1.JVM可以帮助我们屏蔽底层的操作系统 一次编译&#xff0c;到处运行 2.JVM可以运行Class文件 2.JDK&#xff0c;JRE以及JVM的关系 3.我们的编译器到底干了什么事&#xff1f; 仅仅是将我们的 .ja…

coreldraw怎么添加箭头?

使用coreldraw的时候知道箭头在哪里添加吗&#xff1f;下面小编就给大家带来coreldraw箭头添加教程&#xff0c;有需要的小伙伴不要错过哦。 coreldraw添加箭头方法 1、首先选择桌面Coreldraw格式图片。 2、然后点击文件夹按钮打开文件。 3、最后点击上方工具横线&#xff0c…

2024最新版TypeScript安装使用指南

2024最新版TypeScript安装使用指南 Installation and Development Guide to the Latest TypeScript in 2024 By JacksonML 1. 什么是TypeScript? TypeScript is JavaScript with syntax for types. – typescriptlang.org TypeScript 是 JavaScript 的一个超集&#xff0c;…

WPS WORD 宏导出高亮文本

WPS手机版可以直接导出高亮文本&#xff0c;但只能导出手机编辑的部分&#xff0c;如果同时在电脑上编辑过&#xff0c;电脑上高亮的无法导出&#xff0c;因为作者不一样。 但WPS电脑版没有这个功能&#xff0c;只能通过宏编程实现。 这里利用了审阅模式&#xff0c;在文字高亮…

Cesium 展示——加载 glb 格式的数据

文章目录 需求分析需求 加载渲染 glb 格式的数据, 并实现模型上的点击事件 分析 模型加载// 加载模型const position = new Cesium.Cartesian3.fromDegrees(118.29355875458516,39.51516823255016

Flink 流式读取 Debezium CDC 数据写入 Hudi 表无法处理 -D / Delete 消息

问题场景是&#xff1a;使用 Kafka Connect 的 Debezium MySQL Source Connector 将 MySQL 的 CDC 数据 &#xff08;Avro 格式&#xff09;接入到 Kafka 之后&#xff0c;通过 Flink 读取并解析这些 CDC 数据&#xff0c;然后以流式方式写入到 Hudi 表中&#xff0c;测试中发现…

HarmonyOS4.0系统性深入开发31创建列表(List)

创建列表&#xff08;List&#xff09; 概述 列表是一种复杂的容器&#xff0c;当列表项达到一定数量&#xff0c;内容超过屏幕大小时&#xff0c;可以自动提供滚动功能。它适合用于呈现同类数据类型或数据类型集&#xff0c;例如图片和文本。在列表中显示数据集合是许多应用…

贰[2],Xamarin生成APK

1&#xff0c;生成改为Release版本 2&#xff0c;选中****.Android项目 3&#xff0c;点击生成&#xff0c;选择存档 4&#xff0c;点击分发 5&#xff0c;选择临时 6&#xff0c;添加签名标识 7&#xff0c;选择对应的签名标识&#xff0c;点击另存为