html5 canvas+video实时弹幕&添加弹幕功能

2021-03-24  本文已影响0人  Peter_2B
没有做全屏的实时弹幕

html:

<div id="container">
    <div id="content">
        <canvas id="canvas"></canvas>
        <video id="video" src="./x.mp4" width="640" height="380" controls></video>
    </div>

    <input type="text" id="text"> <button id="add">添加弹幕</button>
    <input type="color" id="color"> 
    <input type="range" id="range" max="40" min="20">
</div>

css:

<style>
    #container{
        text-align: center;
    }
    #content{
        width: 640px;
        margin: auto;
        position: relative;
    }
    #canvas{
        position: absolute;
    }
    input{
        vertical-align: middle;
    }
</style>

js:

var canvas = document.getElementById('canvas');
var video = document.getElementById('video');
var inp = document.getElementById('text');
var add = document.getElementById('add');
var coLor = document.getElementById('color');
var range = document.getElementById('range');

class CanvasBarrage{
        constructor(canvas, video, options={}){

                if(!canvas || !video)return;

                this.video = video;
                this.isPaused = true;

                this.canvas = canvas;
                this.ctx = canvas.getContext('2d');     //获取画笔
                
                this.canvas.width = video.clientWidth;  //js设置canvas同video元素等高;
                this.canvas.height = video.clientHeight;

                let defaultOptions = {
                    fontSize: 20, color:'#000', speed: 2, opacity: 0.9,
                    getData:[]
                };
                
                //合并对象: 1参是合并到的目标对象,后面的都是来源对象,合并到this实例对象中;
                        //合并对象到实例对象中; option={},没有传就是为空,合并的就是defaultOptions
                Object.assign(this, defaultOptions, options);   
                
                //存放所有弹幕
                this.barrages = this.getData.map(currentObj=>new Barrage(currentObj,this) );

                console.log(this);
                this.render(); 
        }

        render = ()=>{      //渲染画布
                            //第一次先进行清空操作:( x,y, width,height)
            this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
            this.renderBarrage();//渲染所有弹幕

            if(this.isPaused === false){
                //递归渲染                  //回调函数的this必须用bind,否者指向window;
                requestAnimationFrame( this.render.bind(this) ); //<--必须传一个回调
                //这里用requestAni函数比用setInterval还渲染流畅很多
            }
        }

        renderBarrage = ()=>{
            //取出每个弹幕,判断时间和视频的事件是否符合,符合就执行渲染此弹幕
            let time = this.video.currentTime;
            this.barrages.forEach(currentBarrage=>{

                if(!currentBarrage.flag && time >= currentBarrage.time){    //当视频播放时间等于或大于当前弹幕时间
                   
                        if(!currentBarrage.isInited){       //初始化,再进行绘制
                            currentBarrage.init();
                        }

                        currentBarrage.x -= currentBarrage.speed;
                        currentBarrage.currentRender();     //渲染此条弹幕

                        if(currentBarrage.x <= currentBarrage.width*-1){
                            currentBarrage.flag = true;     //当此条弹幕的x位置小于等于弹幕宽度
                        };
                };
            });
        }

        addBarrage = (obj)=>{   
            this.barrages.push( new Barrage(obj, this) );
        }

        reset(){
            this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);

            let time = this.video.currentTime;
            this.barrages.forEach(currentBarrage=>{

                currentBarrage.flag = false;

                if(time <= currentBarrage.time){
                    currentBarrage.isInited = false;    //重新初始化
                }else{
                    currentBarrage.flag = true;         //其他项目部渲染
                };
            })
        }
}

class Barrage{

        constructor(currentObj, contextObj){
            
            this.value = currentObj.value;
            this.time = currentObj.time;    //value & time 是弹幕必传值
            this.contextObj = contextObj;
            this.isInited = false;
            this.flag = false;
                                            //如果没有传opacity就取defaultOptions的opacity;
            this.opacity = currentObj.opacity || this.contextObj.opacity;
            this.color = currentObj.color || this.contextObj.color;
            this.speed = currentObj.speed || this.contextObj.speed;
            this.fontSize = currentObj.fontSize || this.contextObj.fontSize;
        }

        init = ()=>{  //初始化此条弹幕: 宽高,位置;
            
                            //求此条弹幕的宽度,目的是用来检验当前是否还需要继续绘制
            let span = document.createElement('span');
                span.innerText = this.value;
                span.style.fontSize = this.fontSize +'px "Microsoft Yahei" ';
                span.style.position = 'absolute';
            document.body.appendChild(span);

            this.width = span.clientWidth;
            this.height = span.clientHeight;  //span元素高度就是fontSize高度。span没有padding,剩下内容就只有fontSize高度支撑。 
            document.body.removeChild(span);  //获得此条弹幕高&宽,再从页面中删除。

            this.x = this.contextObj.canvas.width;  //此条弹幕出现在画布的x,y位置
            this.y = this.contextObj.canvas.height * Math.random(); //随机高度

            if(this.y < this.fontSize){
                this.y = this.fontSize;
            }
            if(this.y > this.contextObj.canvas.height - this.fontSize){
                this.y = this.contextObj.canvas.height - this.fontSize;
            }

            this.isInited = true;
        }

        currentRender = ()=>{ //渲染此条弹幕,画在画布上
            this.contextObj.ctx.font = this.fontSize + 'px "Microsoft Yahei" ';
            this.contextObj.ctx.fillStyle = this.color;
            this.contextObj.ctx.fillText(this.value, this.x, this.y);
        }
}

var getData = [
    //value & time 是必传值
    {value:'爷青回',speed:2,time:1,color:'#000',fontSize:20, opacity:0.8},
    {value:'爷的青春回来了',time:2},
];

let ccc = new CanvasBarrage(canvas, video, {getData});
video.addEventListener('play',function(){
    ccc.isPaused = false;
    ccc.render();
});
video.addEventListener('pause',function(){
    ccc.isPaused = true;
});
video.addEventListener('seeked',function(){  //拖动滚动条事件
    ccc.reset();
});
add.addEventListener('click',function(){
    let value = inp.value;
    let time = video.currentTime;
    let color = coLor.value;
    let fontSize = range.value;
    let obj = {value,time,color,fontSize};
    ccc.addBarrage(obj); 
})
上一篇下一篇

猜你喜欢

热点阅读