四、精簡提煉
我們的播放器基本實現了,但是程式碼複用不高,所以我們要進行封裝,以外掛的形式體現。
1.外掛的基本執行程式碼如下:
;(function(undefined){ 'use strict'; ... ... })()
上述程式碼就是基本的外掛程式碼,下面詳細記錄這段程式碼所表示的意思。
前面的分號,可以解決外掛與其它js合併時,別的程式碼可能會產生的錯誤問題;
“(function(){})()”這個結構表示立即執行第一個括號內的函式,其實立即執行函式還有另一種寫法,“(function(){}())”。看大家的喜好,我一般喜歡用第一種;
“undefined”做為引數傳入,因為在老一輩的瀏覽器是不被支援的,直接使用會報錯,js框架要考慮到相容性,因此增加一個形參undefined,就算有人把外面的 undefined 定義了,外掛裡面的 undefined 依然不受影響。
嚴格模式開發
下面進一步補充程式碼函式內程式碼:
'use strict';
這行程式碼表示嚴格模式,顧名思義,嚴格模式就是使得 Javascript 在更嚴格的條件下執行,有助於我們更規範的開發。如果在語法檢測時發現語法問題,則整個程式碼塊失效,並導致一個語法異常。如果在執行期出現了違反嚴格模式的程式碼,則丟擲執行異常。
定義我們的播放器外掛“playMythology”
下面我們真正開始我們的外掛程式碼了,目前整個程式碼如下:
;(function(undefined){ 'use strict'; var _global; function playMythology(opt) { ... ... } playMythology.prototype = {}; //將外掛物件暴露給全域性物件 _global = (function() { return this || (0, eval)('this'); }()); if (typeof module !== "undefined" && module.exports) { module.exports = playMythology; } else if (typeof define === "function" && define.amd) { define(function() { return playMythology; }); } else { !('playMythology' in _global) && (_global.playMythology = playMythology); } })()
定義“_global”,並把全域性環境賦值給一個_global。
並把當前頂級物件賦值給這個變數,程式碼如下:
_global = (function() { return this || (0, eval)('this'); }());
看這段程式碼又是個立即執行函式,不是上面提到的第一種的立即執行函式,而是這種第二種立即執行函式:(functiong(){})結構;首先先介紹一下eval()函式的作用:eval() 函式計算 JavaScript 字串,並把它作為指令碼程式碼來執行,如果引數是一個表示式,eval() 函式將執行表示式,如果引數是Javascript語句,eval()將執行 Javascript 語句。然後在逐一分析語句:return this表示返回當前物件;第一個括號內的逗號操作符 對它的每個運算元求值(從左到右),並返回最後一個運算元的值,那麼這個(0, eval)('this')相當於eval(‘this’),那麼為什麼不用eval(‘this’),而用(0, eval)('this')呢?在嚴格模式下,如果沒有給 this指定值的話,它就是未定義的,為了防止在嚴格模式下window變數被賦予undefined,使用(0, eval)(‘this’)就可以把this重新指向全域性環境物件。因為(0, eval)(‘this’)通過逗號表示式對它的運算元執行了GetValue,計算出一個值,讓this的值指向了全域性物件;而eval(‘this’)計算出的是一個引用,是一個直接呼叫,方法中的this值是obj的引用。
定義“playMythology”,表示我們外掛的名稱。然後我們給這個函式新增屬性,通過prototype來新增,簡單解釋一下prototype是函式的一個屬性,並且是函式的原型物件。prototype只能夠被函式呼叫。
為了實現外掛的模組化並且讓我們的外掛也是一個模組,就得讓我們的外掛也實現模組化的機制。這只需要判斷是否存在載入器,如果存在載入器,我們就使用載入器,如果不存在載入器。我們就使用頂級域物件。下面程式碼就實現了這個功能:
if (typeof module !== "undefined" && module.exports) { module.exports = playMythology; } else if (typeof define === "function" && define.amd) { define(function() { return playMythology; }); } else { !('playMythology' in _global) && (_global.playMythology = playMythology); }
介紹一下主要的運算子:“==”表示相等;“===”表示絕對相等;“!=”表示不相等;“!==”,表示嚴格不相等。JavaScript中,unll與undefined並不相同,但是null==undefined為真,null===undefined為假,所以null !== undefined 為真。
“typeof ”表示返回資料型別,有2種使用方式:typeof(表示式)和typeof 變數名,第一種是對錶達式做運算,第二種是對變數做運算。返回型別為字串,值包括如下幾種:
1. 'undefined' --未定義的變數或值
2. 'boolean' --布林型別的變數或值
3. 'string' --字串型別的變數或值
4. 'number' --數字型別的變數或值
5. 'object' --物件型別的變數或值,或者null(這個是js歷史遺留問題,將null作為object型別處理)
6. 'function' --函式型別的變數或值module.exports 物件是由模組系統建立的。在我們自己寫模組的時候,需要在模組最後寫好模組介面,宣告這個模組對外暴露什麼內容,module.exports 提供了暴露介面的方法。這種方法可以返回全域性共享的變數或者方法。
介紹一下AMD,AMD是一種規範就是其中比較著名一個,全稱是Asynchronous Module Definition,即非同步模組載入機制。從它的規範描述頁面看,AMD很短也很簡單,但它卻完整描述了模組的定義,依賴關係,引用關係以及載入機制。感興趣的朋友可以認真研究一下,requireJS,NodeJs,Dojo,JQuery全部在使用,可見它的價值。
2.基本函式
引入CSS檔案函式:前端開發引入CSS檔案是必不可少的,css主要功能是對頁面佈局進行美化,我希望開發的外掛的皮膚可以動態設定,所以要動態引入CSS檔案,定義了引入CSS檔案函式,具體程式碼如下:
//path表示引入CSS檔案路徑 function cssinto(path) { //如果CSS檔案錯誤,丟擲錯誤異常 if (!path || path.length === 0) { throw new Error('argument "path" is required !'); } //獲取head 物件 var head = document.getElementsByTagName('head')[0]; //建立link標籤並插入到head標籤內 var link = document.createElement('link'); link.href = path; link.rel = 'stylesheet'; link.type = 'text/css'; head.appendChild(link); }
時間轉換函式:主要功能,講audio currentTime 時間戳轉換轉化成“分:秒”顯示格式。
//path表示引入CSS檔案路徑 //時間顯示轉換 function conversion(value) { let minute = Math.floor(value / 60) minute = minute.toString().length === 1 ? ('0' + minute) : minute let second = Math.round(value % 60) second = second.toString().length === 1 ? ('0' + second) : second return minute+":"+second }
引入json檔案函式:file值json檔案路徑,callback只得是回撥函式,當檔案載入完畢就呼叫該函式。
function readTextFile(file, callback) { var rawFile = new XMLHttpRequest(); rawFile.overrideMimeType("application/json"); rawFile.open("GET", file, true); rawFile.onreadystatechange = function() { if (rawFile.readyState === 4 && rawFile.status == "200") { callback(rawFile.responseText); } } rawFile.send(null); }
getElementsByClass:因為我們未講window傳入外掛,所以有些方法我們是不能使用的,所以我們定義下面方法實現,通過class查詢html中dom物件。
//判斷外掛是否存在“getElementsByClass”,沒存在,將使用下面方法實現該功能。 if (!('getElementsByClass' in HTMLElement)) { //prototype在前面已經提到過了,通過“prototype”給HTMLElement新增屬性方法“getElementsByClass” HTMLElement.prototype.getElementsByClass = function(n) { var el = [], _el = this.getElementsByTagName('*'); for (var i = 0; i < _el.length; i++) { if (!!_el[i].className && (typeof _el[i].className == 'string') && _el[i].className.indexOf(n) > -1) { el[el.length] = _el[i]; } } return el; }; ((typeof HTMLDocument !== 'undefined') ? HTMLDocument : Document).prototype.getElementsByClass = HTMLElement.prototype .getElementsByClass; }
引數合併函式: 物件合併,這個主要用於外掛預設引數賦值操作,如果設定就使用新的引數,如果不設定就使用預設引數
//表示原有引數,n表示新引數,override表示是否進行覆蓋 function extend(o, n, override) { for (var key in n) { if (n.hasOwnProperty(key) && (!o.hasOwnProperty(key) || override)) { o[key] = n[key]; } } return o; }
3.基本功能
引數初始化,裡面有詳細的註釋。
_initial: function(opt) { // 預設引數 var def = { skinID: "default", //預設皮膚路徑 domID: "musicbox" //設定播放器容器ID }; //如果函式初始化時,設定引數時,進行合併,如果沒有設定使用預設引數 this.def = extend(def, opt, true); //用於JSON檔案儲存資料 this.data = {}; //播放器初始音量為0.3 this.sound = 0.3; this.currentID = 0; //建立audion this.audion = document.createElement("AUDIO"); //獲取播放器dom物件 this.dom = document.getElementById(def.domID); //播放器初始音量 this.audion.volume = this.sound; //定義定時器,用於進度條調整,歌曲滾動等功能 this.timecolick; //歌曲容器 this.songBox; //播放器狀態,0表示順序播放;1表示迴圈播放;2表示隨機播放。 this.isPlayState = 0; //歌曲列表用於儲存歌曲資料 this.songList; //歌曲播放進度條 this.songProgress; //播放進度條上的播放頭 this.songPlayHead; //判斷歌曲是否允許滾動,0表示允許,1表示不允許 this.isSlide = 0; //播放進度,0表示初始位置 this.playprogress = 0; //最大 this.playMax = 0; //播放器是否在播放,0表示正在播放,1表示暫停 this.isPlaying = 0; //歌曲列表滾動距離 this.scollHeight = 20; //初始化播放器,並開始播放 this._GetData(); },
播放器介面初始化,並播放歌曲
//設定播放器介面 var _this = this;//把當前物件存到_this //初始化CSS檔案 cssinto("skin/" + this.def.skinID + "/css/music.css"); //讀取json資料 readTextFile("skin/" + this.def.skinID + "/data.json", function(text) { //資料讀取到data _this.data = JSON.parse(text); //把介面HTML程式碼插入容器,介面初始化 _this.dom.innerHTML = _this.data[0].MusicHtml; //設定歌曲列表 var htmlinsert = ""; //過去歌曲容器dom物件 _this.songBox = _this.dom.getElementsByClass(_this.data[0].musiclistbox)[0]; //儲存歌曲資料 _this.songList = _this.data[0].Songlist; for (var i = 0; i < _this.songList.length; i++) { htmlinsert += '<li><span>' + _this.songList[i].songname + '</span></li>'; } _this.songBox.innerHTML = htmlinsert; //設定音樂列表單擊事件 for (var i = 0; i < _this.songBox.childNodes.length; i++) { ( function(j) { _this.songBox.childNodes[j].onclick = function() { _this._PlaySong(j); } })(i) } //所有資料載入完畢,開始播放歌曲 _this._PlaySong(0);
暫停播放功能。
//播放停止按鈕事件 _this.dom.getElementsByClass(_this.data[0].playBT)[0].onclick = function(e) { //如果正在播放則停止播放 if (_this.isPlaying == 0) { this.className = "playbutton"; _this.isPlaying = 1; _this.audion.pause() } else //如果停止播放則開始播放 { this.className = "pausebutton"; _this.isPlaying = 0; _this.audion.play(); } }
歌曲切換功能,上一首,下一首切換。
//上一首按鈕 _this.dom.getElementsByClass(_this.data[0].preBton)[0].onclick = function(e) { if (_this.currentID > 0) { _this.currentID--; } else { _this.currentID = _this.songList.length - 1; } _this._PlaySong(_this.currentID) } //下一首按鈕 _this.dom.getElementsByClass(_this.data[0].nextBton)[0].onclick = function(e) { if (_this.currentID < _this.songList.length - 1) { _this.currentID++; } else { _this.currentID = 0; } _this._PlaySong(_this.currentID) }
隨機播放功能,按鈕點選後歌曲將實現隨機播放。
//隨機播放按鈕 var randombtn = _this.dom.getElementsByClass(_this.data[0].randombtn)[0]; randombtn.onclick = function(e) { if (_this.isPlayState == 1) { _this.isPlayState = 0; this.className = _this.data[0].shuffle; return; } if (_this.isPlayState == 2) { onereplay.className = _this.data[0].replay; } _this.isPlayState = 1; this.className = _this.data[0].shuffleon; }
單曲迴圈功能,按鈕點選後歌曲將實現單曲迴圈播放。
//單曲迴圈按鈕 var onereplay = _this.dom.getElementsByClass(_this.data[0].onereplay)[0]; onereplay.onclick = function(e) { if (_this.isPlayState == 2) { _this.isPlayState = 0; this.className = _this.data[0].replay; return; } if (_this.isPlayState == 1) { randombtn.className = _this.data[0].shuffleon; } _this.isPlayState = 2; this.className = _this.data[0].replay; }
音量調節功能,拖放調節音量功能。
//音量調節按鈕 var soundHead = _this.dom.getElementsByClass(_this.data[0].soundHead)[0]; var soundBox = _this.dom.getElementsByClass(_this.data[0].soundBox)[0]; var soundCurrentTime = _this.dom.getElementsByClass(_this.data[0].soundCurrentTime)[0]; soundHead.style.left = _this.sound * 100 + 'px'; soundCurrentTime.style.width = _this.sound * 100 + '%'; soundHead.onmousedown = function(e) { var x = (e || window.event).clientX; var l = this.offsetLeft; var max = soundBox.offsetWidth - this.offsetWidth; document.onmousemove = function(e) { var thisX = (e || window.event).clientX; var to = Math.min(max, Math.max(-2, l + (thisX - x))); if (to < 0) { to = 0; } soundHead.style.left = to + 'px'; //此句程式碼可以除去選中效果 window.getSelection ? window.getSelection().removeAllRanges() : document.selection.empty(); _this.audion.volume = to / max; //document.querySelector('.now') soundCurrentTime.style.width = to / max * 100 + '%'; } //注意此處是document 才能有好的拖動效果 document.onmouseup = function() { document.onmousemove = null; }; }
進度條功能。
//獲取進度條dom _this.songProgress = _this.dom.getElementsByClass(_this.data[0].SongProgress)[0]; //獲取進度條上的播放頭 _this.songPlayHead = _this.dom.getElementsByClass(_this.data[0].playHead)[0]; //單擊進度條 調整發播放進度 _this.songProgress.onclick = function(e) { var x = (e || window.event).clientX; var left = x - this.offsetLeft - _this.songPlayHead.offsetWidth; var maxwidth = _this.songProgress.offsetWidth; _this.dom.getElementsByClass(_this.data[0].playHead)[0].style.left = left + 'px'; var currenttime = _this.audion.duration * (left / maxwidth) var p = left / maxwidth _this.audion.currentTime = p * _this.audion.duration; _this.audion.play(); }; //拖動播放頭,調整播放進度 _this.songPlayHead.onmousedown = function(e) { var x = (e || window.event).clientX; var l = this.offsetLeft; var max = _this.songProgress.offsetWidth - this.offsetWidth; _this.playMax = max; document.onmousemove = function(e) { var thisX = (e || window.event).clientX; var to = Math.min(max, Math.max(-2, l + (thisX - x))); if (to < 0) { to = 0; } _this.playprogress = to; _this.isSlide = 1; _this.songPlayHead.style.left = to + 'px'; _this.dom.getElementsByClass(_this.data[0].SingerCurrentTime)[0].innerHTML = conversion(_this.audion.duration * (_this .playprogress / _this.playMax)); //此句程式碼可以除去選中效果 window.getSelection ? window.getSelection().removeAllRanges() : document.selection.empty(); // _this.audion.currentTime = to / max; } //注意此處是document 才能有好的拖動效果 document.onmouseup = function() { _this.isSlide = 0; _this.audion.currentTime = (_this.playprogress / _this.playMax) * _this.audion.duration; _this.audion.play(); document.onmousemove = null; };
定時函式功能
//定時函式 _this.timecolick = setInterval(function() { if (_this.isSlide == 1) { return; } //設定進度條 var percent = Math.floor(_this.audion.currentTime / _this.audion.duration * 10000) / 100 + "%"; _this.songPlayHead.style.left = percent; //設定當前播放時間 _this.dom.getElementsByClass(_this.data[0].SingerCurrentTime)[0].innerHTML = conversion(_this.audion.currentTime); if (_this.audion.ended) { if (_this.isPlayState == 0) //順序播放 { if (_this.currentID < _this.songList.length - 1) { _this.currentID++; } else { _this.currentID = 0; } } else if (_this.isPlayState == 1) //隨機播放 { _this.currentID = Math.floor(Math.random() * _this.songList.length - 1) } else //單曲迴圈 { _this.currentID = _this.currentID; } console.log(_this.currentID) _this._PlaySong(_this.currentID); } }, 100)
歌曲播放功能
__PlaySong: function(songID) { var _this = this; this.audion.setAttribute("src", this.data[0].Songlist[songID].songurl); this.dom.getElementsByClass(this.data[0].SongName)[0].innerHTML = _this.data[0].Songlist[songID].songname; _this.dom.getElementsByClass(this.data[0].Singer)[0].innerHTML = this.data[0].Songlist[songID].songer; _this.audion.onloadedmetadata = function() { _this.dom.getElementsByClass(this.data[0].SingerCurrentTime)[0].innerHTML = conversion(_this.audion.currentTime); _this.dom.getElementsByClass(this.data[0].showalltime)[0].innerHTML = conversion(_this.audion.duration) } this.audion.play(); var Songlist = _this.songBox.childNodes; for (var i = 0; i < Songlist.length; i++) { if (songID == i) { Songlist.item(i).setAttribute("class", this.data[0].currentSong); } else { Songlist.item(i).setAttribute("class", "") } } //console.log(_this.scollHeight*songID) _this._scollToMusiclist(songID, _this.dom.getElementsByClass(this.data[0].MusicList)[0]) }
歌曲滾動功能。
_scollToMusiclist: function(singID, wmusicbox) { //ok 2019年4月5日,終於除錯成功,長時間不開發真的不行,好多事情想不到,剛才不停的滾動現象是由於我沒有對最大值進行判斷,如果超過最大值,我們需要把最大值賦給變數,那樣就不會不停的閃爍了。 var gundong = singID * 20; var maxgundong = wmusicbox.scrollHeight - wmusicbox.offsetHeight; if (gundong > maxgundong) { gundong = maxgundong; } var scollTime = setInterval(function() { console.log(wmusicbox.scrollTop) if (wmusicbox.scrollTop < gundong) { wmusicbox.scrollTop = wmusicbox.scrollTop + 1; console.log(gundong) } else if (wmusicbox.scrollTop > gundong) { wmusicbox.scrollTop = wmusicbox.scrollTop - 1; console.log("2") } else { console.log("=") clearInterval(scollTime); } }) }
ok,這網我們的網頁播放器已經全部編寫完畢,我把原始碼打包,提供大家下載,多提寶貴意見。 單擊下載