利用原生HTML + CSS + JS实现歌词滚动

news2024/12/23 4:30:05

        对于很多音乐APP,都有这么一个功能,就是根据歌曲的进度来控制对应的歌词滚动,如下图所示:

大概这样的效果,我此次是使用原生的HTML+CSS+JS来实现的,以下是具体的实现过程。

1. 数据获取与处理

        对于数据来源,这里由于只有前端展示,所以我们直接使用死数据,杰伦的《我不配》,数据如下(音频可以找我要):

歌词数据如下:

var lrc = `[00:00.06]︿☆我不配☆︿
[00:00.75]
[00:01.11]演唱:周杰伦
[00:02.62]
[00:03.35]︿☆歌词制作:ikun
[00:06.13]→QQ:2682548155←
[00:09.30]www.90lrc.cn ★【歌词网】
[00:11.09]
[00:18.40]这街上太拥挤 太多人有秘密
[00:22.66]玻璃上有雾气 在被隐藏起过去
[00:27.10]你脸上的情绪 在还原那场雨
[00:31.61]这巷弄太过弯曲 走不回故事里
[00:36.00]
[00:36.10]这日子不再绿 又斑驳了几句
[00:40.49]剩下搬空回忆的我在大房子里
[00:44.92]电影院的座椅 隔遥远的距离
[00:49.24]感情没有对手戏 你跟自己下棋
[00:53.70]
[00:53.80]还来不及 仔仔细细写下你的关于
[01:02.65]描述我如何爱你 你却微笑的离我而去
[01:10.90]
[01:11.50]这感觉 已经不对 我努力在挽回
[01:15.90]一些些 应该体贴的感觉 我没给
[01:20.32]你嘟嘴 许的愿望很卑微 在妥协
[01:24.50]是我忽略 你不过要人陪
[01:29.10]
[01:29.19]这感觉 已经不对 我最后才了解
[01:33.57]一页页 不忍翻阅的情节 你好累
[01:38.03]你默背 为我掉过几次泪 多憔悴
[01:42.30]而我心碎 你受罪你的美 我不配
[01:49.58]
[02:04.98]这街上太拥挤 太多人有秘密
[02:09.37]玻璃上有雾气 在被隐藏起过去
[02:13.80]你脸上的情绪 在还原那场雨
[02:18.25]这巷弄太过弯曲 走不回故事里
[02:22.61]
[02:22.82]这日子不再绿 又斑驳了几句
[02:27.26]剩下搬空回忆的我在大房子里
[02:31.58]电影院的座椅 隔遥远的距离
[02:35.99]感情没有对手戏 你跟自己下棋
[02:40.24]
[02:40.26]还来不及 仔仔细细写下你的关于
[02:49.04]描述我如何爱你 你却微笑的离我而去
[02:57.42]
[02:58.20]这感觉 已经不对 我努力在挽回
[03:02.56]一些些 应该体贴的感觉 我没给
[03:06.98]你嘟嘴 许的愿望很卑微 在妥协
[03:11.10]是我忽略 你不过要人陪
[03:15.50]
[03:15.78]这感觉 已经不对 我最后才了解
[03:20.20]一页页 不忍翻阅的情节 你好累
[03:24.66]你默背 为我掉过几次泪 多憔悴
[03:28.98]而我心碎 你受罪你的美 我不配
[03:36.12]
[03:47.30]这感觉 已经不对 我努力在挽回
[03:51.38]一些些 应该体贴的感觉 我没给
[03:55.79]你嘟嘴 许的愿望很卑微 在妥协
[04:00.00]是我忽略 你不过要人陪
[04:04.54]
[04:04.64]这感觉 已经不对 我最后才了解
[04:09.03]一页页 不忍翻阅的情节 你好累
[04:13.64]你默背 为我掉过几次泪 多憔悴
[04:17.95]而我心碎 你受罪你的美 我不配
[04:25.70]`

问题来了,这个数据是一条又丑又长的字符串,我们需要把他解析成对象才好处理啊,因此,第一件事应该是写解析函数:

const parseTime = (arr) => { //将时间解析成秒s
    let times = arr.split(":");
    let seconds = parseFloat(times[0])*60+parseFloat(times[1]);
    return seconds;
}
export const parseLrc = () => { //解析字符串
    let result = [];
    let lines = lrc.split("\n");
    for (let i = 0; i < lines.length; i++) {
       let line = lines[i];
       let arrs = line.split("]");
       let obj = {
           time: parseTime(arrs[0].substring(1)),
           text: arrs[1]
       }
       result.push(obj);
    }
    return result;
}

2. 页面设计

之后我们把HTML页面和CSS样式大概写好,这里比较简单,直接写上:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>歌曲播放器</title>
    <link rel="stylesheet" href="css/index.css" />
</head>
<body>
    <audio src="asserts/music.mp3" controls></audio>
    <div class="container">
        <ul class="data-list">
        </ul>
    </div>
</body>
<script type="module" src="js/index.js"></script>
</html>

CSS样式如下:

*{
    margin: 0;
    padding: 0;
}
body{
    background-color: #000;
    color: #666;
    text-align: center;
}
audio {
    width: 450px;
    margin: 30px 0;
}
.container{
    height: 500px;
    overflow: hidden;
}
.container::-webkit-scrollbar{
    display: none;
}
.container ul{
    list-style: none;
    transition: 0.6s;
    /*transform: translateY(-20px);*/
}
.container ul li{
    height: 30px;
    line-height: 30px;
    font-size: 18px;
    transition: 0.6s;
}
.container ul li.active{
    color: #fff;
    transform: scale(1.2);
}

3. 歌词滚动效果实现

        对于歌词滚动效果,我们具体分析一下,无非就是歌词整体向上移,时间点对应的词高亮一下,对于高亮效果,直接使用.active的CSS属性来实现,具体就是将字体放大,颜色变为亮白色:

.container ul li.active{
    color: #fff;
    transform: scale(1.2);
}

        对于移动,我们可以根据audio的进度来移动这个music-list容器,可以使用margin-top或者transform:translateY()来移动,而具体的移动高度可以参考下图

        自此,我们可以实现这个效果了:

// 移动...
let currentIndex = 0;
const move = () => { 
    currentIndex = getMusicIndex(); //当前高亮的歌词下标
    let containerHeight = domData.container.clientHeight; //Container高度
    let liHeight = domData.ul.children[0].clientHeight; // 每个li标签的高度
    let movePx = liHeight * currentIndex + liHeight/2 - containerHeight/2; //需要移动的
    let maxMove = domData.ul.clientHeight - containerHeight/2;
    // 范围判断
    if(movePx < 0){
        movePx = 0;
    }
    if(movePx > maxMove){
        movePx = maxMove;
    }
    // 取消前面的高亮
    let activeLi = domData.ul.querySelector('.active');
    if(activeLi){
        activeLi.classList.remove("active");
    }
    // 实现高亮
    let currentLi = domData.ul.children[currentIndex];
    if(currentLi){
        currentLi.classList.add('active');
    }
    // 移动
    domData.ul.style.transform = `translateY(-${movePx}px)`;
}
domData.audio.addEventListener('timeupdate',move);

4. index.js

import {parseLrc} from "./data.js";


let domData = {
    ul: document.querySelector('.container ul'),
    audio: document.querySelector('audio'),
    container: document.querySelector('.container'),
} //dom数据
let musicObj = parseLrc(); // 音乐数据


const getAudioTime = () => {
    let result = domData.audio.currentTime;
    return result;
}

const addMusic = () => {
    let documentFragment = document.createDocumentFragment();

    for(let i = 0; i < musicObj.length; i++){
        let li = document.createElement('li');
        li.textContent = musicObj[i].text;
        documentFragment.appendChild(li);
    }

    domData.ul.appendChild(documentFragment);
}

// 根据时间来获取当前需要显示的条数
const getMusicIndex = () => {
    let time = getAudioTime();
    for(let i = 0;i < musicObj.length; i++) {
        let musicTime = musicObj[i].time;
        if(time < musicTime){
            return i-1;
        }
    }
    return musicObj.length - 1;
}
// 移动...
let currentIndex = 0;
const move = () => {
    currentIndex = getMusicIndex(); //当前高亮的歌词下标
    let containerHeight = domData.container.clientHeight; //Container高度
    let liHeight = domData.ul.children[0].clientHeight; // 每个li标签的高度
    let movePx = liHeight * currentIndex + liHeight/2 - containerHeight/2; //需要移动的
    let maxMove = domData.ul.clientHeight - containerHeight/2;
    // 范围判断
    if(movePx < 0){
        movePx = 0;
    }
    if(movePx > maxMove){
        movePx = maxMove;
    }
    // 取消前面的高亮
    let activeLi = domData.ul.querySelector('.active');
    if(activeLi){
        activeLi.classList.remove("active");
    }
    // 实现高亮
    let currentLi = domData.ul.children[currentIndex];
    if(currentLi){
        currentLi.classList.add('active');
    }
    // 移动
    domData.ul.style.transform = `translateY(-${movePx}px)`;
}
domData.audio.addEventListener('timeupdate',move);

const init = () => {
    //获取页面歌词
//     插入歌词
    addMusic();
    console.log(musicObj)
//     根据时间来移动
}
init(); //入口函数

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

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

相关文章

揭秘低代码平台:解锁表尾统计方案

前言 在现代Web应用中&#xff0c;数据表格是常见的界面元素之一&#xff0c;用于展示和管理大量的数据。而vxe-table作为Vue.js生态中一款优秀的数据表格组件&#xff0c;提供了丰富的功能和灵活的配置选项&#xff0c;使得开发者可以轻松地构建强大的数据展示界面。 然而&…

普林斯顿大学教授终于把算法整理成图解笔记

普林斯顿大学教授终于把算法整理成图解笔记了&#xff01;&#xff01;&#xff01; 这些年虽然学到的编程知识越来越多&#xff0c;但是我对算法却始终没搞明白&#xff0c;直到偶然间看到这份笔记&#xff0c;我才认识到这些概念是多么简单。 对于很多刚入门的小伙伴来说&am…

充电学习—1、psy框架梳理

一、linux充电驱动代码框架&#xff1a; APP 层 该部分属于电量上报的最后的环节。其主要工作是&#xff1a;监听系统广播并对 UI 作出相应更新&#xff0c;包括电池电量百分比&#xff0c;充电状态&#xff0c;低电提醒&#xff0c;led 指示灯&#xff0c;异常提醒等FrameWork…

字符串专题详解

目录 字符串hash进阶 KMP算法 next数组 KMP算法 KMP算法优化 字符串hash进阶 字符串hash是指将一个字符串S映射为一个整数&#xff0c;使得该整数可以尽可能唯一地代表字符串S。那么在一定程度上&#xff0c;如果两个字符串转换成的整数相等&#xff0c;就可以认为这两个…

Typora—适用于 Mac 和 Win 系统的优秀 Markdown 文本编辑器

Typora 是一款适用于 Mac 和 Win 系统的优秀 Markdown 文本编辑器&#xff0c;它以其简洁易用的界面和强大的功能受到了众多用户的喜爱。 首先&#xff0c;Typora 的界面设计非常简洁直观&#xff0c;没有过多繁杂的菜单和按钮&#xff0c;让用户能够专注于写作本身。它采用实时…

【计算机毕业设计】235基于微信小程序点餐系统

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

抖音用户新作品监控助手,第一时间获取博主作品信息。

声明&#xff1a; 本文以教学为基准、本文提供的可操作性不得用于任何商业用途和违法违规场景。本人对任何原因在使用本人中提供的代码和策略时可能对用户自己或他人造成的任何形式的损失和伤害不承担责任。包含关注&#xff0c;点赞等 抖音新作品监控助手系统是一个功能强大的…

华火新能源集成灶评测:创新与品质的融合

在厨房电器的不断推陈出新中&#xff0c;华火新能源集成灶以其独特的魅力进入了人们的视野。今天&#xff0c;我们就来深入评测这款备受关注的产品——华火新能源集成灶 一、华火新能源集成灶的创新与环保 首先&#xff0c;我们先来探讨新能源集成灶的整体表现。华火新能源集成…

Java健身私教服务师傅小程序APP源码(APP+小程序+公众号+H5)

强身健体&#xff0c;私人定制的健身之旅 &#x1f3cb;️ 引言&#xff1a;探索私人健身新纪元 在现代都市的快节奏生活中&#xff0c;越来越多的人开始注重身体健康和健身塑形。然而&#xff0c;传统的健身房模式可能无法满足每个人的个性化需求。这时&#xff0c;一款名为…

如何利用V-Ray优化渲染效果——渲染100邀请码【7788】

3dmax效果图云渲染--渲染100 以3ds Max2025、VR6.2、CR11.2等最新版本为基础&#xff0c;兼容fp、acescg等常用插件&#xff0c;同时UT滤镜等参数也得到了同步支持。 注册填邀清码[7788]领30元礼包和免费渲染券哦~ 第一步&#xff1a;初次渲染 打开场景并将V-Ray设置为当前渲染…

基于复旦微FM33FG065A+OSRAM RGBi车载氛围灯方案

应用场景&#xff1a; 汽车内饰照明&#xff1a;这种方案可以用于改善汽车内部的氛围&#xff0c;提升驾驶舒适度和乘客体验。可以通过调节灯光颜色和亮度来营造不同的氛围&#xff0c;比如温馨的暖色调或者清爽的冷色调。 个性化定制&#xff1a;汽车制造商或者车主可以根据个…

kubesphere踩过的坑,持续更新....

踩过的坑 The connection to the server lb.kubesphere.local:6443 was refused - did you specify the right host… 另一篇文档中 dashboard 安装 需要在浏览器中输入thisisunsafe,即可进入登录页面 ingress 安装的问题 问题描述&#xff1a; 安装后通过命令 kubectl g…

Python实现连连看11

(2)isOneCornerLink()函数 isOneCornerLink()函数判断图片是否是单拐点连通。判断是否是单拐点连通有两种方式,如图14所示。 图14 判断单拐点连通的两种方式 从图14中可以看出,判断两张图片是否是单拐点连通,实际上就是判断图中红点与绿点所在图片是否分别与这两张图是直…

电商平台数据的认知与深度理解

随着信息技术的迅猛发展&#xff0c;电商平台已成为现代社会商业活动的重要舞台。在这个舞台上&#xff0c;数据不仅是交易的记录&#xff0c;更是企业决策的依据、用户行为的镜子和市场变化的晴雨表。本文将从多个维度对电商平台数据进行全面且深入的认知和理解。 一、数据的…

深入理解MySQL字符集

一、字符集介绍 字符集&#xff08;Character Set&#xff09;是多个字符的集合&#xff0c;它规定了字符在计算机中的编码方式。以下是关于字符集的详细介绍&#xff1a; 1. 字符集的定义与作用 字符集是各种文字和符号的总称&#xff0c;包括各国家文字、标点符号、图形符号…

图像分割——U-Net论文介绍+代码(PyTorch)

0、概要 原理大致介绍了一下&#xff0c;后续会不断精进改的更加详细&#xff0c;然后就是代码可以对自己的数据集进行一个训练&#xff0c;还会不断完善&#xff0c;相应其他代码可以私信我。 一、论文内容总结 摘要&#xff1a;人们普遍认为&#xff0c;深度网络成功需要数…

HarmonyOS开发日记 :自定义节点,实现 UI 组件 动态创建、更新

引言 UI动态操作包含组件的动态创建、卸载、更新等相关操作。 通过组件预创建&#xff0c;可以满足开发者在非build生命周期中进行组件创建&#xff0c;创建后的组件可以进行属性设置、布局计算等操作。之后在页面加载时进行使用&#xff0c;可以极大提升页面响应速度。 UI …

中国500米分辨率年最大LAI数据集(2000-2020)

叶面积指数是生态系统的一个重要结构参数&#xff0c;用来反映植物叶面数量、冠层结构变化、植物群落生命活力及其环境效应&#xff0c;为植物冠层表面物质和能量交换的描述提供结构化的定量信息&#xff0c;并在生态系统碳积累、植被生产力和土壤、植物、大气间相互作用的能量…

[Qt的学习日常]--常用控件2

前言 作者&#xff1a;小蜗牛向前冲 名言&#xff1a;我可以接受失败&#xff0c;但我不能接受放弃 如果觉的博主的文章还不错的话&#xff0c;还请点赞&#xff0c;收藏&#xff0c;关注&#x1f440;支持博主。如果发现有问题的地方欢迎❀大家在评论区指正 目录 一、widget的…

如何使用视频文案提取帮手将手机上视频里的声音转成文字?

在自媒体短视频日益增加的时候不少自媒体创作者如何将视频转文字的需求日益增加。本次将给大家分享一款针对广大职场青年用户群体的视频转文字工具&#xff0c;旨在为用户提供高效、准确的视频转文字服务。 如何将手机上的视频转成文字呢 视频转文字工具具有转换速度快&#…