美文网首页
Greasemonkey历险记之荔枝FM

Greasemonkey历险记之荔枝FM

作者: ifcode | 来源:发表于2015-05-01 06:08 被阅读3857次

    从13年开始我迷上了podcast,尤其是在夜晚一片寂静的时候,边听podcast边写代码或是看书学习真是一种难以言喻的幸福。最早的时候是去新浪播客收听的,可那蛋疼的速度和糟糕的设计,让我很快就转向荔枝FM了。荔枝的速度在法国挺快,中文播客也比较全。我平时收听的主要渠道是web端,荔枝的设计虽然简单,但也到清爽干净,比起它日渐复杂的移动端更得我心。但是荔枝FM主打的毕竟是移动端,web端一直都非常简陋。最让我不爽的是web端至今都没能实现订阅自选电台这个最简单,也最核心的功能。

    既然官方等不到了,那就自己来弄一个吧。上一篇介绍过Greasemonkey和类似查件的作用了,他们可以在访问指定页面的时候执行预先写好的脚本,以此实现自定义页面。在这里,我需要的是一个简单的功能:在web端的左边菜单栏中增加一个按钮,用来展开自选电台列表。

    有了功能需要,让我们考虑一下实现思路吧。Dev tools和Inspect element是我们手中的利器, 很快我就发现一个有趣的事实:该网站是基于angular实现的!它带有SPA的特点,当我们点击左边菜单栏按钮的时候,只有中间的内容区会刷新。左边的菜单栏和右边的播放器是基本不刷新的。这对我们要实现的功能来说真是太棒了!自选电台的脚本只在页面第一次加载的时候注入DOM,每次更换电台的时候只有中间的电台信息部分刷新,该脚本不会再次执行,开销小多了!

    好了,那就开始动手吧。首先自然是怎么确定自选电台了;观察几次就会发现,每个电台都由唯一的数字代表,只要将这个数字加到网址后面就可以跳转到该电台了。比如Gadio的地址是http://www.lizhi.fm/#/29345,友的聊的地址是http://www.lizhi.fm/#/14393/。这就简单多了,只要保存每个电台的唯一地址就可以了:

    var myRadioList = {  
      gadio: '[http://www.lizhi.fm/#/29345'](http://www.lizhi.fm/#/29345'),  
      友的聊: '[http://www.lizhi.fm/#/14393'](http://www.lizhi.fm/#/14393'), 
       二次元: '[http://www.lizhi.fm/#/22557'](http://www.lizhi.fm/#/22557'),  
      糖蒜广播: '[http://www.lizhi.fm/#/13461'](http://www.lizhi.fm/#/13461') 
    };
    

    好吧,这种定义方式实在太不灵活了。不过作为第一版的脚本,从简单的,可实现的方案开始并无不可,日后再迭代就是了。电台列表有了,下面就该在左边加个按钮了。用inspect element找到左边导航栏的位置:

    var ul = document.querySelector('.wrap > .leftNav > .content > ul');
    

    然后就该生成一个按钮加到ul上了:

    var radioButton = document.createElement('button'); 
    radioButton.class = 'my-radio'; 
    radioButton.style.cssText = "margin:3px 3px 3px 15px"; 
    var b = document.createElement('b'); 
    b.innerHTML = '自选电台'; radioButton.appendChild(b);
    ul.appendChild(radioButton);
    

    我知道这种inline css很丑,但作为一个简单小脚本,你们就原谅我的放荡不羁吧……

    好了,按钮有了。下面该生成列表,并在按钮上绑定点击事件来展开列表了。绑定事件很简单:

    radioButton.onclick = toggleRadioList;
    

    toggleRadioList就是响应点击事件的方法,由该方法来展开和收起列表。得先有列表才行啊,鉴于列表本质上就是ul元素,我们先创建一个ul节点:

    var radioList = document.createElement('ul'); 
    radioList.className = 'radio-list';
    radioList.style.cssText = "list-style-type:none;margin:3px;padding:0px 15px;visibility:hidden";
    

    为了方便toggle函数改变它的状态,特地加上了一个class来方便查找。好了,下面就该按定义的电台对象一次生成内部的li节点了:

    var frag = document.createDocumentFragment();
    Object.keys(itemList).forEach(function(key) {  
      var li = document.createElement('li');  
      var a = document.createElement('a');  
      a.href = itemList[key];  
      a.innerHTML = key;  
      a.style.cssText = "text-decoration:underline";
      li.appendChild(a);  frag.appendChild(li);
     });
    radioList.appendChild(frag);
    target.appendChild(radioList);
    

    注意这里我用了一个fragment来添加li节点。其实这里并不是必须的,但从练习的角度来看,这么写是符合性能实践的。我们都知道DOM操作开销是非常高的,每次DOM变动都会使浏览器重新计生成DOM树,然后重新渲染DOM。从性能的角度来看,DOM操作的次数越少越好。于是先用一个fragment来添加li节点,最后再将该fragment注入DOM中。这样我们只用一次DOM操作就注入了所有的li节点,比每生成一个li就注入要高效的多,随着列表的增长,性能方面的差距会越来越大。但我们这里的radioList直到最后才加入DOM,所以问题要小一些,但无论如何,记得这一点对以后会有帮助的。

    最后只剩下响应点击事件的toggleRadioList函数了。这个函数的作用就是找到class为radio-list的列表,如果该列表已经可见了就隐藏它,否则就显示它:

    function toggleRadioList() {  
      var list = document.querySelector('.radio-list');  
      if (list.style.visibility === 'hidden')  
        list.style.visibility = 'visible';  
      else  
        list.style.visibility = 'hidden';
     }
    

    好了,零件都齐活了,脚本应该可以用了吧?等等,我最近刚想起来一个问题:全局命名空间的冲突。大家都直到上个leanpub的脚本,所有函数是直接暴露在global下的。这会有什么问题呢?可能会出现变量冲突!比如我这有一个toggleRadioList变量,如果荔枝网站本身有一个同名变量,冲突就出现了,荔枝网站的某部分功能也就会出现问题了。为了确保冲突不出现,我们需要将我们自己定义的脚本限制在一个命名空间内,不要直接暴露出来。最适合完成这个任务的自然就是IIF了,详情我在去年的一篇文卓中已有讲解,就不再详述了。简单来说,就是将我们所有的脚本都包含在这样一个函数中:

    (function() { 
      //自定义脚本内容
    })()
    

    基于JS函数作用域定义,里面包含的所有变量和函数定义都只在该函数内部可见,实现了和global的隔离。

    DeepinScreenshot20150501000457.png

    到这里,脚本就差不多了。还有很多可以改进的地方,比如可以增加一个输入框让用户自己输入电台地址,然后保存到localStorage中;改进一下CSS让电台列表的显示更清晰。这些东西以后再慢慢来吧,已经快要午夜了,让我们选一个podcast,从陌生人的声音中取得一丝慰藉吧。

    P.S: 完整脚本地址

    相关文章

      网友评论

          本文标题:Greasemonkey历险记之荔枝FM

          本文链接:https://www.haomeiwen.com/subject/tzlbfttx.html