确立项目结构,开始编码:
因为使用webpack,所以我们直接将css打包至js内,以便作为插件供用户使用:
require('./skPlayer.scss');
抽离公共方法,在播放器中有很多可能需要抽离的公共方法如:点击播放进度条和音量进度条时需要计算鼠标距离进度条左端的距离以进行进度跳转,时间从duratin中获取到的以秒为单位的时间转换成标准时间格式等等:
const Util = {
leftDistance: (el) => {
let left = el.offsetLeft;
let scrollLeft;while (el.offsetParent) {
el = el.offsetParent;
left += el.offsetLeft;
}
scrollLeft = document.body.scrollLeft + document.documentElement.scrollLeft;return left - scrollLeft;
},
timeFormat: (time) => {
let tempMin = parseInt(time / 60);
let tempSec = parseInt(time % 60);
let curMin = tempMin < 10 ? ('0' + tempMin) : tempMin;
let curSec = tempSec < 10 ? ('0' + tempSec) : tempSec;return curMin + ':' + curSec;
},
percentFormat: (percent) => {return (percent * 100).toFixed(2) + '%';
},
ajax: (option) => {
option.beforeSend && option.beforeSend();
let xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => {if(xhr.readyState === 4){if(xhr.status >= 200 && xhr.status < 300){
option.success && option.success(xhr.responseText);
}else{
option.fail && option.fail(xhr.status);
}
}
};
xhr.open('GET',option.url);
xhr.send(null);
}
};View Code
由于设计之初,考虑到播放器的独特性,设计为只能存在一个实例,设置了一个全局变量以判断当前是否存在实例:
let instance = false;
在使用ES6的情况下,我们将主逻辑放在构造函数内部,将通用性强和API放在公共函数内部:
class skPlayer {
constructor(option){
}
template(){
}
init(){
}
bind(){
}
prev(){
}
next(){
}
switchMusic(index){
}
play(){
}
pause(){
}
toggle(){
}
toggleList(){
}
toggleMute(){
}
switchMode(){
}
destroy(){
}
}View Code
实例判断,如果存在返回无原型的空对象,因为ES6构造函数内默认返回带原型的实例:
if(instance){
console.error('SKPlayer只能存在一个实例!');return Object.create(null);
}else{
instance = true;
}
初始化配置项,默认配置与用户配置合并:
const defaultOption = {
...
};this.option = Object.assign({},defaultOption,option);
将常用属性绑定在实例上:
this.root = this.option.element;this.type = this.option.music.type;this.music = this.option.music.source;this.isMobile = /mobile/i.test(window.navigator.userAgent);
一些公共的API内部this指向在默认情况下指向实例,但是为了减少代码量,将操作界面上的功能与API调用一套代码,在绑定事件的时候this指向会改变,所以通过bind的方式绑定this,当然也可以在绑定事件的时候使用箭头函数:
this.toggle = this.toggle.bind(this);this.toggleList = this.toggleList.bind(this);this.toggleMute = this.toggleMute.bind(this);this.switchMode = this.switchMode.bind(this);
接下来,我们就使用ES6字符串模板开始生成HTML,插入到页面中:
this.root.innerHTML = this.template();
接下来初始化,初始化过程中将常用DOM节点绑定,初始化配置项,初始化操作界面:
this.init();
init(){this.dom = {
cover: this.root.querySelector('.skPlayer-cover'),
playbutton: this.root.querySelector('.skPlayer-play-btn'),
name: this.root.querySelector('.skPlayer-name'),
author: this.root.querySelector('.skPlayer-author'),
timeline_total: this.root.querySelector('.skPlayer-percent'),
timeline_loaded: this.root.querySelector('.skPlayer-line-loading'),
timeline_played: this.root.querySelector('.skPlayer-percent .skPlayer-line'),
timetext_total: this.root.querySelector('.skPlayer-total'),
timetext_played: this.root.querySelector('.skPlayer-cur'),
volumebutton: this.root.querySelector('.skPlayer-icon'),
volumeline_total: this.root.querySelector('.skPlayer-volume .skPlayer-percent'),
volumeline_value: this.root.querySelector('.skPlayer-volume .skPlayer-line'),
switchbutton: this.root.querySelector('.skPlayer-list-switch'),
modebutton: this.root.querySelector('.skPlayer-mode'),
musiclist: this.root.querySelector('.skPlayer-list'),
musicitem: this.root.querySelectorAll('.skPlayer-list li')
};this.audio = this.root.querySelector('.skPlayer-source');if(this.option.listshow){this.root.className = 'skPlayer-list-on';
}if(this.option.mode === 'singleloop'){this.audio.loop = true;
}this.dom.musicitem[0].className = 'skPlayer-curMusic';
}View Code
事件绑定,主要绑定audio的事件以及操作面板的事件:
this.bind();
bind(){this.updateLine = () => {
let percent = this.audio.buffered.length ? (this.audio.buffered.end(this.audio.buffered.length - 1) / this.audio.duration) : 0;this.dom.timeline_loaded.style.width = Util.percentFormat(percent);
};// this.audio.addEventListener('load', (e) => {// if(this.option.autoplay && this.isMobile){// this.play();// }// });this.audio.addEventListener('durationchange', (e) => {this.dom.timetext_total.innerHTML = Util.timeFormat(this.audio.duration);this.updateLine();
});this.audio.addEventListener('progress', (e) => {this.updateLine();
});this.audio.addEventListener('canplay', (e) => {if(this.option.autoplay && !this.isMobile){this.play();
}
});this.audio.addEventListener('timeupdate', (e) => {
let percent = this.audio.currentTime / this.audio.duration;this.dom.timeline_played.style.width = Util.percentFormat(percent);this.dom.timetext_played.innerHTML = Util.timeFormat(this.audio.currentTime);
});//this.audio.addEventListener('seeked', (e) => {// this.play();//});this.audio.addEventListener('ended', (e) => {this.next();
});this.dom.playbutton.addEventListener('click', this.toggle);this.dom.switchbutton.addEventListener('click', this.toggleList);if(!this.isMobile){this.dom.volumebutton.addEventListener('click', this.toggleMute);
}this.dom.modebutton.addEventListener('click', this.switchMode);this.dom.musiclist.addEventListener('click', (e) => {
let target,index,curIndex;if(e.target.tagName.toUpperCase() === 'LI'){
target = e.target;
}else{
target = e.target.parentElement;
}
index = parseInt(target.getAttribute('data-index'));
curIndex = parseInt(this.dom.musiclist.querySelector('.skPlayer-curMusic').getAttribute('data-index'));if(index === curIndex){this.play();
}else{this.switchMusic(index + 1);
}
});this.dom.timeline_total.addEventListener('click', (event) => {
let e = event || window.event;
let percent = (e.clientX - Util.leftDistance(this.dom.timeline_total)) / this.dom.timeline_total.clientWidth;if(!isNaN(this.audio.duration)){this.dom.timeline_played.style.width = Util.percentFormat(percent);this.dom.timetext_played.innerHTML = Util.timeFormat(percent * this.audio.duration);this.audio.currentTime = percent * this.audio.duration;
}
});if(!this.isMobile){this.dom.volumeline_total.addEventListener('click', (event) => {
let e = event || window.event;
let percent = (e.clientX - Util.leftDistance(this.dom.volumeline_total)) / this.dom.volumeline_total.clientWidth;this.dom.volumeline_value.style.width = Util.percentFormat(percent);this.audio.volume = percent;if(this.audio.muted){this.toggleMute();
}
});
}
}View Code
至此,核心代码基本完成,接下来就是自己根据需要完成API部分。
最后我们暴露模块:
module.exports = skPlayer;
一个HTML5音乐播放器就大功告成了 ~ !