2024年前端最新Canvas + WebSocket实现视频弹幕,前端面试官

最后

由于篇幅限制,pdf文档的详解资料太全面,细节内容实在太多啦,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

上面的 value 和 time 是必填参数,其他的选填参数可以在前端设置默认值。

前端定义的假数据如下:

// 文件:index.js

let data = [

{

value: “这是第一条弹幕”,

speed: 2,

time: 0,

color: “red”,

fontSize: 20

},

{

value: “这是第二条弹幕”,

time: 1

}

];

实现前端弹幕的逻辑


我们希望是把弹幕封装成一个功能,只要有需要的地方就可以使用,从而实现复用,那么不同的地方使用这个功能通常的方式是 new 一个实例,传入当前使用该功能对应的参数,我们也使用这种方式来实现,所以我们需要封装一个统一的构造函数或者类,参数为当前的 canvas 元素、video 元素和一个 options 对象,options 里面的 data 属性为我们的弹幕数据,之所以不直接传入 data 是为了后续参数的扩展,严格遵循开放封闭原则,这里我们就统一使用 ES6 的 class 类来实现。

1、创建弹幕功能的类及基本参数处理

布局时需要注意 Canvas 的默认宽为 300px,高为 150px,我们要保证 Canvas 完全覆盖整个视频,需要让 Canvas 与 video 宽高相等。

因为我们不确定每一个使用该功能的视频的宽高都是一样的,所以 Canvas 画布的宽高并没有通过 CSS 来设置,而是通过 JS 在类创建实例初始化属性的时候动态设置。

// 文件:index.js

class CanvasBarrage {

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

// 如果没有传入 canvas 或者 video 直接跳出

if (!canvas || !video) return;

this.canvas = canvas; // 当前的 canvas 元素

this.video = video; // 当前的 video 元素

// 设置 canvas 与 video 等高

this.canvas.width = video.clientWidth;

this.canvas.height = video.clientHeight;

// 默认暂停播放,表示不渲染弹幕

this.isPaused = true;

// 没传参数的默认值

let defaultOptions = {

fontSize: 20,

color: “gold”,

speed: 2,

opacity: 0.3,

data: []

};

// 对象的合并,将默认参数对象的属性和传入对象的属性统一放到当前实例上

Object.assign(this, defaultOptions, options);

}

}

应该挂在实例上的属性除了有当前的 canvas 元素、video 元素、弹幕数据的默认选项以及弹幕数据之外,还应该有一个代表当前是否渲染弹幕的参数,因为视频暂停的时候,弹幕也是暂停的,所以没有重新渲染,因为是否暂停与弹幕是否渲染的状态是一致的,所以我们这里就用 isPaused 参数来代表当前是否暂停或重新渲染弹幕,值类型为布尔值。

2、创建构造每一条弹幕的类

我们知道,后台返回给我们的弹幕数据是一个数组,这个数组里的每一个弹幕都是一个对象,而对象上有着这条弹幕的信息,如果我们需要在每一个弹幕对象上再加一些新的信息或者在每一个弹幕对象的处理时用到了当前弹幕功能类 CanvasBarrage 实例的一些属性值,取值显然是不太方便的,这样为了后续方便扩展,遵循开放封闭原则,我们把每一个弹幕的对象转变成同一个类的实例,所以我们创建一个名为 Barrage 的类,让我们每一条弹幕的对象进入这个类里面走一遭,挂上一些扩展的属性。

// 文件:index.js

class Barrage {

constructor(item, ctx) {

this.value = item.value; // 弹幕的内容

this.time = item.time; // 弹幕出现的时间

this.item = item; // 每一个弹幕的数据对象

this.ctx = ctx; // 弹幕功能类的执行上下文

}

}

在我们的 CanvasBarrage 类上有一个存储弹幕数据的数组 data,此时我们需要给 CanvasBarrage 增加一个属性用来存放 “加工” 后的每条弹幕对应的实例。

// 文件:index.js

class CanvasBarrage {

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

// 如果没有传入 canvas 或者 video 直接跳出

if (!canvas || !video) return;

this.canvas = canvas; // 当前的 canvas 元素

this.video = video; // 当前的 video 元素

// 设置 canvas 与 video 等高

this.canvas.width = video.clientWidth;

this.canvas.height = video.clientHeight;

// 默认暂停播放,表示不渲染弹幕

this.isPaused = true;

// 没传参数的默认值

let defaultOptions = {

fontSize: 20,

color: “gold”,

speed: 2,

opacity: 0.3,

data: []

};

// 对象的合并,将默认参数对象的属性和传入对象的属性统一放到当前实例上

Object.assign(this, defaultOptions, options);

// ********** 以下为新增代码 **********

// 存放所有弹幕实例,Barrage 是创造每一条弹幕的实例的类

this.barrages = this.data.map(item => new Barrage(item, this));

// ********** 以上为新增代码 **********

}

}

其实通过上面操作以后,我们相当于把 data 里面的每一条弹幕对象转换成了一个 Barrage 类的一个实例,把当前的上下文 this 传入后可以随时在每一个弹幕实例上获取 CanvasBarrage 类实例的属性,也方便我们后续扩展方法,遵循这种开放封闭原则的方式开发,意义是不言而喻的。

3、在 CanvasBarrage 类实现渲染所有弹幕的 render 方法

CanvasBarrage 的 render 方法是在创建弹幕功能实例的时候应该渲染 Canvas 所以应该在 CanvasBarrage 中调用,在 render 内部,每一次渲染之前都应该先将 Canvas 画布清空,所以需要给当前的 CanvasBarrage 类新增一个属性用于存储 Canvas 画布的内容。

// 文件:index.js

class CanvasBarrage {

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

// 如果没有传入 canvas 或者 video 直接跳出

if (!canvas || !video) return;

this.canvas = canvas; // 当前的 canvas 元素

this.video = video; // 当前的 video 元素

// 设置 canvas 与 video 等高

this.canvas.width = video.clientWidth;

this.canvas.height = video.clientHeight;

// 默认暂停播放,表示不渲染弹幕

this.isPaused = true;

// 没传参数的默认值

let defaultOptions = {

fontSize: 20,

color: “gold”,

speed: 2,

opacity: 0.3,

data: []

};

// 对象的合并,将默认参数对象的属性和传入对象的属性统一放到当前实例上

Object.assign(this, defaultOptions, options);

// 存放所有弹幕实例,Barrage 是创造每一条弹幕的实例的类

this.barrages = this.data.map(item => new Barrage(item, this));

// ********** 以下为新增代码 **********

// Canvas 画布的内容

this.context = canvas.getContext(“2d”);

// 渲染所有的弹幕

this.render();

// ********** 以上为新增代码 **********

}

// ********** 以下为新增代码 **********

render() {

// 渲染整个弹幕

// 第一次先进行清空操作,执行渲染弹幕,如果没有暂停,继续渲染

this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);

// 渲染弹幕

this.renderBarrage();

if (this.isPaused == false) {

// 递归渲染

requestAnimationFrame(this.render.bind(this));

}

}

// ********** 以上为新增代码 **********

}

在上面的 CanvasBarrage 的 render 函数中,清空时由于 Canvas 性能比较好,所以将整个画布清空,所以从坐标 (0, 0) 点,清空的宽高为整个 Canvas 画布的宽高。

只要视频是在播放状态应该不断的调用 render 方法实现清空画布、渲染弹幕、判断是否暂停,如果非暂停状态继续渲染,所以我们用到了递归调用 render 去不断的实现渲染,但是递归时如果直接调用 render,性能特别差,程序甚至会挂掉,以往这种情况我们会在递归外层加一个 setTimeout 来定义一个短暂的递归时间,但是这个过程类似于动画效果,如果使用 setTimeout 其实是将同步代码转成了异步执行,会增加不确定性导致画面出现卡顿的现象。

这里我们使用 H5 的新 API requestAnimationFrame,可以在平均 1/60 S 内帮我执行一次该方法传入的回调,我们直接把 render 函数作为回调函数传入 requestAnimationFrame,该方法是按照帧的方式执行,动画流畅,需要注意的是,render 函数内使用了 this,所以应该处理一下 this 指向问题。

由于我们使用面向对象的方式,所以渲染弹幕的具体细节,我们抽离出一个单独的方法 renderBarrage,接下来看一下 renderBarrage 的实现。

4、CanvasBarrage 类 render 内部 renderBarrage 的实现

// 文件:index.js

class CanvasBarrage {

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

// 如果没有传入 canvas 或者 video 直接跳出

if (!canvas || !video) return;

this.canvas = canvas; // 当前的 canvas 元素

this.video = video; // 当前的 video 元素

// 设置 canvas 与 video 等高

this.canvas.width = video.clientWidth;

this.canvas.height = video.clientHeight;

// 默认暂停播放,表示不渲染弹幕

this.isPaused = true;

// 没传参数的默认值

let defaultOptions = {

fontSize: 20,

color: “gold”,

speed: 2,

opacity: 0.3,

data: []

};

// 对象的合并,将默认参数对象的属性和传入对象的属性统一放到当前实例上

Object.assign(this, defaultOptions, options);

// 存放所有弹幕实例,Barrage 是创造每一条弹幕的实例的类

this.barrages = this.data.map(item => new Barrage(item, this));

// Canvas 画布的内容

this.context = canvas.getContext(“2d”);

// 渲染所有的弹幕

this.render();

}

render() {

// 渲染整个弹幕

// 第一次先进行清空操作,执行渲染弹幕,如果没有暂停,继续渲染

this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);

// 渲染弹幕

this.renderBarrage();

if (this.isPaused == false) {

// 递归渲染

requestAnimationFrame(this.render.bind(this));

}

}

// ********** 以下为新增代码 **********

renderBarrage() {

// 将数组的弹幕一个一个取出,判断时间和视频的时间是否符合,符合就执行渲染此弹幕

let time = this.video.currentTime;

this.barrages.forEach(barrage => {

// 当视频时间大于等于了弹幕设置的时间,那么开始渲染(时间都是以秒为单位)

if (time >= barrage.time) {

// 初始化弹幕的各个参数,只有在弹幕将要出现的时候再去初始化,节省性能,初始化后再进行绘制

// 如果没有初始化,先去初始化一下

if (!barrage.isInited) {

// 初始化后下次再渲染就不需要再初始化了,所以创建一个标识 isInited

barrage.init();

barrage.isInited = true;

}

}

});

}

// ********** 以上为新增代码 **********

}

此处的 renderBarrage 方法内部主要对每一条弹幕实例所设置的出现时间和视频的播放时间做对比,如果视频的播放时间大于等于了弹幕出现的时间,说明弹幕需要绘制在 Canvas 画布内。

之前我们的每一条弹幕实例的属性可能不全,弹幕的其他未传参数并没有初始化,所以为了最大限度的节省性能,我们在弹幕该第一次绘制的时候去初始化参数,等到视频播放的时间变化再去重新绘制时,不再初始化参数,所以初始化参数的方法放在了判断弹幕出现时间的条件里面执行,又设置了代表弹幕实例是不是初始化了的参数 isInited,初始化函数 init 执行过一次后,马上修改 isInited 的值,保证只初始化参数一次。

在 renderBarrage 方法中我们可以看出来,其实我们是循环了专门存放每一条弹幕实例(Barrage 类的实例)的数组,我们在内部用实例去调用的方法 init 应该是在 Barrage 类的原型上,下面我们去 Barrage 类上实现 init 的逻辑。

5、Barrage 类 init 的实现

// 文件:index.js

class Barrage {

constructor(item, ctx) {

this.value = item.value; // 弹幕的内容

this.time = item.time; // 弹幕出现的时间

this.item = item; // 每一个弹幕的数据对象

this.ctx = ctx; // 弹幕功能类的执行上下文

}

// ********** 以下为新增代码 **********

init() {

this.opacity = this.item.opacity || this.ctx.opacity;

this.color = this.item.color || this.ctx.color;

this.fontSize = this.item.fontSize || this.ctx.fontSize;

this.speed = this.item.speed || this.ctx.speed;

// 求自己的宽度,目的是用来校验当前是否还要继续绘制(边界判断)

let span = document.createElement(“span”);

// 能决定宽度的只有弹幕的内容和文字的大小,和字体,字体默认为微软雅黑,我们就不做设置了

span.innerText = this.value;

span.style.font = this.fontSize + ‘px "Microsoft YaHei’;

// span 为行内元素,取不到宽度,所以我们通过定位给转换成块级元素

span.style.position = “absolute”;

document.body.appendChild(span); // 放入页面

this.width = span.clientWidth; // 记录弹幕的宽度

document.body.removeChild(span); // 从页面移除

// 存储弹幕出现的横纵坐标

this.x = this.ctx.canvas.width;

this.y = this.ctx.canvas.height;

// 处理弹幕纵向溢出的边界处理

if (this.y < this.fontSize) {

this.y = this.fontSize;

}

if (this.y > this.ctx.canvas.height - this.fontSize) {

this.y = this.ctx.canvas.height - this.fontSize;

}

}

// ********** 以上为新增代码 **********

}

在上面代码的 init 方法中我们其实可以看出,每条弹幕实例初始化的时候初始的信息除了之前说的弹幕的基本参数外,还获取了每条弹幕的宽度(用于后续做弹幕是否已经完全移出屏幕的边界判断)和每一条弹幕的 x 和 y 轴方向的坐标并为了防止弹幕在 y 轴显示不全做了边界处理。

6、实现每条弹幕的渲染和弹幕移除屏幕的处理

我们当时在 CanvasBarrage 类的 render 方法中的渲染每个弹幕的方法 renderBarrage中(原谅这么啰嗦,因为到现在内容已经比较多,说的具体一点方便知道是哪个步骤,哈哈)只做了对每一条弹幕实例的初始化操作,并没有渲染在 Canvas 画布中,这时我们主要做两部操作,实现每条弹幕渲染在画布中和左侧移出屏幕不再渲染的边界处理。

// 文件:index.js

class CanvasBarrage {

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

// 如果没有传入 canvas 或者 video 直接跳出

if (!canvas || !video) return;

this.canvas = canvas; // 当前的 canvas 元素

this.video = video; // 当前的 video 元素

// 设置 canvas 与 video 等高

this.canvas.width = video.clientWidth;

this.canvas.height = video.clientHeight;

// 默认暂停播放,表示不渲染弹幕

this.isPaused = true;

// 没传参数的默认值

let defaultOptions = {

fontSize: 20,

color: “gold”,

speed: 2,

opacity: 0.3,

data: []

};

// 对象的合并,将默认参数对象的属性和传入对象的属性统一放到当前实例上

Object.assign(this, defaultOptions, options);

// 存放所有弹幕实例,Barrage 是创造每一条弹幕的实例的类

this.barrages = this.data.map(item => new Barrage(item, this));

// Canvas 画布的内容

this.context = canvas.getContext(“2d”);

// 渲染所有的弹幕

this.render();

}

render() {

// 渲染整个弹幕

// 第一次先进行清空操作,执行渲染弹幕,如果没有暂停,继续渲染

this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);

// 渲染弹幕

this.renderBarrage();

if (this.isPaused == false) {

// 递归渲染

requestAnimationFrame(this.render.bind(this));

}

}

renderBarrage() {

// 将数组的弹幕一个一个取出,判断时间和视频的时间是否符合,符合就执行渲染此弹幕

let time = this.video.currentTime;

this.barrages.forEach(barrage => {

// ********** 以下为改动的代码 **********

// 当视频时间大于等于了弹幕设置的时间,那么开始渲染(时间都是以秒为单位)

if (!barrage.flag && time >= barrage.time) {

// ********** 以上为改动的代码 **********

// 初始化弹幕的各个参数,只有在弹幕将要出现的时候再去初始化,节省性能,初始化后再进行绘制

// 如果没有初始化,先去初始化一下

if (!barrage.isInited) {

// 初始化后下次再渲染就不需要再初始化了,所以创建一个标识 isInited

barrage.init();

barrage.isInited = true;

}

// ********** 以下为新增代码 **********

barrage.x -= barrage.speed;

barrage.render(); // 渲染该条弹幕

if (barrage.x < barrage.width * -1) {

barrage.flag = true; // 是否出去了,出去了,做停止渲染的操作

}

// ********** 以上为新增代码 **********

}

});

}

}

每个弹幕实例都有一个 speed 属性,该属性代表着弹幕移动的速度,换个说法其实就是每次减少的 x 轴的差值,所以我们其实是通过改变 x 轴的值再重新渲染而实现弹幕的左移,我们创建了一个标识 flag 挂在每个弹幕实例下,代表是否已经离开屏幕,如果离开则更改 flag 的值,使外层的 CanvasBarrage 类的 render 函数再次递归时不进入渲染程序。

每一条弹幕具体是怎么渲染的,通过代码可以看出每个弹幕实例在 x 坐标改变后都调用了实例方法 render 函数,注意此 render 非彼 render,该 render 函数属于 Barrage 类,目的是为了渲染每一条弹幕,而 CanvasBarrage 类下的 render,是为了在视频时间变化时清空并重新渲染整个 Canvas 画布。

7、Barrage 类下的 render 方法的实现

// 文件:index.js

class Barrage {

constructor(item, ctx) {

this.value = item.value; // 弹幕的内容

this.time = item.time; // 弹幕出现的时间

this.item = item; // 每一个弹幕的数据对象

this.ctx = ctx; // 弹幕功能类的执行上下文

}

init() {

this.opacity = this.item.opacity || this.ctx.opacity;

this.color = this.item.color || this.ctx.color;

this.fontSize = this.item.fontSize || this.ctx.fontSize;

this.speed = this.item.speed || this.ctx.speed;

// 求自己的宽度,目的是用来校验当前是否还要继续绘制(边界判断)

let span = document.createElement(“span”);

// 能决定宽度的只有弹幕的内容和文字的大小,和字体,字体默认为微软雅黑,我们就不做设置了

span.innerText = this.value;

span.style.font = this.fontSize + ‘px "Microsoft YaHei’;

// span 为行内元素,取不到宽度,所以我们通过定位给转换成块级元素

span.style.position = “absolute”;

document.body.appendChild(span); // 放入页面

this.width = span.clientWidth; // 记录弹幕的宽度

document.body.removeChild(span); // 从页面移除

// 存储弹幕出现的横纵坐标

this.x = this.ctx.canvas.width;

this.y = this.ctx.canvas.height;

// 处理弹幕纵向溢出的边界处理

if (this.y < this.fontSize) {

this.y = this.fontSize;

}

if (this.y > this.ctx.canvas.height - this.fontSize) {

this.y = this.ctx.canvas.height - this.fontSize;

}

}

// ********** 以下为新增代码 **********

render() {

this.ctx.context.font = this.fontSize + ‘px “Microsoft YaHei”’;

this.ctx.context.fillStyle = this.color;

this.ctx.context.fillText(this.value, this.x, this.y);

}

// ********** 以上为新增代码 **********

}

从上面新增代码我们可以看出,其实 Barrage 类的 render 方法只是将每一条弹幕的字号、颜色、内容、坐标等属性通过 Canvas 的 API 添加到了画布上。

8、实现播放、暂停事件

还记得我们的 CanvasBarrage 类里面有一个属性 isPaused,属性值控制了我们是否递归渲染,这个属性与视频暂停的状态是一致的,我们在播放的时候,弹幕不断的清空并重新绘制,当暂停的时候弹幕也应该跟着暂停,说白了就是不在调用 CanvasBarrage 类的 render 方法,其实就是在暂停、播放的过程中不断的改变 isPaused 的值即可。

还记得我们之前构造的两条假数据 data 吧,接下来我们添加播放、暂停事件,来尝试使用一下我们的弹幕功能。

// 文件:index.js

// 实现一个简易选择器,方便获取元素,后面获取元素直接调用 $

const $ = document.querySelector.bind(document);

// 获取 Canvas 元素和 Video 元素

let canvas = $(“#canvas”);

let video = $(“#video”);

let canvasBarrage = new CanvasBarrage(canvas, video, {

data

});

// 添加播放事件

video.addEventListener(“play”, function() {

canvasBarrage.isPaused = false;

canvasBarrage.render();

});

// 添加暂停事件

video.addEventListener(“pause”, function() {

canvasBarrage.isPaused = true;

});

9、实现发送弹幕事件

// 文件:index.js

$(“#add”).addEventListener(“click”, function() {

let time = video.currentTime; // 发送弹幕的时间

let value = $(“#text”).value; // 发送弹幕的文字

let color = $(“#color”).value; // 发送弹幕文字的颜色

let fontSize = $(“#range”).value; // 发送弹幕的字体大小

let sendObj = { time, value, color, fontSize }; //发送弹幕的参数集合

canvasBarrage.add(sendObj); // 发送弹幕的方法

});

其实我们发送弹幕时,就是向 CanvasBarrage 类的 barrages 数组里添加了一条弹幕的实例,我们单独封装了一个 add 的实例方法。

// 文件:index.js

class CanvasBarrage {

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

// 如果没有传入 canvas 或者 video 直接跳出

if (!canvas || !video) return;

this.canvas = canvas; // 当前的 canvas 元素

this.video = video; // 当前的 video 元素

// 设置 canvas 与 video 等高

this.canvas.width = video.clientWidth;

this.canvas.height = video.clientHeight;

// 默认暂停播放,表示不渲染弹幕

this.isPaused = true;

// 没传参数的默认值

let defaultOptions = {

fontSize: 20,

color: “gold”,

speed: 2,

opacity: 0.3,

data: []

};

// 对象的合并,将默认参数对象的属性和传入对象的属性统一放到当前实例上

Object.assign(this, defaultOptions, options);

// 存放所有弹幕实例,Barrage 是创造每一条弹幕的实例的类

this.barrages = this.data.map(item => new Barrage(item, this));

// Canvas 画布的内容

this.context = canvas.getContext(“2d”);

// 渲染所有的弹幕

this.render();

}

render() {

// 渲染整个弹幕

// 第一次先进行清空操作,执行渲染弹幕,如果没有暂停,继续渲染

this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);

// 渲染弹幕

this.renderBarrage();

if (this.isPaused == false) {

// 递归渲染

requestAnimationFrame(this.render.bind(this));

}

}

renderBarrage() {

// 将数组的弹幕一个一个取出,判断时间和视频的时间是否符合,符合就执行渲染此弹幕

let time = this.video.currentTime;

this.barrages.forEach(barrage => {

// 当视频时间大于等于了弹幕设置的时间,那么开始渲染(时间都是以秒为单位)

if (!barrage.flag && time >= barrage.time) {

// 初始化弹幕的各个参数,只有在弹幕将要出现的时候再去初始化,节省性能,初始化后再进行绘制

// 如果没有初始化,先去初始化一下

if (!barrage.isInited) {

// 初始化后下次再渲染就不需要再初始化了,所以创建一个标识 isInited

barrage.init();

barrage.isInited = true;

}

barrage.x -= barrage.speed;

barrage.render(); // 渲染该条弹幕

if (barrage.x < barrage.width * -1) {

barrage.flag = true; // 是否出去了,出去了,做停止渲染的操作

}

}

});

}

// ********** 以下为新增代码 **********

add(item) {

this.barrages.push(new Barrage(item, this));

}

// ********** 以上为新增代码 **********

}

10、拖动进度条实现弹幕的前进和后退

其实我们发现,弹幕虽然实现了正常的播放、暂停以及发送,但是当我们拖动进度条的时候弹幕应该是跟着视频时间同步播放的,现在的弹幕一旦播放过无论怎样拉动进度条弹幕都不会再出现,我们现在就来解决这个问题。

// 文件:index.js

最后

一个好的心态和一个坚持的心很重要,很多冲着高薪的人想学习前端,但是能学到最后的没有几个,遇到困难就放弃了,这种人到处都是,就是因为有的东西难,所以他的回报才很大,我们评判一个前端开发者是什么水平,就是他解决问题的能力有多强。

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

分享一些前端面试题以及学习路线给大家

ideo.currentTime;

this.barrages.forEach(barrage => {

// 当视频时间大于等于了弹幕设置的时间,那么开始渲染(时间都是以秒为单位)

if (!barrage.flag && time >= barrage.time) {

// 初始化弹幕的各个参数,只有在弹幕将要出现的时候再去初始化,节省性能,初始化后再进行绘制

// 如果没有初始化,先去初始化一下

if (!barrage.isInited) {

// 初始化后下次再渲染就不需要再初始化了,所以创建一个标识 isInited

barrage.init();

barrage.isInited = true;

}

barrage.x -= barrage.speed;

barrage.render(); // 渲染该条弹幕

if (barrage.x < barrage.width * -1) {

barrage.flag = true; // 是否出去了,出去了,做停止渲染的操作

}

}

});

}

// ********** 以下为新增代码 **********

add(item) {

this.barrages.push(new Barrage(item, this));

}

// ********** 以上为新增代码 **********

}

10、拖动进度条实现弹幕的前进和后退

其实我们发现,弹幕虽然实现了正常的播放、暂停以及发送,但是当我们拖动进度条的时候弹幕应该是跟着视频时间同步播放的,现在的弹幕一旦播放过无论怎样拉动进度条弹幕都不会再出现,我们现在就来解决这个问题。

// 文件:index.js

最后

一个好的心态和一个坚持的心很重要,很多冲着高薪的人想学习前端,但是能学到最后的没有几个,遇到困难就放弃了,这种人到处都是,就是因为有的东西难,所以他的回报才很大,我们评判一个前端开发者是什么水平,就是他解决问题的能力有多强。

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

分享一些前端面试题以及学习路线给大家

[外链图片转存中…(img-NU976k3t-1715006491400)]

[外链图片转存中…(img-LWX4lIOE-1715006491401)]

  • 20
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
WebSocket技术可以用来实现实时弹幕功能。以下是一个简单的WebSocket弹幕示例: 1. 前端代码 ```html <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Websocket弹幕</title> <style> #barrage { position: absolute; top: 0; left: 0; width: 100%; height: 100%; overflow: hidden; pointer-events: none; } .barrage-item { position: absolute; top: 0; white-space: nowrap; font-size: 20px; color: white; text-shadow: 1px 1px 2px black; } </style> </head> <body> <canvas id="barrage"></canvas> <script> const canvas = document.getElementById('barrage'); const ctx = canvas.getContext('2d'); const width = canvas.width = window.innerWidth; const height = canvas.height = window.innerHeight; let barrageList = []; // 连接websocket服务器 const ws = new WebSocket('ws://localhost:8080'); ws.onmessage = (event) => { const data = JSON.parse(event.data); barrageList.push({ text: data.text, x: width, y: Math.random() * height, color: data.color }); }; function draw() { ctx.clearRect(0, 0, width, height); for (let i = 0; i < barrageList.length; i++) { const item = barrageList[i]; ctx.fillStyle = item.color; ctx.fillText(item.text, item.x, item.y); item.x -= 3; } barrageList = barrageList.filter(item => item.x > -ctx.measureText(item.text).width); requestAnimationFrame(draw); } draw(); </script> </body> </html> ``` 2. 后端代码 ```javascript const WebSocket = require('ws'); const wss = new WebSocket.Server({ port: 8080 }); wss.on('connection', (ws) => { console.log('connected'); ws.on('message', (message) => { console.log(`received: ${message}`); wss.clients.forEach(client => { if (client.readyState === WebSocket.OPEN) { client.send(message); } }); }); }); ``` 3. 使用方法 用户在前端页面上输入文本和颜色,点击发送按钮后,将文本和颜色发送给后端WebSocket服务器,服务器接收到消息后将消息广播给所有连接的客户端,客户端接收到消息后将消息添加到弹幕列表中,并在画布上绘制弹幕。 这是一个最基本的弹幕实现,实际中还需要考虑弹幕的速度、字体大小、弹幕屏蔽等问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值