LRC 滚动展示VueJS

cnblogs @ Orcim   


最 近一直在学习尤大大的这个前端框架。Vue 无疑是一款极易上手的前端框架,因为官方的文档就是中文的,十分“本土化”,中文文档会比较快最先更新。除此之外,官方网站上的 Vue 教学非常适合像我这样的新手,教学文档很详尽,在这里给 Vue 的维护团队点个赞。

自己这几天边看文档,边动手跟着练习,然后今天花了一些时间,模仿手机音乐播放器实现了一个状态栏歌词滚动器,又在此之上添加了一个切换歌词语言的功能:点击左边的音符图标即可切换到歌词翻译,点击这里,在我的 CodePen 中查看这个 demo。

demo 大概就长这样,点击左侧 icon 即可切换翻译。

思路 & 逻辑

1. 对LRC文件字符串格式,进行解析。

 
***.lrc
COPY

1234567
[ti:]
[ar:]
[al:]
[by:]
[00:00.05]
[00:01.09]Never knowing where our own futures lie,
[00:04.39]And easily we start simplifying all our planning,
COPY

 

这里要注意的是lrc文件每行,开头的“时间戳”的时间格式,普遍的格式有三种:①[min:sec.ms]②[min:sec]③其他,这里只对前两种情况做了兼容,因为这两种现在见得最多;还有要注意的就是ms(毫秒)的位数可能是一位、两位、三位。

 
lrcHandler.js

12345678910111213141516171819202122232425262728293031323334
/**
 * @method lrcHandler 返回指定 type 的歌词 json 对象
 * @param { String } type lrc(原) 或 tlrc(译)
 * @param { Object } lns_obj info&main
 */
function lrcHandler (type) {
    var refer = {
        "lrc": "lyric",
        "tlrc": "translateLyric"
    };
    var lns_obj = {
        "lrc-info": [],
        "lrc-main": []
    };
    var lns = lrc[refer[type]].split("\n"); 
    // lrc 文件字符串的每行所组成的 JSON
    for(var i=0, len=lns.length; i<len; i++){
        var m = lns[i];
        var info = m.replace(/\[(.*)\](.*)/, "$1");
        var lys = m.replace(/\[(.*)\](.*)/, "$2");
        var mth = info.match(/([0-9]+)\:([0-9]+)\.([0-9]+)/);
        var pad_0 = function(num_str){return (num_str + (new Array(4-num_str.length)).join("0"))};
        if(mth){
            var milis = mth[1]*1*60*1000 + mth[2]*1*1000 + pad_0(mth[3])*1;
            var ln = {"time": milis, "lyric": lys};
            lns_obj["lrc-main"].push(ln)
        }else{
            var inf = {};
            inf[info.replace(/(.*)\:(.*)/, "$1")] = info.replace(/(.*)\:(.*)/, "$2");
            lns_obj["lrc-info"].push(inf);
        };
    };
    return lns_obj;
};
 

2. 对 ***.lrc 文件利用如上方法进行解析后,返回一个对象,对象包括歌词的附属信息(lrc-info):by、ti、ar等头信息,歌词的主要部分(lrc-main)数组类型,其每项包含时间戳转换的毫秒总数,以及对应时间戳点(time)的歌词字符串(lyric)。之后要做的就是批量注册定时器,在 Vue 生命周期函数的 created 中注册这些定时器:

 
setTimers.js

12345678910111213141516171819
"created": function(){
    for(var j=0, len=$lrc["lrc-main"].length; j<len; j++){
        var ti = that.displayLrc["lrc-main"][j]["time"];
        var c = 0;
        that.timers[j] = setTimeout(function(){
            that.scroll_fn();
            clearTimeout(that.transition_timer);
            that.transition_timer = setTimeout(function(){
                $lrc["lrc-main"][c-1] && that.setLnLrc(c-1, function(){
                    that.recover_fn();
                    console.log(that.displayLrc["lrc-main"][c-1]["time"], that.displayLrc["lrc-main"][c-1]["lyric"]);
                });
                clearTimeout(that.transition_timer);
            }, 200)
            nowLine = c++;
            clearTimeout(that.timers[j]);
        }, ti);
    }
}
 

部分代码逻辑如上,完整代码可见文章开头处的 CodePen 链接。

3. 注册和创建 Vue 组件,这个部分就不多说了,直接贴代码,看看吧。

 
main.html

1234567891011121314151617181920
<html>
    <head>
        <meta charset="utf-8">
        <link rel="stylesheet" href="main.css">
        <script src="../../vuejs_2.6.10.js"></script>
    </head>
    <body>
        <div id="main">
            <lrc-scroller
            :offset-class="sim"
            :lrc_top="tlrc"
            :lrc_bt="blrc"
            :swl="switchLang"
            ></lrc-scroller>
        </div>
    </body>
    <script src="498286345" type="text/javascript"></script>
    <!-- 引入 lrc 文件,为基于网易云音乐的歌词文件 @param { JSON } lrc -->
    <script src="main.js" type="text/javascript"></script>
</html>
 

核心逻辑代码 main.js 如下:

 
main.js

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
Vue.component("lrc-scroller", {
    "template": 
        "<div class='container'>\
            <div class='box'>\
                <div class='box-outer' v-bind:class='offsetClass'>\
                    <div class='box1'><span>{{ lrc_top }}</span></div>\
                    <div class='box2'><span>{{ lrc_bt }}</span></div>\
                </div>\
                <div class='switchBtn' title='switch language' v-on:click='swl'></div>\
            </div>\
        </div>",
    "props":["offsetClass", "lrc_top", "lrc_bt", "swl"]
});
var that = {};
var $lrc = lrcHandler("lrc");
var $tlrc = lrcHandler("tlrc");
var nowLine = 0;
var main = new Vue({
    "el": "#main",
    "data": {
        "sim": {
            "offset": false,
            "transition": true
        },
        "tlrc": "",
        "blrc": "",
        "timers": [],
        "transition_timer": null,
        "displayLrc": window["$lrc"]
    },
    "methods": {
        setLnLrc: function(line, fn){
            that["tlrc"] = that.displayLrc["lrc-main"][line]["lyric"];
            $lrc["lrc-main"][line+1] && (that["blrc"] = that.displayLrc["lrc-main"][line+1]["lyric"]);
            fn && fn();
        },
        recover_fn: function(){
            that.sim["transition-none"] = true;
            that.sim["transition"] = false;
            that.sim["offset"] = false;
        },
        scroll_fn: function(){
            that.sim["offset"] = true;
            that.sim["transition"] = true;
            that.sim["transition-none"] = false;
        },
        switchLang: function(){
            that.displayLrc = (that.displayLrc === window["$tlrc"] ? window["$lrc"] : window["$tlrc"]);
        }
    },
    "watch": {
        "displayLrc": function(){
            that.setLnLrc(nowLine);
        }
    },
    "beforeCreate": function(){
        that = this;
    },
    "created": function(){
        for(var j=0, len=$lrc["lrc-main"].length; j<len; j++){
            var ti = that.displayLrc["lrc-main"][j]["time"];
            var c = 0;
            that.timers[j] = setTimeout(function(){
                that.scroll_fn();
                clearTimeout(that.transition_timer);
                that.transition_timer = setTimeout(function(){
                    $lrc["lrc-main"][c-1] && that.setLnLrc(c-1, function(){
                        that.recover_fn();
                        console.log(that.displayLrc["lrc-main"][c-1]["time"], that.displayLrc["lrc-main"][c-1]["lyric"]);
                    });
                    clearTimeout(that.transition_timer);
                }, 200)
                nowLine = c++;
                clearTimeout(that.timers[j]);
            }, ti);
        }
    }
});
/**
 * @method lrcHandler 返回指定 type 的歌词 json 对象
 * @param { String } type lrc(原) 或 tlrc(译)
 * @param { Object } lns_obj info&main
 */
function lrcHandler (type) {
    var refer = {
        "lrc": "lyric",
        "tlrc": "translateLyric"
    };
    var lns_obj = {
        "lrc-info": [],
        "lrc-main": []
    };
    var lns = lrc[refer[type]].split("\n"); 
    // lrc 文件字符串的每行所组成的 JSON
    for(var i=0, len=lns.length; i<len; i++){
        var m = lns[i];
        var info = m.replace(/\[(.*)\](.*)/, "$1");
        var lys = m.replace(/\[(.*)\](.*)/, "$2");
        var mth = info.match(/([0-9]+)\:([0-9]+)\.([0-9]+)/);
        var pad_0 = function(num_str){return (num_str + (new Array(4-num_str.length)).join("0"))};
        if(mth){
            var milis = mth[1]*1*60*1000 + mth[2]*1*1000 + pad_0(mth[3])*1;
            var ln = {"time": milis, "lyric": lys};
            lns_obj["lrc-main"].push(ln)
        }else{
            var inf = {};
            inf[info.replace(/(.*)\:(.*)/, "$1")] = info.replace(/(.*)\:(.*)/, "$2");
            lns_obj["lrc-info"].push(inf);
        };
    };
    return lns_obj;
};
console.log(lrcHandler("tlrc"));
 

 
main.css

1234567891011121314
body{padding: 0; margin: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; letter-spacing: .5px; font-size: 18px;}
.container{position: relative; width: 100%; height: 36px; background-color: #eee;}
.box{height: 32px; width: calc(100% - 36px); position: absolute; top: 0; left: 0; padding: 2px 0 2px 36px; overflow: hidden;}
.box::before{display: block; content: ""; width: 36px; height: 36px; position: absolute; top: 0; left: 0; background: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzNiIgaGVpZ2h0PSIzNiIgdmlld0JveD0iMCAwIDEyOTYgMTI5NiI+PHBhdGggZD0iTTExNTIgNjMuOTNMNDI0LjIwNyAyMDEuMjM4djYxMi44NjVhMjE0LjM0NCAyMTQuMzQ0IDAgMCAwLTY1Ljk3Mi0xMC43MjNBMjE0LjM0NCAyMTQuMzQ0IDAgMCAwIDE0NCAxMDE3Ljg0N2EyMTQuMzQ0IDIxNC4zNDQgMCAwIDAgMjE0LjIzNSAyMTQuMjM1IDIxNC4zNDQgMjE0LjM0NCAwIDAgMCAyMTQuNDY4LTIxNC4yMzV2LTYyOS42NWw0MzAuNTY4LTgxLjM1OHY0MjIuMTc2YTIxNC4zNDQgMjE0LjM0NCAwIDAgMC02NS43NC0xMC43MjRBMjE0LjM0NCAyMTQuMzQ0IDAgMCAwIDcyMy4yOTggOTMyLjc2YTIxNC4zNDQgMjE0LjM0NCAwIDAgMCAyMTQuMjM1IDIxNC4yMzVBMjE0LjM0NCAyMTQuMzQ0IDAgMCAwIDExNTIgOTMyLjc2VjI3OC42MzJ6TTkzNy43NjUgODY2Ljc4OGE2NS43NzYgNjUuNzc2IDAgMCAxIDY1LjczOSA2NS43MzkgNjUuNzc2IDY1Ljc3NiAwIDAgMS02NS43NCA2NS45NzIgNjUuNzc2IDY1Ljc3NiAwIDAgMS02NS45NzEtNjUuOTcyIDY1Ljc3NiA2NS43NzYgMCAwIDEgNjUuOTcyLTY1LjczOXptLTU3OS4yOTcgODUuMDg4YTY1Ljc3NiA2NS43NzYgMCAwIDEgNjUuNzQgNjUuNzM5IDY1Ljc3NiA2NS43NzYgMCAwIDEtNjUuNzQgNjUuOTcyIDY1Ljc3NiA2NS43NzYgMCAwIDEtNjUuOTcyLTY1Ljk3MiA2NS43NzYgNjUuNzc2IDAgMCAxIDY1Ljk3Mi02NS43NHoiLz48L3N2Zz4=) no-repeat center; background-size: 24px;}
.switchBtn{position: absolute; top: 0; left: 0; width: 36px; height: 36px; cursor: pointer;}
.switchBtn::before{display: block; content: ""; position: absolute; left: 0; top: 0; width: 100%; height: 100%; background-color: grey; border-radius: 50%; opacity: 0; transform: scale(0); transition: all 200ms linear}
.switchBtn:hover::before{transform: scale(1.5); opacity: .25;}
 
.box-outer{height: 64px; display: felx; flex-direction: column; transition: all 200ms;}
.box-outer.offset{transform: translateY(-32px);}
.box-outer.transition{transition: transform 200ms ease-in-out;}
.box-outer.transition-none{transition-duration: 0ms;}
.box1{height: 32px; display: flex; justify-content: flex-start; align-items: center; background-color: none; white-space: nowrap;}
.box2{height: 32px; display: flex; justify-content: flex-start; align-items: center; background-color: none; white-space: nowrap;}
 

结束语

Vue确实方便学习和使用,如果接触过微信小程序的话,基本上看看官网上的文档几乎就可以直接拿来用了,框架设计十分友好和人性化,除此外 Vue 式的组件化和模块化让代码变得简洁和明了易于维护,数据绑定等降低 DOM 渲染成本也恰到好处,是个很不错的前端框架。

其他

VueJS 版本:2.6.10,2019/07

更新

今天又花了一些时间完善了这个歌词滚动器,点击这里来查看demo,在我的CodePen中查看源代码。主要是修正了一些错误,适配了所有格式的LRC文件,添加了窄屏下歌词可以左右以较为合理的速度滚动的功能,优化了细节。2019/07/17

LRC 滚动器 + Vue.js的更多相关文章

  1. vue安装调试器Vue.js devtools

    一. 打开https://github.com/vuejs/vue-devtools,进入gitlab.往下翻找到: 找到installation,选择以chrome的拓展方式安装. 二. 这边选择添 ...

  2. 基于 Vue.js 的移动端组件库mint-ui实现无限滚动加载更多

    通过多次爬坑,发现了这些监听滚动来加载更多的组件的共同点, 因为这些加载更多的方法是绑定在需要加载更多的内容的元素上的, 所以是进入页面则直接触发一次,当监听到滚动事件之后,继续加载更多, 所以对于无 ...

  3. Vue.js之Vue计算属性、侦听器、样式绑定

    前言 上一篇介绍了Vue的基本概念,这一篇介绍一下Vue的基本使用. 一.搭建一个Vue程序 1.1 搭建Vue环境 搭建Vue的开发环境总共有三种方法: 引入CDN <script src=& ...

  4. Vue.js 源码分析(七) 基础篇 侦听器 watch属性详解

    先来看看官网的介绍: 官网介绍的很好理解了,也就是监听一个数据的变化,当该数据变化时执行我们的watch方法,watch选项是一个对象,键为需要观察的数据名,值为一个表达式(函数),还可以是一个对象, ...

  5. Vue.js路由管理器 Vue Router

    起步 HTML <script src="https://unpkg.com/vue/dist/vue.js"></script> <script s ...

  6. (vue.js)axios interceptors 拦截器中添加headers 属性

    (vue.js)axios interceptors 拦截器中添加headers 属性:http://www.codes51.com/itwd/4282111.html 问题: (vue.js)axi ...

  7. Vue.js 第2章 钩子函数&自定义指令&过滤器&计算属性&侦听器

    目标 钩子函数 自定义指令 自定义过滤器 计算属性 监听属性 局部自定义指令 为什么需要自定义指令 为了复用,为了代码的灵活 指令的分类:全局指令,局部指令 在vm外面创建的指令 通过Vue.dire ...

  8. 解决游览器安装Vue.js devtools插件无效的问题

    一: 打开自己写的一个vue.js网页,发现这个图标并没有亮起来,还是灰色 解决方案:  1.我们先看看Vue.js devtools是否生效,打开Bilibili(https://www.bilib ...

  9. 基于animate.css动画库的全屏滚动小插件,适用于vue.js(移动端、pc)项目

    功能简介 基于animate.css动画库的全屏滚动,适用于vue.js(移动端.pc)项目. 安装 npm install vue-animate-fullpage --save 使用 main.j ...

随机推荐

  1. 精华技巧,学会这几招可以假装是Python高手

    Python里面的技巧真的是太多了,菜鸟如何假装自己的是高手呢,下面教大家几招快速变成老司机的招数,尤其是在实战的项目中,用上这几招一定让你周围的同事刮目相看,哇这个代码有两把刷子. 很多人学习pyt ...

  2. 攻防世界——web新手练习区解题记录<1>(1-4题)

    web新手练习区一至四题 第一题view_source: 题目说右键不管用了,我们先获取在线场景来看一看,我们看到这样一个网页,并且右键确实点了没什么反应,而用到右键一般就是查看网页源码 用快捷键(F ...

  3. Lua索引、伪索引、引用

    索引:堆栈的索引 伪索引:一个类似于索引,但是有着特殊存储的索引,使用方式和索引一样,看上去像在操作堆栈 引用:LUA_REGISTRYINDEX伪索引下的表的整数键

  4. 【小白学PyTorch】6 模型的构建访问遍历存储(附代码)

    文章转载自微信公众号:机器学习炼丹术.欢迎大家关注,这是我的学习分享公众号,100+原创干货. 文章目录: 目录 1 模型构建函数 1.1 add_module 1.2 ModuleList 1.3 ...

  5. java初探(1)之登录终探

    上一章讲了表单验证,数据验证和加密.这一章,将研究服务器和数据库的交互过程. 后端服务器有两种主流的形式,SQL数据库和NOSQL数据库.其中MYSQL属于SQL数据库,REDIS属于非SQL数据库. ...

  6. Spring security OAuth2.0认证授权学习第二天(基础概念-授权的数据模型)

    如何进行授权即如何对用户访问资源进行控制,首先需要学习授权相关的数据模型. 授权可简单理解为Who对What(which)进行How操作,包括如下: Who,即主体(Subject),主体一般是指用户 ...

  7. Oracle序列Sequence用法

    序列 序列(Sequence)是用来生成连续的整数数据的对象.序列常常用来作为主键中增长列,序列中的可以升序生成,也可以降序生成.创建序列的语法是:语法结构:创建序列 CREATE SEQUENCE ...

  8. (课堂笔记)第三章:F5 LTM 负载均衡理论

    BIG-IP LTM负载均衡理论 ------F5 BIG-IP LTM负载均衡策略---------- 1.1 LTM VS工作模式F5 BIG-IP LTM的内部对于数据包的处理方式,即是VS的工 ...

  9. shell字体颜色应用

    输出特效格式控制: \033[0m  关闭所有属性   \033[1m   设置高亮度   \03[4m   下划线   \033[5m   闪烁   \033[7m   反显   \033[8m   ...

  10. 吴恩达《深度学习》-第一门课 (Neural Networks and Deep Learning)-第三周:浅层神经网络(Shallow neural networks) -课程笔记

    第三周:浅层神经网络(Shallow neural networks) 3.1 神经网络概述(Neural Network Overview) 使用符号$ ^{[