HTML使用canvas实现弹幕功能

简介

最近在做大作业的时候需要做一个弹幕播放器。借鉴了一下别人的源码自己重新实现了一个,演示如下

主要的功能有

  • 发送弹幕
  • 设置弹幕的颜色,速度和类型
  • 显示弹幕

已知缺陷:

  • 不能全屏
  • canvas没有做自适应
  • 没有自定义播放器控件
  • 没有根据播放时间显示相应的弹幕
  • 弹幕不能实现悬停

已知的缺陷以后会进行改进。网上能找到的弹幕播放器的源码一般只做了滚动的弹幕而没有做静止的弹幕,这里我特意加上了静止弹幕的实现。

Canvas绘制文字以及文字滚动效果

整个播放器的核心就是绘制文字以及做文字滚动的动画,canvas中对于文字并没有很好的动画支持,只能通过自己实现,实现的思路就是不断的清屏然后重写文字,当清屏重写的频率达到24fps的时候就是流畅的动画了。

先在HTML文件中添加视频video标签和画布canvas标签

<div id="barrageplayer">
<canvas id="cv_video" width="900px" height="450px"></canvas>
<video id="v_video" src="test.MP4" controls type="video/mp4"></video>
</div>

canvas标签的位置样式设置为position:absolute然后视频和画布就重叠在一起,看起来就是一个弹幕播放器了。然后为画布添加弹幕相关的内容,首先获取画布的相关信息和设置画布的字体大小和字体样式

var c=document.getElementById("cv_video");
//获取画布大小
var c_height=c.height;
var c_width=c.width;

//获取画布
ctx=c.getContext("2d");

//设置字体样式
ctx.font="25px DengXian";

画布信息已经获取和设置,巧妇难为无米之炊,接着我们就要构造弹幕对象,使用的构造模式是动态原型模式

//弹幕对象
function Barrage(content,color,type,speed){
this.content=content;
this.color=color;
this.type=type;
this.speed=speed;
if(this.type=="default"){
this.height=parseInt(Math.random()*c_height)+10;
}else if (this.type=="static top"){
this.height=parseInt((c_height/2)-Math.random()*c_height/2)+10;
}else if (this.type=="static bottom"){
this.height=parseInt((c_height/2)+Math.random()*c_height/2)+10;
}
if(typeof this.move!="function"){
Barrage.prototype.move=function(){
if(this.type=="default"){
this.left=this.left-this.speed;
}
}
}
}

构造的弹幕对象初始化了各种参数,包括内容,颜色,运动类型和速度,定义了move()方法来控制弹幕的缓动,每出发一次move()方法向左滚动一个单位speed的像素。
弹幕对象构造完成之后就进入到主题,动画的制作,直接上代码

//循环擦写画布实现动画效果
setInterval(function(){
ctx.clearRect(0,0,c_width,c_height);
ctx.save();
for(var i=0;i<msgs.length;i++){
if(msgs[i]!=null){
if(msgs[i].type=="default"){
handleDefault(msgs[i]);
}else{
handleStatic(msgs[i]);
}
}
}
},20)

每20ms执行一次擦写,ctx.clearRect(0,0,c_width,c_height);是将整张当前的画布清除,然后使用ctx.save()将当前的画布保存,接着遍历弹幕列表(msgs是弹幕列表,当每发送一条弹幕都会将该弹幕实例添加到列表中),然后按照默认样式的弹幕还是静止样式的弹幕分别处理。如果是默认样式的弹幕将会按照以下的方法处理

//处理默认弹幕样式
function handleDefault(barrage){
if(barrage.left==undefined||barrage.left==null){
barrage.left=c.width;
}else{
if(barrage.left<-200){
barrage=null;
}else{
barrage.move()
ctx.fillStyle=barrage.color;
ctx.fillText(barrage.content,barrage.left,barrage.height)
ctx.restore();
}
}
}

首先如果弹幕实例没有设置left属性则将画布的宽度赋予它,如果弹幕实例已经退出画布则将其置null以节省内存,否则的话就调用弹幕实例的move()方法改变left属性的值,然后设置文字的颜色,一级写入新的文字,恢复画布。这样就完成了一帧动画。

对于静止弹幕的实现方法如下

//处理静止弹幕样式
function handleStatic(barrage){
ctx.moveTo(c_width/2,barrage.height);
ctx.textAlign="center";
ctx.fillStyle=barrage.color;
ctx.fillText(barrage.content,c_width/2,barrage.height);
if(barrage.left==undefined||barrage.left==null){
barrage.left=c.width;
}else{
if(barrage.left<-200){
ctx.fillText("",c_width/2,barrage.height);
barrage=null;
//ctx.restore();
ctx.clearRect(0,0,c_width,c_height);
}else{
barrage.left=barrage.left-6;
}
}
}

首先将画布的基点移动到画布的中心,需要注意的是这时候相对与生成了一张新的画布,原来画布的clearRect()方法已经不适用与这张画布了。然后再设置文字对齐为居中对齐,设置文字样式,填充文字。因为弹幕是静止的所以不需要进行缓动,但是静止弹幕也是会消失的,需要设置一个标志位来使他定时消失。在这里为了不占用额外的属性,我们直接使用left属性作为标志位,同样进行left属性的递减,但不把递减反映到画布中,当left达到阈值,则使用ctx.clearRect()方法将弹幕清除。这样就实现了静止弹幕的处理。

其他关于颜色,样式的设置有一定基础的人应该是很容易掌握的在这里就不多介绍了,自己看可运行代码部分理解一下就好。

总结

这个项目主要是使用了canvas进行文字绘制以及实现文字的缓动动画,主要用到的方法有

  • canvasDom.getContext()
  • canvas.save()/canvas.restore()
  • canvas.clearRect()
  • canvas.moveTo()

原来我对与save()restore()是不能理解的,现在我算是有一点理解了,当你更改了画布状态,现在的画布就已经不是原来的画布,所以在修改画布状态之前先把画布状态保存,切换画布状态,完成工作之后,恢复为原来的画布状态继续工作。像我处理静态弹幕的时候,把画布的基点改变了,那么原来画布的清除方法就不再适用于当前画布,只有在当前画布中自己使用另外的清除方法。然后再恢复到原来的画布。

可运行代码

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<style type="text/css">
.pickdiv{
width: 30px;
height: 30px;
cursor: pointer;
border: 2px solid gray;
display: inline-block;
}
#white{
background: white;
}
#red{
background:#ff6666;
}
#yellow{
background:#ffff00;
}
#blue{
background:#333399;
}
#green{
background:#339933;
}
#cv_video{
position: absolute;
z-index: 1;
}
#barrageplayer{
position: relative;
display: block;
width: 900px;
height: 500px;
}
#v_video{
position: absolute;
width: 100%;
height: 100%;
z-index: 0;
}
</style>


<body>
<div id="barrageplayer">
<canvas id="cv_video" width="900px" height="450px"></canvas>
<video id="v_video" src="test.MP4" controls type="video/mp4"></video>
</div>
<div id="barrageinput">
<div>
<input type="text" id="smsg" placeholder="请输入弹幕内容"/>
<button id="send"> 发送</button>
</div>
<div id="colorpick">
<div class="pickdiv" id="white"></div>
<div class="pickdiv" id="red"></div>
<div class="pickdiv" id="yellow"></div>
<div class="pickdiv" id="blue"></div>
<div class="pickdiv" id="green"></div>
</div>
<div id="typepick">
<input type="radio" name="type" value="default">默认
<input type="radio" name="type" value="static top">静止顶部
<input type="radio" name="type" value="static bottom">静止底部
</div>
<div id="speedpick">
<input type="radio" name="speed" value="1">1X
<input type="radio" name="speed" value="2">2X
<input type="radio" name="speed" value="3">3X
</div>
<div id="stylepick"></div>
</div>

<script>
var c=document.getElementById("cv_video");

var typeDom=document.getElementsByName("type");
var speedDom=document.getElementsByName("speed");
var colorpick=document.getElementById("colorpick");
var smsg=document.getElementById("smsg");

var color="#white";
var speed=1;
var type="default";
var msgs=[];

//获取画布大小
var c_height=c.height;
var c_width=c.width;

//获取画布
ctx=c.getContext("2d");
ctx.font="25px DengXian";

//处理颜色选择
colorpick.addEventListener('click',function(event){
switch(event.target.id){
case "white":
color="white";
break;
case "red":
color="#ff6666";
break;
case "yellow":
color="#ffff00";
break;
case "green":
color="#339933";
break;
case "blue":
color="#333399";
break;
}
})

//处理发送弹幕
document.getElementById("send").onclick=function(){
var text=smsg.value;
for(var i=0;i<typeDom.length;i++){
if(typeDom[i].checked){
type=typeDom[i].value;
break;
}
}
for(var i=0;i<speedDom.length;i++){
if(speedDom[i].checked){
speed=2*parseInt(speedDom[i].value);
break;
}
}
var tempBarrage=new Barrage(text,color,type,speed);
msgs.push(tempBarrage);
}


//
//弹幕功能部分代码
//
//弹幕对象
function Barrage(content,color,type,speed){
this.content=content;
this.color=color;
this.type=type;
this.speed=speed;

if(this.type=="default"){
this.height=parseInt(Math.random()*c_height)+10;
}else if (this.type=="static top"){
this.height=parseInt((c_height/2)-Math.random()*c_height/2)+10;
}else if (this.type=="static bottom"){
this.height=parseInt((c_height/2)+Math.random()*c_height/2)+10;
}
if(typeof this.move!="function"){
Barrage.prototype.move=function(){
if(this.type=="default"){
this.left=this.left-this.speed;
}
}
}
}

//循环擦写画布实现动画效果
setInterval(function(){
ctx.clearRect(0,0,c_width,c_height);
ctx.save();
for(var i=0;i<msgs.length;i++){
if(msgs[i]!=null){
if(msgs[i].type=="default"){
handleDefault(msgs[i]);
}else{
handleStatic(msgs[i]);
}
}
}
},20)

//处理默认弹幕样式
function handleDefault(barrage){
if(barrage.left==undefined||barrage.left==null){
barrage.left=c.width;
}else{
if(barrage.left<-200){
barrage=null;
}else{
barrage.move()
ctx.fillStyle=barrage.color;
ctx.fillText(barrage.content,barrage.left,barrage.height)
ctx.restore();
}
}
}

//处理静止弹幕样式
function handleStatic(barrage){
ctx.moveTo(c_width/2,barrage.height);
ctx.textAlign="center";
ctx.fillStyle=barrage.color;
ctx.fillText(barrage.content,c_width/2,barrage.height);
if(barrage.left==undefined||barrage.left==null){
barrage.left=c.width;
}else{
if(barrage.left<-200){
ctx.fillText("",c_width/2,barrage.height);
barrage=null;
//ctx.restore();
ctx.clearRect(0,0,c_width,c_height);
}else{
barrage.left=barrage.left-6;
}
}
}
</script>
</body>
</html>

更多相关文章

  1. 动画在画布中移动图像
  2. 检查并删除重复的坐标x,y画布
  3. 如何更改我的按钮文字?
  4. 获取错误“ValueError:int()的无效文字,基数为10:'3128;'在运行Tensor
  5. Python 用hashlib求中文字符串的MD5值
  6. python的list要打印中文字符
  7. python1.返回一个字符串中出现次数第二多的单词 2.字符串中可能
  8. 心中的完美的E680I[文字]
  9. sqlserver2008r2查找非中文字母数字出现的第一个位置

随机推荐

  1. Android(安卓)java.lang.NoClassDefFound
  2. 11、从头学Android之Android布局管理:Line
  3. ListView 使用技巧
  4. Android(安卓)WiFi Direct文件传输
  5. Intent(意图)
  6. 转:关于android中图片裁剪以及PorterDuffX
  7. Android应用程序组件Content Provider应
  8. Android原生(Native)C开发之三 鼠标事件
  9. Android防止崩溃的库,有效的降低Crash率
  10. Android高手进阶教程(十)之----Android(