弹幕
在完成视频弹幕案例的过程中运用了JavaScript中的类和canvas标签,首先对它们进行了解后再进行实战。
类
在ES6之前,JavaScript中没有类的概念,而是通过使用构造函数和原型链模拟类的行为。然而在ES6中,引入了类的语法。
类的用法
例1:
function Person(name, age) {
this.name = name
this.age = age
}
Person.eat = function () {
console.log('I like food')
}
Person.prototype.sex= 'girl'
Person.prototype.addAge = function () {
this.age++
}
Person.prototype.say = function () {
console.log('hello');
}
let person = new Person('张三', 18)
通过创建一个类达到和例1代码所具有一样效果,这样可以快速了解类的用法。
-
定义一个
Person类。其中constructor是类的一个特殊方法,用于初始化新创建的实例。在类中定义方法不需要function关键字。class Person(){ constructor(name, age) {//构造函数,会自动被调用 this.name = name this.age = age } } -
为类本身创建静态方法需要使用
static关键字。static eat(){ console.log('I like food') } -
在类中达到例1中在构造函数的原型上面添加一个属性的效果,可以使用
get关键字实现。get sex() {//函数名当作属性名 return 'girl' } -
在类中达到例1中在构造函数的原型上面添加一个
addAge方法的效果,可以使用set关键字实现。但是使用set关键字后,效果会有些不一样。addAge会成为实例对象的属性使用,并且必须要有参数。set addAge(val) {//函数名当作属性名 this.age = val } -
在类中到达例1中在构造函数的原型上面添加一个方法的效果,可以直接在类中添加一个方法来实现。
say() { console.log('hello'); } -
这两种方法创建实例对象的方法是一样的,都是通过
new Person()创建。let person = new Person('张三', 18)
综上所述Person类的代码如下:
class Person {
constructor(name, age) {
this.name = name
this.age = age
}
static eat() {
console.log('I like food');
}
get sex() {//函数名当作属性名
return 'girl'
}
set addAge(val) {//函数名当作属性名
this.age = val
}
say() {
console.log('hello');
}
}
let person = new Person('张三', 18)
在类中还可以通过#定义私有变量,这样的变量只能在类中使用。
class Person{
#count = 1
}
canvas
传送门--拿捏canvas,从实现环形进度条与随机验证码开始 - 掘金 (juejin.cn)
在这篇文章里介绍了一些常用的canvas用法。如果看一遍没了解,那就再看一遍。
实战
html部分
<div class="wrap">
<h1>周杰伦--听妈妈的话</h1>
<div class="main">
<canvas id="canvas" style="position: absolute">></canvas>
<video id="video" src="./mv.mp4" controls width="720" height="480"></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>
效果如图:
- 通过给canvas标签添加绝对定位的样式,让其脱离文档流,这样才能使通过canvas标签绘制的弹幕可以漂浮在视频上。
- 设置一个文本输入框接收要发送的弹幕。
- 设置一个按钮,当点击按钮时发送弹幕。
- 设置一个颜色选择输入框,用于选择设置要发送的弹幕颜色。
- 设置一个滑动输入框,用于选择设置要发送的弹幕字体大小。其中字体大小最小值为20,最大值为40。
JavaScript部分
//定义弹幕的数据结构,每一条弹幕可以包含内容、开始时间、弹幕颜色、移动速度和弹幕字体大小。
let data = [
{ value: '周杰伦的听妈妈的话我听了10年', time: 5, color: 'red', speed: 1, fontSize: 22 },
{ value: '快快长大才,能保护她', time: 10, color: '#00a1f5', speed: 1, fontSize: 30 },
{ value: '听妈妈的话晚点再恋爱吧', time: 6 },
{ value: '别让她受伤', time: 20, color: '#fff', speed: 1, fontSize: 30 },
]
//获取页面中的元素
let canvas = document.getElementById('canvas')//获取弹幕画布元素
let ctx = canvas.getContext('2d')//获取绘图的2D渲染上下文
let video = document.getElementById('video')//获取视频元素
let $text = document.getElementById('text')//获取输入弹幕的文本框元素
let $color = document.getElementById('color')//获取颜色选择输入框元素
let $range = document.getElementById('range')//获取滑动输入框元素
let $btn = document.getElementById('btn')//获取发送弹幕按钮元素
//弹幕绘制的准备工作
class CanvasBarrage {
constructor(canvas, video, opts = {}) {
if (!canvas || !video) return//如果创建实例对象时没有传入canvas和video参数就会直接返回
this.canvas = canvas
this.video = video
//初始化canvas的尺寸,使其于video的尺寸匹配
this.canvas.width = video.width
this.canvas.height = video.height
this.ctx = canvas.getContext('2d')
//设置弹幕的默认值
let defOpts = {
color: '#e91e63',//默认颜色
speed: 1, //默认弹幕移动速度
opacity: 0.5,//默认弹幕透明度
fontSize: 20,//默认弹幕字体大小
data: []//默认弹幕数据
}
Object.assign(this, defOpts, opts)//合并弹幕的默认值和用户传入的参数
this.isPaused = true//默认的暂停状态为true
this.barrages = this.data.map(item => new Barrage(item, this))//创建Barrage实例集合
this.render()//开始渲染
}
//渲染函数,通过requestAnimationFrame实现动画
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 (time >= barrage.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))
}
}
//弹幕类
class Barrage {
constructor(obj, context) {
this.value = obj.value//弹幕内容
this.time = obj.time//弹幕出现时间
this.obj = obj//原始数据对象
this.context = context//引用CanvasBarrage实例
}
//初始化弹幕的属性
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//弹幕字体大小
//计算每一条弹幕的宽度。通过创建一个p标签,然后将弹幕文本放入p标签中然后获取宽度然后赋值给this.width,然后移除p标签。
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.fillStyle = this.color
this.context.ctx.font = `${this.fontSize}px Arial`
this.context.ctx.fillText(this.value, this.x, this.y)
}
}
//实例化CanvasBarrage,并处理视频播放事件和发送弹幕按钮的点击事件
let canvasBarrage = new CanvasBarrage(canvas, video, { data: data })
video.addEventListener('play', function () {
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)
})
代码看起来比较复杂,如果一遍没看懂就多看几遍。
大致思路流程:
- 创建了一个
data数组,里面存储的每一个元素都是一条弹幕的基本信息。 - 定义
CanvasBarrage类处理弹幕相关的操作:- 在构造函数
constructor中,将画布的大小设置为视频的大小;合并默认弹幕信息和用户自定义的弹幕信息,使弹幕信息保持完整;数据中的每条信息转换为Barrage对象并存入barrages数组。 - 定义一个
clear方法用于清空canvas,确保每次绘制前画布是干净的 - 定义
render方法用于绘制弹幕,其中包括清除画布、根据视频当前时间绘制未出界的弹幕,并通过递归保持不断绘制,直到视频停止时结束递归。 - 定义
renderBarrages方法遍历每一条弹幕,检查其是否到达出现时间,然后更新弹幕的位置并调用render方法绘制弹幕。如果弹幕移出了画布,则标记为已经结束。 - 定义
add函数添加新的弹幕到渲染列表里。
- 在构造函数
- 定义
Barrage类处理每条弹幕的具体操作:- 通过构造函数接受弹幕信息和上下文。
- 封装
init方法初始化弹幕的颜色、速度、字体大小和透明度,并且通过利用p标签计算弹幕的宽度;设置弹幕的起始位置。 render方法负责在画布上渲染弹幕。
- 当用户播放视频时,弹幕开始渲染,并且当用户发送弹幕时动态添加新的弹幕。