前言
今年小编喜欢上了在bilibili上看游戏直播,也不是因为有多爱打游戏,主要是up有些帅气,当看到观众们在直播中发送五彩斑斓的弹幕时,我也跃跃欲试,加入了发弹幕的行列。然而,我很快发现,我的普通弹幕在屏幕上迅速划过,而一些观众的弹幕却以其独特的颜色和字体脱颖而出。
经过一番探索,我了解到要获得这种更加丰富的弹幕效果,需要加入up主的粉丝团并提升等级,这也就打开了小编的花点小钱打赏之路。当然作为一个技术人员,我也对发送弹幕背后的技术实现产生了兴趣,所以接下我将讲解如何给实现发送弹幕功能。
原理
发送弹幕通常在视频播放器中应用,它的基本原理是利用 HTML5 的 Canvas 技术,在视频播放的同时,在视频上方绘制一个透明的画布(Canvas),然后在该画布上绘制弹幕文本,并通过动画效果实现文本的移动、显示和隐藏。
下面是发送弹幕使用 Canvas 的基本原理:
-
创建 Canvas 元素:首先,在视频播放器上方创建一个透明的 Canvas 元素,并设置其使其位于视频上方,这样就可以在视频上方绘制弹幕。
-
绘制弹幕文本:当用户发送弹幕时,将用户输入的文本绘制到 Canvas 上。可以通过 JavaScript 获取用户输入的弹幕文本,并使用 Canvas API 在 Canvas 上绘制文本。可以设置文本的样式、颜色、字体等。
-
动画效果:绘制文本后,通过动画效果实现弹幕的移动。通常情况下,弹幕会从屏幕右侧(或其他指定位置)出现,然后向左移动至屏幕边缘,最终消失。可以使用 JavaScript 的定时器或 requestAnimationFrame() 函数来实现动画效果,不断地更新文本的位置,从而实现文本的移动。
-
控制弹幕显示时间:可以设置每条弹幕的显示时间,一般情况下,弹幕显示一段时间后会自动隐藏。可以在发送弹幕时记录发送时间,并在绘制弹幕时检查当前时间,若超过了显示时间,则停止绘制该弹幕。
界面
为了更好的讲解我们先写一个简单的界面!
html和css代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
* {
padding: 0;
margin: 0;
}
.wrap {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
/* 让wrap占据整个视口的高度 */
}
#canvas {
position: absolute;
}
.main {
text-align: center;
}
.content {
margin: 20px;
text-align: center;
}
#text {
padding: 10px;
margin-right: 10px;
border: 2px solid #ccc;
border-radius: 5px;
font-size: 16px;
}
#btn {
padding: 10px 20px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
}
#color {
margin-left: 10px;
vertical-align: middle;
}
#range {
margin-left: 10px;
}
</style>
</head>
<body>
<div class="wrap">
<h1>蒲猫猫直播录屏</h1>
<div class="main">
<canvas id="canvas"></canvas>
<video src="./mv.MP4" controls width="720" height="480" id="video"></video>
</div>
<div class="content">
<input type="text" id="text">
<input type="button" id="btn" value="发弹幕">
<input type="color" id="color">
<input type="range" id="range" min="20" max="40">
</div>
</div>
<script src="./index.js"></script>
</body>
</html>
效果:
JS逻辑
1.数据定义
let data = [
{
value: '我生于南方,长在北方',
time: 5,
color: 'blue',
speed: 1,
fontSize: 30
},
// 其他弹幕数据
];
这里我们自定义了一些弹幕数据,包括弹幕内容、出现时间、颜色、速度和字体大小等信息。
2. 获取页面元素
let canvas = document.getElementById('canvas');
let video = document.getElementById('video');
let $text = document.getElementById('text');
let $btn = document.getElementById('btn');
let $color = document.getElementById('color');
let $range = document.getElementById('range');
我们通过getElementById方法获取了页面上对应的canvas元素、video元素、文本输入框、发送按钮、颜色选择器和字体大小调节器。
3.创建弹幕绘制类
class CanvasBarrage {
constructor(canvas, video, opts = {}) {
if (!canvas || !video) {
throw new Error('canvas and video is required');
}
this.video = video;
this.canvas = canvas;
this.canvas.width = video.width;
this.canvas.height = video.height;
// 创建画布上下文
this.ctx = canvas.getContext('2d');
// 弹幕默认值
let defOpts = {
color: '#e91e63',
speed: 1,
fontSize: 20,
opacity: 0.5,
data: []
};
// 合并默认值和传入的选项
Object.assign(this, defOpts, opts);
this.isPaused = true;
// 初始化所有弹幕
this.barrages = this.data.map(item => new Barrage(item, this));
this.render();
}
render() {
// 清除画布
this.clear();
// 渲染弹幕
this.renderBarrages();
// 如果视频未暂停,继续渲染下一帧
if (!this.isPaused) {
requestAnimationFrame(this.render.bind(this))
}
}
clear() {
// 清除画布上的内容
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
}
renderBarrages() {
// 获取视频当前播放时间
let time = this.video.currentTime;
this.barrages.forEach(barrage => {
// 判断弹幕是否应该出现
if (barrage.time <= time && !barrage.flag) {
if (!barrage.isInit) {
barrage.init();
barrage.isInit = true;
}
// 更新弹幕位置
barrage.x -= barrage.speed;
barrage.render();
// 如果弹幕移出画布,将其标记为已处理
if (barrage.x < -barrage.width) {
barrage.flag = true;
}
}
});
}
add(obj) {
// 添加新的弹幕
this.barrages.push(new Barrage(obj, this));
}
}
- 构造函数
constructor(canvas, video, opts = {})
-
初始化画布和视频元素,设置画布尺寸,创建画布上下文。
-
定义弹幕的默认属性(颜色、速度、字体大小、不透明度、数据),并将传入的选项与默认值合并。
-
将传入的数据初始化为
Barrage
实例,并开始渲染。
渲染方法 render()
-
清除画布内容。
-
渲染所有弹幕。
-
通过递归调用
requestAnimationFrame
实现动画效果,除非视频暂停。
清除方法 clear()
- 清除画布上的所有内容,以便重新绘制。
渲染弹幕方法 renderBarrages()
-
获取视频的当前播放时间。
-
遍历所有弹幕,判断是否应该显示,并更新其位置和渲染。
-
如果弹幕移出画布,标记为已处理。
添加弹幕方法 add(obj)
- 接收新的弹幕对象,并将其转换为
Barrage
实例添加到barrages
数组中。
4.创建弹幕类
class Barrage {
constructor(obj, context) {
this.value = obj.value; // 弹幕的文本内容
this.time = obj.time; // 弹幕出现的时间
this.obj = obj;
this.context = context; // CanvasBarrage 实例的引用||存储上下文对象
this.flag = false; // 标记弹幕是否已经移出屏幕
}
init() {
this.color = this.obj.color || this.context.color; // 设置弹幕颜色
this.speed = this.obj.speed || this.context.speed; // 设置弹幕速度
this.opacity = this.obj.opacity || this.context.opacity; // 设置弹幕不透明度
this.fontSize = this.obj.fontSize || this.context.fontSize; // 设置弹幕字体大小
// 计算每一条弹幕的宽度
let p = document.createElement('p');
p.style.fontSize = this.fontSize + 'px';
p.innerHTML = this.value;
document.body.appendChild(p);
this.width = p.clientWidth;
document.body.removeChild(p);
// 设置弹幕初始位置
this.x = this.context.canvas.width;
this.y = this.context.canvas.height * Math.random();
// 限制弹幕的横纵向显示范围
if (this.y < this.fontSize) {
this.y = this.fontSize;
} else if (this.y > this.context.canvas.height - this.fontSize) {
this.y = this.context.canvas.height - this.fontSize;
}
}
render() {
this.context.ctx.font = `${this.fontSize}px Arial`; // 设置字体
this.context.ctx.fillStyle = this.color; // 设置颜色
this.context.ctx.fillText(this.value, this.x, this.y); // 绘制文本
}
}
构造函数 constructor(obj, context)
- 初始化弹幕的基本属性,包括内容、出现时间、引用的上下文(
CanvasBarrage
实例)和标记是否已经移出屏幕。
初始化方法 init()
-
设置弹幕的颜色、速度、不透明度和字体大小,优先使用传入的值,如果没有则使用默认值。
-
通过创建一个临时的 DOM 元素来计算弹幕文本的宽度。
-
设置弹幕的初始位置(在画布右边缘,垂直位置随机但不超出画布范围)。
渲染方法 render()
- 设置画布的字体和颜色,然后在画布上绘制弹幕的文本内容。
5.事件监听与交互
//初始化CanvasBarrage实例
let canvasBarrage = new CanvasBarrage(canvas, video, { data });
video.addEventListener('play', () => {
canvasBarrage.isPaused = false;// 视频播放时取消暂停状态
canvasBarrage.render();// 视频播放时取消暂停状态
})
$btn.addEventListener('click', () => {
let value = $text.value;
let time = video.currentTime;
let color = $color.value;
let fontSize = $range.value;
let obj = { value, time, color, fontSize };// 创建一个新的弹幕对象
canvasBarrage.add(obj);// 创建一个新的弹幕对象
$text.value = '';
})
这部分代码添加了视频播放事件监听和发送弹幕的点击事件监听。
当视频播放时,启动弹幕渲染;点击发送按钮时,获取输入的弹幕内容、当前视频时间以及颜色和字体大小等信息,并添加到弹幕列表中。
最后
结果截图
这只是一个简单的demo,主要让大家理解这样一个原理和逻辑,各种细节还需要我们去考量修改!
(文章中用的视频因为是录屏,所以也会录到当时的一些弹幕,有些干扰,多多包涵)