萝三画室

仿网易云音乐PC-总结Part1(HTML5)

最近两个半月,利用业余时间做了网易云音乐PC仿站,是学习前端以来第一个完整的实战项目。前端使用VUE(包括vue-resource和vue-router),后端使用python实时获取数据(感谢来自猩猩的男票提供的大力支持=3=)。
全部项目请戳->>>>> https://github.com/love3forever/gogo3

这一系列总结主要想整理的是,在项目过程中使用的新方法、思路、以及遇到的坑,包括HTML5、CSS、ES6、VUE四个部分。本篇是Part1-HTML5,内容包括选择符API、类扩展classList、自定义数据属性三个内容。

选择符API

H5之前,原生DOM选择符API仅有两个:

  • getElementById()
  • getElementsByTagName()
     尽管DOM作为API已经非常完善了,但是为了实现更多的功能,DOM仍然进行了扩展,其中一个重要的扩展就是对选择器API的扩展。扩展内容包括:getElementsByClassName()、querySelector()、querySelectorAll()、matchesSelector()。

示例:

1
2
3
4
5
6
7
8
9
10
<body>
<img id="outside">
<div id="mydiv">
<img id="inside">
<div class="lonely"></div>
<div class="selected">
<div class="inner"></div>
</div>
</div>
</body>

getElementsByClassName()

getElementsByClassName()方法接收一个参数,即一个包含一个或多个类名的字符串,返回带有指定类的所有元素的类数组对象HTMLCollection。传入多个类名时,类名的先后顺序不重要。与getElementsByTagName()类似,该方法既可以用于HTML文档对象,也可以用于element元素对象。

querySelector()

querySelector()方法接收一个CSS选择符,返回与该模式匹配的第一个元素,如果没找到则返回null

1
2
3
var id = document.querySelector("#mydiv");
var class = document.querySelector(".selected");
var fistChild = document.querySelector(".selected div");

querySelectorAll()

querySelectorAll()方法接收一个CSS选择符,但返回是所有匹配元素,以NodeList实例形式返回,如果没找到则返回空的NodeList

1
var children = document.querySelectorAll(".selected div");

matchesSelector()

matchesSelector()方法接收一个CSS选择符,返回是是否存在匹配元素(true/false)。

1
2
var mydiv = document.matchesSelector("#mydiv");//true
var myId = document.matchesSelector("#myId");//false

需要注意的问题:

  1. 当通过Element类型调用querySelectorAll()方法时,会在该Element的后代元素并包括Element自身范围内查找元素。
    比如对于上文结构:
1
2
document.querySelectorAll("#mydiv div div");//返回div.inner
document.querySelector("#mydiv").querySelectorAll("div div");//返回div.lonely、div.selected、div.inner
  1. 静态方法&返回值
    get系列返回的是动态HTMLCollection,query系列返回静态NodeList。
    我们知道getElementById()、getElementsByTagName()、getElementsByClassName()会随着实际DOM元素的变化而变化,可以理解为这些方法返回的元素或NodeList始终与当前页面保持一致。而接收CSS选择符的querySelector()、querySelectorAll()、matchesSelector()方法并不会这样,他们保存的是赋值那一时刻的页面状态,即快照。

  2. 性能
    就单次运行而言,getELement(s)By…系列更快(速度大约是100倍的差距)。因此建议,如果目标元素不是需要多次getELement(s)By…,就使用get系列。如果目标元素获取很麻烦,则使用query系列,这样更方便。

  3. 兼容
    query系列IE9以下不支持。

类扩展classList

H5新增了一种操作类名的方式:classList。
在此之前,我们只能通过className属性添加、删除和替换类名。由于className中保存的是一个字符串,所以每次修改都需要重新设置整个字符串的值。
比如对于以下元素:

1
<div class="a b c"></div>

我们试图删除b类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var classes = div.className.split(/\s+/);
var pos = -1,
i = 0,
len = classes.length;
for (i,len;i<len;i++){
if (classes[i] == "b"){
pos = i;
break;
}
}
classes.splice(i,1);
div.className = classes.join(" ");

新增的classList属性可以让操作更简单,它是集合类型DOMTokenList的实例。这个集合包括以下属性和方法:

  • 属性:length 表示包含多少元素
  • 方法:
    • item():遍历取得每个元素
    • add(value):添加字符串到列表,若已存在则不添加。
    • remove(value):从列表中删除字符串,若不存在则不删除。
    • contain(value):列表中是否包含字符串,返回true/false。
    • toggle(value):若列表中已存在字符串,则删除;不存在,则添加
      这样一来,前面的代码只用一行即可实现:
1
div.classList.remove("b");

需要注意的是,这些方法一次仅能操作一个元素。

自定义数据属性

H5规定,可以为元素添加非标准的属性(需要加data-前缀),提供用户自定义的信息,并可以通过元素的dataset属性访问。
比如为元素添加自定义属性myName:

1
<div id="myId" data-myName="Suri"></div>

访问myName属性:

1
var myName = document.getElementById("myId").dataset.myName;//Suri

在项目中的应用

classList

背景:对于首页的海报轮播图,当页面滚动导致海报消失在视口时,暂停轮播;出现在视口时,继续轮播。
实现方法:

  1. 将暂停轮播动画写入一个单独的css类
1
2
3
4
.paused{
animation-play-state:paused;
-webkit-animation-play-state:paused;
}
  1. 监听scroll事件,实时获取视口与页面顶部的距离(滚动条滚动的距离),根据该距离与轮播图位置判断轮播图是否消失在视口。是则暂停动画,否则继续动画。
1
2
3
4
5
6
7
8
9
10
11
12
13
window.addEventListener('scroll', ()=>{//箭头函数修正this指针
this.scrollLength = document.documentElement.scrollTop || document.body.scrollTop;
//控制首页slide组件动画开始/暂停。鼠标滚动使得slide离开页面可视范围时,动画暂停
let slide = document.getElementById('flag');
if (slide){
if (this.scrollLength > 440){
slide.classList.add('paused');
} else {
slide.classList.remove('paused');
};
}
});

源码请戳 https://github.com/love3forever/gogo3/blob/master/src/App.vue (line 29-40)

自定义数据属性

背景:一个歌曲列表中,每个子元素(每首歌曲)中都有一个播放按钮,点击按钮则按钮改变样式。同一时刻列表中最多只能有一个按钮被按下。
实现方法:

  1. 为歌曲子元素加入自定义数据属性tag,唯一识别一个子元素。
1
<span :class="[track.click?'tracks-cli':'tracks-ply']" :data-tag="index"></span>
  1. 对歌曲按钮的点击事件添加事件代理,即监听父元素歌曲列表的onclick事件。
  2. 利用delement.dataset.tag获取触发onclick事件的歌曲子元素。
1
2
3
4
5
6
7
8
9
10
11
//歌单播放按钮点击事件代理
plySong:function(ev){
var ev = ev||window.event;
var target = ev.target||ev.srcElement;
if (target.nodeName.toLowerCase() == "span"){
var index = parseInt(target.dataset.tag);
this.plyClick(index);
}
},
},
  1. 如果歌曲列表中已有播放按钮被按下,则将其回复默认样式。然后改变当前按下的按钮样式。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //歌单播放按钮点击事件
    plyClick:function(index){
    var current = this.top50.map(function(item){
    return item.click;
    }).indexOf(true);
    if (current>-1){
    mouseBtnEv.setNewVal(this.top50[current], 'click', false);
    }
    mouseBtnEv.setNewVal(this.top50[index], 'click', true);
    },

源码请戳 https://github.com/love3forever/gogo3/blob/master/src/components/artist-hot.vue
1.见line 22
2.3.见line 78-87
4.见line 67-77