成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專(zhuān)欄INFORMATION COLUMN

HTML5 CANVAS 彈幕插件--DanMuer.js(V3.2.5)

FleyX / 1563人閱讀

摘要:主要是因?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

相關(guān)文章

  • HTML5 CANVAS 彈幕插件--DanMuer.jsV3.2.5

    摘要:主要是因?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ì)思...

    qpwoeiru96 評(píng)論0 收藏0
  • HTML5彈幕視頻播放器

    摘要:考慮到只適合在單位時(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)了,就作為自己工作半年后的一次沉淀算了。 文中...

    happyfish 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

閱讀需要支付1元查看
<