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);
})