HTML5 CANVAS Bullet Screen Plug-in (V3.0.0)

Keywords: Javascript IE github git

Preface

To tell the truth, from Second Edition Half a year later, I thought I might not be able to write the third edition. I could reconstruct the second edition of the code at most, but it took me about a week to continue the third edition. The main reason is that in the second edition, the player module and the bullet screen module are too seriously coupled to achieve the desired effect, so the third edition is continued. This time, the code will be lighter. I went to the player module to expand the scope of application of the plug-in, and I was a little surprised that in the process of writing the third edition, the performance of the bullet screen system has been further improved, which can be said to be an additional surprise.

Because the third edition is written in ES6 grammar, the compatibility is not very good (yes, I'm only aiming at IE). Even if I use babel to convert to ES5, IE is still poisonous, so I will take some time later to write a fully compatible version of ES5, regardless of IE or just interested in the source code can be used as much as I like.

demo : I'm demo.
github : github
API interfaces are all in git, the article will not introduce plug-in use related content, only explain some of the source code and design ideas, if you think plug-in is OK, please give a star, thank you.

Those things about the code

Source code consists of four parts:

  1. Ordinary Barrage Category

  2. Advanced Barrage Category

  3. Main Program Class

  4. Encapsulated Output Function

The fourth part is relatively simple, that is to filter all the internal interfaces, selectively expose some of the internal functional interfaces I want to expose, and provide an external interface, add a little stability. The source code is as follows:

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];
        }
    }); //Ensure that this points to the original object

    let DM = proxyDMer;

    //Selective exposure of certain interfaces
    return {
        pause : DM.pause, //suspend
        run : DM.run, //Continue
        start : DM.start, //Function
        stop : DM.stop,    //Stop it
        changeStyle : DM.changeStyle, //Modify the Global Style of Ordinary Barrage
        addGradient : DM.addGradient, //Ordinary barrage gradient
        setSize : DM.setSize, //Modify width and height
        inputData : DM.inputData, //Insert data into ordinary barrage
        inputEffect : DM.inputEffect, //Insert data into advanced barrage
        clear : DM.clear, //Clear all barrages
        reset : DM.reset, //Re-start with a barrage
        addFilter : DM.addFilter, //Adding filtering
        removeFilter : DM.removeFilter, //Delete filtering
        disableEffect : DM.disableEffect, //No Advanced Barrage Curtain
        enableEffect : DM.enableEffect, //Enabling Advanced Barrage Curtain
        getSize : DM.getSize, //Acquire width and height.
        getFPS : DM.getFPS //Get fps
    };
};

//Provide external reference interface
if( typeof module != 'undefined' && module.exports ){
    module.exports = DanMuer;
} else if( typeof define == "function" && define.amd ){
    define(function(){ return DanMuer;});
} else {
    window.DanMuer = DanMuer;
}

The third part belongs to the entry class. In fact, every call to the plug-in will first instantiate the third part. Here, we mainly save some exposed API interfaces, as well as the initialization function, event function and main loop function of the plug-in for the overall control of the plug-in. Part of the source code is as follows:

//Initialization
    constructor(wrap,opts = {}){

        if(!wrap){
            throw new Error("Not set correctly 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); //Here is the object of the ordinary barrage.
        this.effect = new effectDM(this.canvas2,opts); //Here's the object of a high-level barrage.

        this.name = opts.name || ""; //Eggless
        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](){
        //Generate corresponding 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;
            //Here is the internal loop operation.
            normal.update(w,h,time);
            effect.update(w,h,time);
        }

        requestAnimationFrame( () => { this[loop](normal,effect,now); } );
    }
    
    //It mainly binds the right mouse button.
    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();
        };

    }

The most important part of the source code is the first part and the second part. You can see two kinds of files corresponding to each other in Git - > src. In the source code, I made a lot of comments, and the length of each function is not very long. It is easy to understand. Here, I will not give a specific introduction to each function. Here, I will mainly talk about several more important functions and design ideas:

/*Cycle, here is the main interface exposed to the main program, which is used for the circular work inside the ordinary barrage. In fact, the workflow is mainly composed of several steps:
** 1.Determine whether the global style has changed and maintain the accuracy of the global style
** 2.Judge the status of the current projectile curtain machine (such as suspension, operation, etc.) and carry out relevant operations
** 3.Updating the initial subscript of the for loop (startIndex) is mainly used for performance optimization
** 4.Calculate the state of each barrage
** 5.Drawing the Barrage Curtain
** 6.Evaluate the status of each barrage and reclaim it if it has been displayed.
** Basically, other functions are developed and perfected around these steps. It's good to understand the working principle of other functions.
** Understand, all for the completion of these workflows, and basically there are comments in the source code, I will not elaborate here.
*/
    update(w,h,time){

        let [items,cxt] = [this.save,this.cxt];

        this.globalChanged && this.initStyle(cxt); //Initialize global style

        !this.looped && this.countWidth(items); //Calculate text width and initialization location (once only)

        if( this.paused ) return false; //suspend

        this.refresh(items); //Update the initial subscript 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);
        }

    }

Another difficult thing to understand about ordinary barrage classes is the acquisition of "channels". The "channel" here refers to the line where the barrage runs from right to left. These channels are generated when the size of canvas changes. Different types of barrage have their channel set. When a new bullet screen needs to be displayed on canvas, it needs to get its assigned position, that is, channel, when the channel is occupied, the line will not reset the new bullet screen. When the channel has been allocated, a temporary channel will be generated randomly. The location of the temporary channel will appear randomly, and temporary passage will not be retracted from the channel set when it is released, but normal. Channels are retrieved into the collection for the next barrage call. Here is the code:

//Generate channel rows
    countRows(){

        //Save temporary variables
        let unitHeight = parseInt(this.globalSize) + this.space;
        let [rowNum , rows] = [
            ( ( this.height - 20 ) / unitHeight ) >> 0,
            this.rows
        ];

        //Reset channel
        for( let key of Object.keys(rows) ){
            rows[key] = [];
        }

        //Reproduction Channel
        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);
        }

        //Update instance attributes
        this.unitHeight = unitHeight;
        this.rowNum = rowNum;
    }



//Access Channel
    getRow(item){
        
        //If the barrage is on display, return to its existing channel
        if( item.row ) 
        return item.row;

        //Access to new channels
        const [rows,type] = [this.rows,item.type];
        const row = ( type != "bottom" ? rows[type].shift() : rows[type].pop() );
        //Generate temporary channels
        const tempRow = this["getRow_"+type]();

        if( row && item.type == "slide" ){
            item.x += ( row.idx * 8 );
            item.speed += ( row.idx / 3 );
        }

        //Return the allocated channel
        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
        };
    }

Advanced barrage classes are slightly different from ordinary barrage classes, but generally the same, the only thing to be concerned with is the calculation-related code, because it is not difficult, so do not continue to explain here, see the comments in the source code.

epilogue

As far as the second edition is concerned, the third edition has better performance and achieves decoupling between the player module and the bullet screen module. That is to say, compared with the second edition, the third edition can be applied to but not limited to the player, has higher usability, and achieves the sending of advanced bullet screen. In the future, it will gradually complete more functions and code reconstruction. I hope you will meet some BUG or some requirements. Private mail or submit feedback to this email address: 454236029@qq.com || z454236029@gmail.com. If you think this plug-in is useful to you, please give a star. Thank you.

Posted by mustang on Sat, 15 Jun 2019 14:54:31 -0700