LRC 滚动器 + Vue.js

vue

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" title="vuejs">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 的全部内容, 来源链接: utcz.com/z/377298.html

回到顶部