使用js,html,css实现歌词滚动的效果

简介: 使用js,html,css实现歌词滚动的效果


html


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="shortcut icon" href="./assets/favicon.ico" type="image/x-icon">
    <link rel="stylesheet" href="./style.css">
    <title>歌词滚动效果</title>
</head>
<body>
    <div class="contain">
        <audio src="https://m704.music.126.net/20240113180245/ce64353d84d93d230abf3ab672bfa732/jdymusic/obj/wo3DlMOGwrbDjj7DisKw/28481681349/59fe/87c8/8ed7/1b985989835af163dd057b1e3cbd74ea.mp3?_authSecret=0000018d022f36cf01dd0aa463681994" controls></audio>
        <div class="box">
            <ul>
             // 这里等后面使用js动态生成
            </ul>
        </div>
    </div>
    <script src="./main.js" type="module"></script>
</body>
</html>

css

css

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
    list-style: none;
}
.contain {
    width: 100vw;
    height: 100vh;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 16px;
    background: #aa4b6b;  /* fallback for old browsers */
    background: -webkit-linear-gradient(to right, #3b8d99, #6b6b83, #aa4b6b);  /* Chrome 10-25, Safari 5.1-6 */
    background: linear-gradient(to right, #3b8d99, #6b6b83, #aa4b6b); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
}
audio{
    width: 50%;
    height: 50px;
}
.box{
    width: 550px;
    height: 420px;
    text-align: center;
    color: #f6f6f6;
    
    /* 超出隐藏 */
    overflow: hidden;
}
ul {
   transition: .6s;
} 
li{
    height: 30px;
    line-height: 30px;
    transition: 0.2s;
}
.active{
    color: rgb(255, 150, 4);
    transform: scale(1.3);
}

js部分

1.导出资源

首先将准备好的歌词字符串进行导出

js

var lrc = `[00:01.06]难念的经
 [00:03.95]演唱:周华健
 [00:06.78]
 [00:30.96]笑你我枉花光心计
 [00:34.15]爱竞逐镜花那美丽
 [00:36.75]怕幸运会转眼远逝
 [00:39.32]为贪嗔喜恶怒着迷
 [00:41.99]责你我太贪功恋势ƒ
 [00:44.48]怪大地众生太美丽
 ....`;
 
 export default lrc

2. 引入资源

在主文件里面进行引入

js

import lrc from "./assets/data.js";
 console.log(lrc)

3. 歌词字符串转换为 数组对象的形式

解析歌词 转换为歌词数组对象的形式

因为目前歌词lrc 只是一个字符串对象,里面包含了歌曲时间和对应的歌词,但是在字符串里面,不好操作,我们需要将每一句歌词以及开始的时间放入一个歌词对象里面,然后将每一个歌词对象放入数组里面

js

const parseLrc = () => {
     // 准备好一个包含歌词对象的数组
     const lrcData = [];
     // 按照"]"字符进行分割
     const lines = lrc.split('\n');
     // 生成一个数组,用来放置每一句歌词对象(包括时间,和 歌词)
     // 循环遍历整个歌词数组
     for (let i = 0; i < lines.length; i++) {
         const item = lines[i].split(']')
         const time = item[0].substring(1);
         const words = item[1];
         const obj = {
             time: parseTime(time),
             words
         }
         lrcData.push(obj);
     }
     return lrcData
 }
  • 观察 歌词字符串 [00:01.06]难念的经\n [00:03.95]演唱:周华健我们不难发现,我们首先可以按照\n 进行分割, 得到一个字符串数组

image.png

  • 然后进行遍历,拿到第二项,再次进行分割 按照] 进行分割,得到['[00:01.06', '难念的经'],进而进行针对数组的第一项也就是开始时间 进行substring截取
  • 然后创建一个歌词对象,在里面添加属性和值即可,

js

const obj = {
             time: parseTime(time),
             words
      }
  • 但是又遇到了问题, 我们上面的时间,如果不进行特殊处理的话,是这样的 00:39.32 , 而我们希望它是39.32, 我们可以得出一个转换公式 第一个项 * 60 + 第二项

转换时间的函数

js

const parseTime = (timeStr) => {
     // 进行分割 按照:进行分割
     const parts = timeStr.split(':');
     return Number(parts[0]) * 60 + Number(parts[1])
 }
  • 最后将处理好的对象 添加到准备好的数组里面即可, 并返回这个数组

4. 绘制页面

创建 元素片段的作用 主要为了优化代码, 提高效率,其实对于这种少数循环插入, 可以不采用.

js

const doms = {
     audio: document.querySelector('audio'),
     box: document.querySelector('.box'),
     ul: document.querySelector('.box ul')
 }

js

const drawPage = () => {
     // 创建元素片段
     const frag = document.createDocumentFragment();
     for (let i = 0; i < resulr.length; i++) {
         // 创建li标签
         const li = document.createElement('li');
         // 设置li标签的文本内容
         li.textContent = resulr[i].words;
         frag.appendChild(li);
     }
     doms.ul.appendChild(frag);
 }
 drawPage()

5. 设置ul元素偏移

这个我们可以看一下图,来更好的方便去理解

这个是最大偏移量, 用来后续做边界判断的

image.png

每次更新时间线之后, ul元素的偏移的量:

image.png


// 容器高度
 var boxclientHeight = doms.box.clientHeight; // 420
 // 每个 li 的高度
 var liHeight = doms.ul.children[0].clientHeight;// 30
 // 最大偏移量
 //  最大偏移量 = ul的高度 - 容器高度
 //  最大偏移量 = 2160 - 420 = 1740
 var maxOffset = doms.ul.clientHeight - boxclientHeight;
 
 console.log("最大偏移量", maxOffset);
 
 // 高亮歌词 + 设置ul偏移量
 const setOffset = () => {
     let index = findIndex();
 
     // 
     console.log(index);
     //  30 * 1 + 15 = 45 - 210 = -165
     let offset = liHeight * index + liHeight / 2 - 210;
     console.log(offset);
     if (offset < 0) {
         offset = 0
     }
     // 如果超出最大偏移量 设置偏移量为最大偏移量
     if (offset > maxOffset) {
         offset = maxOffset
     }
 
     doms.ul.style.transform = `translateY(-${offset}px)`;
     console.log(doms.ul.style.transform);
     // 去掉之前的active样式
     let li = doms.ul.querySelector('.active');
     if (li) {
         li.classList.remove('active');
     }
     // 添加active样式
     li = doms.ul.children[index];
     if (li) {
         li.classList.add('active');
     }
 }

6. 添加事件监听, 不断执行偏移函数

js

doms.audio.addEventListener('timeupdate', setOffset);

7. 完整js代码

data.js

js

var lrc = `[00:01.06]难念的经
[00:03.95]演唱:周华健
[00:06.78]
[00:30.96]笑你我枉花光心计
[00:34.15]爱竞逐镜花那美丽
[00:36.75]怕幸运会转眼远逝
[00:39.32]为贪嗔喜恶怒着迷
[00:41.99]责你我太贪功恋势ƒ
[00:44.48]怪大地众生太美丽
[00:47.00]悔旧日太执信约誓
[00:49.66]为悲欢哀怨妒着迷
[00:52.56]啊 舍不得璀灿俗世
[00:57.66]啊 躲不开痴恋的欣慰
[01:02.86]啊 找不到色相代替
[01:08.09]啊 参一生参不透这条难题
[01:13.15]吞风吻雨葬落日未曾彷徨
[01:15.73]欺山赶海践雪径也未绝望
[01:18.23]拈花把酒偏折煞世人情狂
[01:20.90]凭这两眼与百臂或千手不能防
[01:23.76]天阔阔雪漫漫共谁同航
[01:26.09]这沙滚滚水皱皱笑着浪荡
[01:28.68]贪欢一刻偏教那女儿情长埋葬
[01:32.38]
[01:34.09]吞风吻雨葬落日未曾彷徨
[01:36.50]欺山赶海践雪径也未绝望
[01:39.07]拈花把酒偏折煞世人情狂
[01:41.69]凭这两眼与百臂或千手不能防
[01:44.68]天阔阔雪漫漫共谁同航
[01:46.93]这沙滚滚水皱皱笑着浪荡
[01:49.54]贪欢一刻偏教那女儿情长埋葬
[01:53.41]
[02:15.45]笑你我枉花光心计
[02:18.53]爱竞逐镜花那美丽
[02:21.14]怕幸运会转眼远逝
[02:23.76]为贪嗔喜恶怒着迷
[02:26.43]责你我太贪功恋势
[02:28.98]怪大地众生太美丽
[02:31.60]悔旧日太执信约誓
[02:34.26]为悲欢哀怨妒着迷
[02:36.90]啊 舍不得璀灿俗世
[02:42.04]啊 躲不开痴恋的欣慰
[02:47.34]啊 找不到色相代替
[02:52.52]啊 参一生参不透这条难题
[02:57.47]吞风吻雨葬落日未曾彷徨
[03:00.05]欺山赶海践雪径也未绝望
[03:02.64]拈花把酒偏折煞世人情狂
[03:05.27]凭这两眼与百臂或千手不能防
[03:08.22]天阔阔雪漫漫共谁同航
[03:10.49]这沙滚滚水皱皱笑着浪荡
[03:13.06]贪欢一刻偏教那女儿情长埋葬
[03:18.45]吞风吻雨葬落日未曾彷徨
[03:20.90]欺山赶海践雪径也未绝望
[03:23.54]拈花把酒偏折煞世人情狂
[03:26.21]凭这两眼与百臂或千手不能防
[03:29.07]天阔阔雪漫漫共谁同航
[03:31.32]这沙滚滚水皱皱笑着浪荡
[03:33.92]贪欢一刻偏教那女儿情长埋葬
[03:39.32]吞风吻雨葬落日未曾彷徨
[03:41.84]欺山赶海践雪径也未绝望
[03:44.38]拈花把酒偏折煞世人情狂
[03:47.04]凭这两眼与百臂或千手不能防
[03:49.99]天阔阔雪漫漫共谁同航
[03:52.20]这沙滚滚水皱皱笑着浪荡
[03:54.89]贪欢一刻偏教那女儿情长埋葬
[04:00.28]吞风吻雨葬落日未曾彷徨
[04:02.68]欺山赶海践雪径也未绝望
[04:05.25]拈花把酒偏折煞世人情狂
[04:07.90]凭这两眼与百臂或千手不能防
[04:10.85]天阔阔雪漫漫共谁同航
[04:13.08]这沙滚滚水皱皱笑着浪荡
[04:15.75]贪欢一刻偏教那女儿情长埋葬
[04:19.48]`;
export default lrc

main.js

js

import lrc from "./assets/data.js";
/**
 * 将歌词字符 转换为对象的形式
 * obj = {time:开始时间, words: 歌词内容}
 */
const parseLrc = () => {
    // 准备好一个包含歌词对象的数组
    const lrcData = [];
    // 按照"]"字符进行分割
    const lines = lrc.split('\n');
    // console.log(lines);
    // 生成一个数组,用来放置每一句歌词对象(包括时间,和 歌词)
    // 循环遍历整个歌词数组
    for (let i = 0; i < lines.length; i++) {
        const item = lines[i].split(']')
        // console.log(item);
        const time = item[0].substring(1);
        const words = item[1].substring(1);
        const obj = {
            time: parseTime(time),
            words
        }
        lrcData.push(obj);
    }
    return lrcData
}
// 转换规则 
//  00:39.32 ===> 0 + 39 + 0.32 = 39.32
// 01:02.86 ===> 60 + 2 + 0.86 = 62.86
const parseTime = (timeStr) => {
    // 进行分割 按照:进行分割
    const parts = timeStr.split(':');
    return Number(parts[0]) * 60 + Number(parts[1])
}
const resulr = parseLrc()
// console.log(resulr);
// 页面绘制
// 获取dom
const doms = {
    audio: document.querySelector('audio'),
    box: document.querySelector('.box'),
    ul: document.querySelector('.box ul')
}
const drawPage = () => {
    // 创建元素片段
    const frag = document.createDocumentFragment();
    for (let i = 0; i < resulr.length; i++) {
        // 创建li标签
        const li = document.createElement('li');
        // 设置li标签的文本内容
        li.textContent = resulr[i].words;
        frag.appendChild(li);
    }
    doms.ul.appendChild(frag);
}
drawPage()
// 找到当前歌词
const findIndex = () => {
    // 播放器当前时间
    const curTime = doms.audio.currentTime;
    for (let i = 0; i < resulr.length; i++) {
        if (curTime < resulr[i].time) {
            return i - 1;
        }
    }
    // 找遍了都没找到(说明播放到最后一句)
    return lrcData.length - 1;
}
// 容器高度
var boxclientHeight = doms.box.clientHeight;
console.log("boxclientHeight", boxclientHeight);
// 每个 li 的高度
var liHeight = doms.ul.children[0].clientHeight;
console.log(liHeight); // 30
// 最大偏移量
var maxOffset = doms.ul.clientHeight - boxclientHeight;
//  最大偏移量 = ul的高度 - 容器高度
//  最大偏移量 = 2160 - 420 = 1740
console.log("最大偏移量", maxOffset);
// 高亮歌词 + 设置ul偏移量
const setOffset = () => {
    let index = findIndex();
    // 
    console.log(index);
    //  30 * 1 + 15 = 45 - 210 = -165
    let offset = liHeight * index + liHeight / 2 - 210;
    console.log(offset);
    if (offset < 0) {
        offset = 0
    }
    if (offset > maxOffset) {
        offset = maxOffset
    }
    doms.ul.style.transform = `translateY(-${offset}px)`;
    console.log(doms.ul.style.transform);
    // 去掉之前的active样式
    let li = doms.ul.querySelector('.active');
    if (li) {
        li.classList.remove('active');
    }
    // 添加active样式
    li = doms.ul.children[index];
    if (li) {
        li.classList.add('active');
    }
}
doms.audio.addEventListener('timeupdate', setOffset);

到此就结束了, 主要难点在于计算ul的偏移量, 和 最大偏移量, 以及边界情况考虑. 不过,只要画好图,一分析,便会清晰明了许多.


目录
相关文章
|
5天前
|
JavaScript 前端开发
页面滚动触发css3动画js插件
delighters.js是一款页面滚动触发css3动画js插件。该js插件可以在页面向下滚动时,为进入浏览器视口的元素制作各种炫酷的CSS3动画效果。
30 13
|
14天前
纸屑飘落生日蛋糕场景js+css3动画特效
纸屑飘落生日蛋糕CSS3动画特效是一款js+css3制作的全屏纸屑飘落,生日蛋糕点亮庆祝动画特效。
32 3
|
14天前
|
Web App开发 移动开发 HTML5
html5 + Three.js 3D风雪封印在棱镜中的梅花鹿动效源码
html5 + Three.js 3D风雪封印在棱镜中的梅花鹿动效源码。画面中心是悬浮于空的梅花鹿,其四周由白色线段组成了一个6边形将中心的梅花鹿包裹其中。四周漂浮的白雪随着多边形的转动而同步旋转。建议使用支持HTML5与css3效果较好的火狐(Firefox)或谷歌(Chrome)等浏览器预览本源码。
47 2
|
24天前
|
前端开发 JavaScript UED
CSS滚动效果和视差滚动的原理、应用及其对用户体验的影响。从平滑滚动到元素跟随,再到滚动触发动画
本文探讨了CSS滚动效果和视差滚动的原理、应用及其对用户体验的影响。从平滑滚动到元素跟随,再到滚动触发动画,这些效果增强了页面的吸引力和互动性。视差滚动通过不同层次元素的差异化移动,增加了页面的深度感和沉浸感。文章还讨论了实现方法、性能优化及案例分析,旨在为设计师和开发者提供实用指导。
51 7
|
24天前
|
前端开发 测试技术 定位技术
如何利用HTML和CSS构建企业级网站的全过程。从项目概述到页面结构设计,再到HTML结构搭建与CSS样式设计,最后实现具体页面并进行优化提升,全面覆盖了网站开发的关键步骤
本文深入介绍了如何利用HTML和CSS构建企业级网站的全过程。从项目概述到页面结构设计,再到HTML结构搭建与CSS样式设计,最后实现具体页面并进行优化提升,全面覆盖了网站开发的关键步骤。通过实例展示了主页、关于我们、产品展示、新闻动态及联系我们等页面的设计与实现,强调了合理布局、美观设计及用户体验的重要性。旨在为企业打造一个既专业又具吸引力的线上平台。
49 7
|
24天前
|
前端开发 JavaScript 搜索推荐
HTML与CSS在Web组件化中的核心作用及前端技术趋势
本文探讨了HTML与CSS在Web组件化中的核心作用及前端技术趋势。从结构定义、语义化到样式封装与布局控制,两者不仅提升了代码复用率和可维护性,还通过响应式设计、动态样式等技术增强了用户体验。面对兼容性、代码复杂度等挑战,文章提出了相应的解决策略,强调了持续创新的重要性,旨在构建高效、灵活的Web应用。
32 6
|
24天前
|
存储 移动开发 前端开发
高效的 HTML 与 CSS 编写技巧,涵盖语义化标签、文档结构优化、CSS 预处理、模块化设计、选择器优化、CSS 变量、媒体查询等内容
本文深入探讨了高效的 HTML 与 CSS 编写技巧,涵盖语义化标签、文档结构优化、CSS 预处理、模块化设计、选择器优化、CSS 变量、媒体查询等内容,旨在提升开发效率、网站性能和用户体验。
36 5
|
1月前
|
移动开发 JavaScript 前端开发
html table+css实现可编辑表格的示例代码
html table+css实现可编辑表格的示例代码
58 12
|
1月前
|
前端开发 JavaScript
用HTML CSS JS打造企业级官网 —— 源码直接可用
必看!用HTML+CSS+JS打造企业级官网-源码直接可用,文章代码仅用于学习,禁止用于商业
126 1
下一篇
DataWorks