摘要:主要是因?yàn)榈诙嬷胁シ牌髂K和彈幕模塊耦合得太嚴(yán)重了,遠(yuǎn)遠(yuǎn)達(dá)不到我想要的效果,所以續(xù)寫(xiě)了第三版。這里的通道是指彈幕從右往左運(yùn)行時(shí)所在的那一行位置,這些通道是在尺寸變化時(shí)生成的,不同類(lèi)型的彈幕都有其通道集合。
最新版本 V 3.2.5
新增了圖片彈幕類(lèi)型,修改了demo展示頁(yè)面,調(diào)整了部分代碼,具體請(qǐng)參看git里的CHANGELOG.md和README.md
文章里主要講實(shí)現(xiàn)方法和設(shè)計(jì)思想,所以有部分接口依舊是老版本接口,最新的接口請(qǐng)去git里面查看
前言說(shuō)實(shí)話(huà),從第二版到現(xiàn)在又過(guò)了半年,本來(lái)以為可能不會(huì)寫(xiě)第三版的,頂多將第二版的代碼重構(gòu)下就可以了,沒(méi)想到還是花了一個(gè)星期左右續(xù)寫(xiě)了第三版。主要是因?yàn)榈诙嬷?播放器模塊和彈幕模塊耦合得太嚴(yán)重了,遠(yuǎn)遠(yuǎn)達(dá)不到我想要的效果,所以續(xù)寫(xiě)了第三版。這次的代碼將更輕,我去除了播放器模塊,使得插件的適用范圍更加的擴(kuò)大,而且讓我有點(diǎn)驚喜的是在寫(xiě)第三版的過(guò)程中又讓彈幕系統(tǒng)的性能進(jìn)一步得到了提升,可以講也是額外的驚喜了。
由于第三版我是用ES6語(yǔ)法寫(xiě)的,所以兼容性不是很好(沒(méi)錯(cuò),我只是在針對(duì)IE),就算用babel轉(zhuǎn)成ES5,IE依舊毒,目前支持IE10+。所以后面我會(huì)抽個(gè)時(shí)間去寫(xiě)個(gè)ES5全兼容版本的,不考慮IE或者只是對(duì)源碼感興趣的可以盡情使用。
github : github
API接口都在git里面,文章不會(huì)介紹插件使用相關(guān)的內(nèi)容,僅僅解釋部分源碼和設(shè)計(jì)思想,如果覺(jué)得插件還行,請(qǐng)大家git給個(gè)星,謝謝
demo : 我是demo
我碰到好像有人對(duì)demo不知道怎么操作,下面我簡(jiǎn)單介紹下基本操作:
所有已發(fā)布功能項(xiàng)可以通過(guò)下拉框進(jìn)行切換,目前包括“添加普通彈幕”,“添加高級(jí)彈幕”,“過(guò)濾”,“添加全局樣式”,“控制項(xiàng)”
添加普通彈幕和添加高級(jí)彈幕都只是添加數(shù)據(jù)而已,不會(huì)運(yùn)行插件,你需要跳到“控制項(xiàng)”點(diǎn)擊啟動(dòng),然后等彈幕出來(lái)即可
高級(jí)彈幕的動(dòng)畫(huà)是屬于排隊(duì)動(dòng)畫(huà),你要先將修改后的數(shù)據(jù)“保存為第n步”(n至少為1)后點(diǎn)擊確定添加才可以
過(guò)濾功能的話(huà),最簡(jiǎn)單的“type”:“slide”,表示過(guò)濾所有滾動(dòng)型的彈幕,或者“text”:“string”表示過(guò)濾包含string的所有彈幕
你可以先啟動(dòng)然后添加彈幕,也是一樣的,操作順序沒(méi)太高要求
代碼的那些事源碼總共由5部分組成:
普通彈幕類(lèi)
高級(jí)彈幕類(lèi)
主程序類(lèi)
封裝輸出函數(shù)
Tween算法類(lèi)
第4和第5個(gè)部分比較簡(jiǎn)單,第5部分就是Tween算法,原汁原味,第4部分則是將所有內(nèi)部的接口進(jìn)行過(guò)濾,選擇性地暴露一些我想暴露的內(nèi)部功能接口,并且提供一個(gè)對(duì)外的接口,增加一點(diǎn)穩(wěn)定性罷了。源碼如下:
let DanMuer = function(wrapper,opts){ let proxyDMer = new Proxy( new DMer(wrapper,opts), { get : function(target,key){ if(typeof target[key] == "function") return target[key].bind(target); return target[key]; } }); //保證this指向原對(duì)象 let DM = proxyDMer; //選擇性的暴露某些接口 return { pause : DM.pause, //暫停 run : DM.run, //繼續(xù) start : DM.start, //運(yùn)行 stop : DM.stop, //停止 changeStyle : DM.changeStyle, //修改普通彈幕全局樣式 addGradient : DM.addGradient, //普通彈幕漸變 setSize : DM.setSize, //修改寬高 inputData : DM.inputData, //向普通彈幕插入數(shù)據(jù) inputEffect : DM.inputEffect, //向高級(jí)彈幕插入數(shù)據(jù) clear : DM.clear, //清除所有彈幕 reset : DM.reset, //重新從某個(gè)彈幕開(kāi)始 addFilter : DM.addFilter, //添加過(guò)濾 removeFilter : DM.removeFilter, //刪除過(guò)濾 disableEffect : DM.disableEffect, //不啟用高級(jí)彈幕 enableEffect : DM.enableEffect, //啟用高級(jí)彈幕 getSize : DM.getSize, //獲取寬高, getFPS : DM.getFPS //獲取fps }; }; //提供對(duì)外的引用接口 if( typeof module != "undefined" && module.exports ){ module.exports = DanMuer; } else if( typeof define == "function" && define.amd ){ define(function(){ return DanMuer;}); } else { window.DanMuer = DanMuer; }
第3個(gè)部分屬于入口類(lèi),事實(shí)上每次調(diào)用插件都會(huì)先對(duì)第3部分進(jìn)行實(shí)例化,這里主要保存一些對(duì)外暴露的API接口,還有就是插件的初始化函數(shù),事件函數(shù)以及主循環(huán)函數(shù),用于對(duì)插件總體的控制,部分源碼如下:
//初始化 constructor(wrap,opts = {}){ if(!wrap){ throw new Error("沒(méi)有設(shè)置正確的wrapper"); } //datas this.wrapper = wrap; this.width = wrap.clientWidth; this.height = wrap.clientHeight; this.canvas = document.createElement("canvas"); this.canvas2 = document.createElement("canvas"); this.normal = new normalDM(this.canvas,opts); //這里是普通彈幕的對(duì)象 this.effect = new effectDM(this.canvas2,opts); //這里是高級(jí)彈幕的對(duì)象 this.name = opts.name || ""; //沒(méi)卵用 this.fps = 0; //status this.drawing = opts.auto || false; this.startTime = new Date().getTime(); //fn this[init](); this[loop](); if(opts.enableEvent) this.initEvent(opts); } [init](){ //生成對(duì)應(yīng)的canvas this.canvas.style.cssText = "position:absolute;z-index:100;top:0px;left:0px;"; this.canvas2.style.cssText = "position:absolute;z-index:101;top:0px;left:0px;"; this.setSize(); this.wrapper.appendChild(this.canvas); this.wrapper.appendChild(this.canvas2); } //loop [loop](normal = this.normal,effect = this.effect,prev = this.startTime){ let now = new Date().getTime(); if(!this.drawing){ normal.clearRect(); effect.clearRect(); return false; } else { let [w,h,time] = [this.width,this.height,now - prev]; this.fps = 1000 / time >> 0; //這里進(jìn)行內(nèi)部的循環(huán)操作 normal.update(w,h,time); effect.update(w,h,time); } requestAnimationFrame( () => { this[loop](normal,effect,now); } ); } //主要對(duì)鼠標(biāo)右鍵進(jìn)行綁定 initEvent(opts){ let [el,normal,searching] = [this.canvas2,this.normal,false]; el.onmouseup = function(e){ e = e || event; if( searching ) return false; searching = true; if( e.button == 2 ){ let [pos,result] = [e.target.getBoundingClientRect(),""]; let [x,y,i,items,item] = [ e.clientX - pos.left, e.clientY - pos.top, 0, normal.save ]; for( ; item = items[i++]; ){ let [ix,iy,w,h] = [item.x, item.y, item.width + 10, item.height]; if( x < ix || x > ix + w || y < iy - h/2 || y > iy + h/2 || item.hide || item.recovery ) continue; result = item; break; } let callback = opts.callback || function(){}; callback(result); searching = false; } }; el.oncontextmenu = function(e){ e = e || event; e.preventDefault(); }; }
源碼最主要的就是第1部分和第2部分,大家在git->src里面可以看到兩個(gè)類(lèi)分別對(duì)應(yīng)的文件,源碼里面我的注釋打了很多,而且每個(gè)函數(shù)的長(zhǎng)度都不長(zhǎng),很容易看懂,這里就不對(duì)每一個(gè)功能做具體介紹了,下面主要講講幾個(gè)比較重要的函數(shù)和設(shè)計(jì)思想:
/*循環(huán),這里是對(duì)主程序暴露的主要接口,用于普通彈幕內(nèi)部的循環(huán)工作,其實(shí)工作流程主要由幾個(gè)步驟組成: ** 1.判斷全局樣式是否發(fā)生變化,保持全局樣式的準(zhǔn)確性 ** 2.判斷當(dāng)前彈幕機(jī)的狀態(tài)(如暫停、運(yùn)行等)并進(jìn)行相關(guān)操作 ** 3.更新for循環(huán)的初始下標(biāo)(startIndex),主要是用于性能的優(yōu)化 ** 4.計(jì)算每個(gè)彈幕的狀態(tài) ** 5.繪制彈幕 ** 6.對(duì)每個(gè)彈幕的狀態(tài)進(jìn)行評(píng)估,如果已經(jīng)顯示完成就進(jìn)行回收 ** 基本上其他的功能都是圍繞這些步驟開(kāi)始拓展和完善,明白了工作原理后其他的函數(shù)就很好理 ** 解了,都是為了完成這些工作流程而進(jìn)行的,而且基本上源碼里都有注釋?zhuān)@里就不詳細(xì)說(shuō)了 */ update(w,h,time){ let [items,cxt] = [this.save,this.cxt]; this.globalChanged && this.initStyle(cxt); //初始化全局樣式 !this.looped && this.countWidth(items); //計(jì)算文本寬度以及初始化位置(只執(zhí)行一次) if( this.paused ) return false; //暫停 this.refresh(items); //更新初始下標(biāo)startIndex let [i,item] = [this.startIndex]; cxt.clearRect(0,0,w,h); for( ; item = items[i++]; ){ this.step(item,time); this.draw(item,cxt); this.recovery(item,w); } }
針對(duì)普通彈幕類(lèi)還有一個(gè)有點(diǎn)難理解的是“通道”的獲取。這里的“通道”是指彈幕從右往左運(yùn)行時(shí)所在的那一行位置,這些通道是在canvas尺寸變化時(shí)生成的,不同類(lèi)型的彈幕都有其通道集合。當(dāng)一條新彈幕需要顯示在canvas上時(shí)需要去獲取它被分配的位置,也就是通道,通道被占用時(shí),該行將不會(huì)重新放置新的彈幕, 當(dāng)通道已經(jīng)被分配完成后,將會(huì)隨機(jī)生成一條臨時(shí)通道,臨時(shí)通道的位置隨機(jī)出現(xiàn),并且臨時(shí)通過(guò)被釋放時(shí)不會(huì)被收回通道集合中,而正常通道會(huì)被收回到集合中以待被下一個(gè)彈幕調(diào)用。下面是代碼:
//生成通道行 countRows(){ //保存臨時(shí)變量 let unitHeight = parseInt(this.globalSize) + this.space; let [rowNum , rows] = [ ( ( this.height - 20 ) / unitHeight ) >> 0, this.rows ]; //重置通道 for( let key of Object.keys(rows) ){ rows[key] = []; } //重新生成通道 for( let i = 0 ; i < rowNum; i++ ){ let obj = { idx : i, y : unitHeight * i + 20 }; rows.slide.push(obj); i >= rowNum / 2 ? rows.bottom.push(obj) : rows.top.push(obj); } //更新實(shí)例屬性 this.unitHeight = unitHeight; this.rowNum = rowNum; } //獲取通道 getRow(item){ //如果該彈幕正在顯示中,則返回其現(xiàn)有通道 if( item.row ) return item.row; //獲取新通道 const [rows,type] = [this.rows,item.type]; const row = ( type != "bottom" ? rows[type].shift() : rows[type].pop() ); //生成臨時(shí)通道 const tempRow = this["getRow_"+type](); if( row && item.type == "slide" ){ item.x += ( row.idx * 8 ); item.speed += ( row.idx / 3 ); } //返回分配的通道 return row || tempRow; } getRow_bottom(){ return { y : 20 + this.unitHeight * ( ( Math.random() * this.rowNum / 2 + this.rowNum / 2 ) << 0 ), speedChange : false, tempItem : true }; } getRow_slide(){ return { y : 20 + this.unitHeight * ( ( Math.random() * this.rowNum ) << 0 ), speedChange : true, tempItem : true }; } getRow_top(){ return { y : 20 + this.unitHeight * ( ( Math.random() * this.rowNum / 2 ) << 0 ), speedChange : false, tempItem : true }; }
高級(jí)彈幕類(lèi)與普通彈幕類(lèi)有點(diǎn)微妙的差別,但總體是一樣,唯一需要在意的是與計(jì)算相關(guān)的代碼,因?yàn)椴浑y所以這里也不做繼續(xù)說(shuō)明了,請(qǐng)參看源碼里的注釋。
結(jié)語(yǔ)就第二版來(lái)說(shuō),第三版性能更好,而且實(shí)現(xiàn)了播放器模塊和彈幕模塊的解耦,也就是說(shuō)相比第二版,第三版 可以適用但不限于播放器,可用性更高,而且實(shí)現(xiàn)了高級(jí)彈幕的發(fā)送,未來(lái)將慢慢補(bǔ)齊更多的功能和代碼重構(gòu),希望大家遇到什么BUG或者是有某些的需求,請(qǐng)私信或是將反饋提交到本郵箱:[email protected] || [email protected],如果覺(jué)得本插件對(duì)你有用歡迎給個(gè)星,謝謝。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/83909.html
摘要:主要是因?yàn)榈诙嬷胁シ牌髂K和彈幕模塊耦合得太嚴(yán)重了,遠(yuǎn)遠(yuǎn)達(dá)不到我想要的效果,所以續(xù)寫(xiě)了第三版。這里的通道是指彈幕從右往左運(yùn)行時(shí)所在的那一行位置,這些通道是在尺寸變化時(shí)生成的,不同類(lèi)型的彈幕都有其通道集合。 最新版本 V 3.2.5 新增了圖片彈幕類(lèi)型,修改了demo展示頁(yè)面,調(diào)整了部分代碼,具體請(qǐng)參看git里的CHANGELOG.md和README.md 文章里主要講實(shí)現(xiàn)方法和設(shè)計(jì)思...
摘要:考慮到只適合在單位時(shí)間內(nèi)繪制少量彈幕,這對(duì)于我們播放器來(lái)說(shuō)明顯是不合需求的。比如說(shuō)直播播放器模式和視頻播放器模式的優(yōu)化細(xì)節(jié)又有點(diǎn)不一樣,還有高級(jí)彈幕的處理圖片,圖形等。。 前言 大家年前好,馬上就要元旦了,在很久沒(méi)有寫(xiě)文章之后,想到這篇文章將會(huì)成為本人今年的絕響也是有點(diǎn)蛋疼。不過(guò)也好,畢竟本人算不得什么勤快的生物,而且比起那些大神來(lái)說(shuō)也差遠(yuǎn)了,就作為自己工作半年后的一次沉淀算了。 文中...
閱讀 1063·2021-11-24 09:39
閱讀 3602·2021-11-22 13:54
閱讀 2558·2021-10-11 10:59
閱讀 796·2021-09-02 15:40
閱讀 1035·2019-08-30 15:55
閱讀 1053·2019-08-30 13:57
閱讀 2314·2019-08-30 13:17
閱讀 3034·2019-08-29 18:32