canvas实现视频弹幕
老样子,先看效果图,再进行愉悦的学习~
什么是canvas
?
canvas
(画布)可以使用Javascript
在网页中绘制图形图像。基本结构:
<canvas id="" width="" height=""></canvas>
width
:定义画布的宽度
height
:定义画布的高度
canvas 的基本使用
基本绘制步骤如下:
let cvs = document.getElementById('canvas');
// 获取用于绘制2维图像的context对象
let ctx = cvs.getContext('2d');
// 调用相关API,绘制图像
ctx.fillStyle = 'red'
ctx.fillRect(x, y, width, height);
canvas
元素的方法:
let ctx = cvs.getContext(type);
type
上下文类型:
2d
返回CanvasRenderingContext2D
。用于画二维图像。
webgl
返回WebGLRederingContext
。用于渲染三维图像。(three.js
)
canvas
基础绘图API
填充
ctx.fillStyle = 'red' // 设置填充颜色
ctx.fillRect(x,y,width,height); // 填充矩形
描边
ctx.strokeStyle = 'blue' // 设置描边的颜色
ctx.strokeRect(x,y,width,height); // 描边矩形
绘制文本
ctx.font = '25px 微软雅黑' // 设置字体
ctx.fillText('文本内容', x, y); // 填充文本
ctx.strokeText('文本内容', x, y); // 描边文本
Canvas
路径绘图API
路径(Path
)是将预先设定好的坐标点按照顺序连接起来所形成的图形。
路径的绘制步骤:
- 通过
ctx.beginPath()
开启一条新的路径。- 通过
ctx.moveTo(x, y)
将画笔移动到指定位置。- 通过
ctx
的相关方法开始绘制路径(经典方法:lineTo(x, y)
)。- 最后通过
ctx.stroke()
、ctx.fill()
方法对路径进行描边或填充。
了解完基础,让我们一起瞅瞅视频弹幕的实现
Canvas
动画
动画的本质即:每隔一段时间(非常快 1/60
秒[每秒60帧])重绘画布。只要能保证60帧,由于视觉残留现象,就会出现动画效果。
window.setInterval(()=>{
重绘UI
}, 1000/60)
案例:实现弹幕。
在
video
标签之上蒙一层canvas
。添加
input
与button
,实现发送弹幕功能。不仅需要发送弹幕,还需要让弹幕动起来。但是不能每个弹幕都启动一个定时器,因为太消耗资源。一个定时器管理所有弹幕的动画足矣。这样设计的话就不能再点击发送按钮时启动定时器,而是页面加载后,就可以启动唯一的定时器了。定时器所做的事情是将所有弹幕的位置一起更新,一起绘制。
这就意味着,发送弹幕操作,其实只是向弹幕数组中添加一个弹幕对象即可,不需要关心绘制的事情。
- 准备一个存储弹幕对象的数组:
dmlist = {}
。- 当发送弹幕时,把弹幕内容、x、y封装成对象,存入
dmlist
。- 页面加载完毕后,启动一个定时器,不断(每秒60帧)执行回调方法,每次执行回调方法时加载
dmlist
,读取每个弹幕信息,更新弹幕的x
坐标,重新绘制canvas
中的所有弹幕内容。
具体实现步骤
1.在把canvas的画布定位到video视频标签上,html与css 样式如下
<div>
<video id="video"
src="https://huazizhanye.oss-cn-beijing.aliyuncs.com/catmovie.mp4"
width="640" height="360"
style="background: black;">
</video>
<canvas id="canvas" width="640" height="360"></canvas>
<input id="range" type="range" min="0" value="0" max="100">
<div class="time">
<span class="left">00:00</span>
<span class="right">00:00</span>
</div>
<button id="btn_play">播放/暂停</button>
<button id="btn_vp">音量+</button>
<button id="btn_vm">音量-</button>
<button id="btn_05">0.5倍速</button>
<button id="btn_1">1倍速</button>
<button id="btn_2">2倍速</button>
<br><br>
<button id="btn_fc">全屏显示</button>
<input id='input' type="text" placeholder="填写弹幕内容">
<button id="send">发送弹幕</button>
</div>
.container {
width: 400px;
border: 1px solid #ddd;
text-align: center;
padding-bottom: 10px;
}
.container p{
font-size: 1.3em;
font-weight: bold;
text-align: center;
}
.container img{
width: 340px;
height: 340px;
border-radius: 50%;
}
.container input{
width: 340px;
display: block;
margin: 10px auto;
}
.container .time{
width: 340px;
height: 30px;
margin: 0px auto;
}
.container .time .left{
float: left;
}
.container .time .right{
float: right;
}
canvas{
width: 640px; height: 360px; position: absolute;
top: 0; left: 0;
}
.container{ position: relative; }
随便添加点按钮后~看图说话
– 点击发送按钮之后,将弹幕出现的位置随机化,并且每一条的信息push 到声明的数组中
// 发送弹幕
send.addEventListener('click', ()=>{
let content = input.value; // 文本框的内容
// 封装为dm对象,存入dmlist
dmlist.push({
content: content,
x: 600,
y: (Math.floor(Math.random() * 12)+1) * 30
});
console.log(dmlist);
})
– 实现核心定时器,每次发送新弹幕时候先清除定时器,否则定时器会累加导致卡 !。然后遍历数组中push 进来的数据,绘制到页面上
// 启动一个定时器 每秒执行60次,重绘UI
window.setInterval(()=>{
// 把canvas中的像素全部清除 重新绘制
ctx.clearRect(0, 0, 640, 360);
// 遍历dmlist,在canvas中进行绘制
dmlist.forEach(item=>{ //item: 弹幕对象
ctx.fillStyle = "white";
ctx.font = '20px 微软雅黑';
item.x--;
ctx.fillText(item.content, item.x, item.y);
})
}, 1000/60);
完整代码如下,喜欢记得收藏
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>video_player.html</title>
<style>
.container {
width: 400px;
border: 1px solid #ddd;
text-align: center;
padding-bottom: 10px;
}
.container p{
font-size: 1.3em;
font-weight: bold;
text-align: center;
}
.container img{
width: 340px;
height: 340px;
border-radius: 50%;
}
.container input{
width: 340px;
display: block;
margin: 10px auto;
}
.container .time{
width: 340px;
height: 30px;
margin: 0px auto;
}
.container .time .left{
float: left;
}
.container .time .right{
float: right;
}
canvas{
width: 640px; height: 360px; position: absolute;
top: 0; left: 0;
}
.container{ position: relative; }
</style>
</head>
<body>
<div class="container">
<video id="video"
src="https://huazizhanye.oss-cn-beijing.aliyuncs.com/catmovie.mp4"
width="640" height="360"
style="background: black;">
</video>
<canvas id="canvas" width="640" height="360"></canvas>
<input id="range" type="range" min="0" value="0" max="100">
<div class="time">
<span class="left">00:00</span>
<span class="right">00:00</span>
</div>
<button id="btn_play">播放/暂停</button>
<button id="btn_vp">音量+</button>
<button id="btn_vm">音量-</button>
<button id="btn_05">0.5倍速</button>
<button id="btn_1">1倍速</button>
<button id="btn_2">2倍速</button>
<br><br>
<button id="btn_fc">全屏显示</button>
<input id='input' type="text" placeholder="填写弹幕内容">
<button id="send">发送弹幕</button>
</div>
<script src="assets/moment.js"></script>
<script>
let dmlist = []; // 存储弹幕对象
let cvs = document.getElementById('canvas');
let ctx = cvs.getContext('2d');
// function step(){
// ctx.clearRect(0, 0, 640, 360);
// // 遍历dmlist,在canvas中进行绘制
// dmlist.forEach(item=>{ //item: 弹幕对象
// ctx.fillStyle = "white";
// ctx.font = '20px 微软雅黑';
// item.x--;
// ctx.fillText(item.content, item.x, item.y);
// })
// // 通知下次绘制UI时,再次执行step
// window.requestAnimationFrame(step);
// }
// window.requestAnimationFrame(step);
// 启动一个定时器 每秒执行60次,重绘UI
window.setInterval(()=>{
// 把canvas中的像素全部清除 重新绘制
ctx.clearRect(0, 0, 640, 360);
// 遍历dmlist,在canvas中进行绘制
dmlist.forEach(item=>{ //item: 弹幕对象
ctx.fillStyle = "white";
ctx.font = '20px 微软雅黑';
item.x--;
ctx.fillText(item.content, item.x, item.y);
})
}, 1000/60);
// 发送弹幕
send.addEventListener('click', ()=>{
let content = input.value; // 文本框的内容
// 封装为dm对象,存入dmlist
dmlist.push({
content: content,
x: 600,
y: (Math.floor(Math.random() * 12)+1) * 30
});
console.log(dmlist);
})
// 创建一个音频播放器
let player = document.getElementById('video');
btn_fc.addEventListener('click', ()=>{
// player.requestFullscreen(); // 全屏显示dom元素
btn_fc.requestFullscreen();
})
// 当拖拽进度条后,从拖拽结束位置继播放
range.addEventListener('change', ()=>{
player.currentTime = range.value;
})
// 资源加载完毕后更新总时长
player.addEventListener('loadedmetadata', ()=>{
let right=document.getElementsByClassName('right')[0];
let tt=moment.unix(player.duration).format('mm:ss');
right.innerHTML= tt;
})
// 处理一下进度条
// 在音乐播放过程中捕获持续触发的timeupdate事件
player.addEventListener('timeupdate', ()=>{
console.log('timeupdated...');
// 设置range的max value min
range.max = player.duration; // 总时长
range.value = player.currentTime; // 当前时长
// 处理时间文本内容 使用momentjs转换字符串
let left=document.getElementsByClassName('left')[0];
let right=document.getElementsByClassName('right')[0];
let tt=moment.unix(player.duration).format('mm:ss');
let ct=moment.unix(player.currentTime).format('mm:ss');
left.innerHTML = ct;
right.innerHTML= tt;
});
// 设置倍速播放
btn_05.addEventListener('click', ()=>{
player.playbackRate = 0.5;
})
btn_1.addEventListener('click', ()=>{
player.playbackRate = 1;
})
btn_2.addEventListener('click', ()=>{
player.playbackRate = 2;
})
// 修改音量
btn_vp.addEventListener('click', ()=>{
player.volume = Math.min(1, player.volume+0.1);
console.log(player.volume);
})
btn_vm.addEventListener('click', ()=>{
player.volume = Math.max(0, player.volume-0.1);
console.log(player.volume);
})
// 直接访问btn_play,就可以找到id=btn_play的dom对象
btn_play.addEventListener('click', ()=>{
if(player.paused){
player.play();
}else{
player.pause();
}
})
</script>
</body>
</html>