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 = {