如果让你设计一个弹幕组件,你会怎么做???
大家好,我是前端小张同学,这次给大家更新一个开发中常见的需求,接下来我会将我的弹幕实现以及设计思路一步一步描述出来,并分享给大家,希望大家喜欢。
今天我们的主题
是 ,用vue手写一个弹幕
1:关于弹幕设计思想
1.1 : 业务层 | 视图层(全局组件)
1.1.1 : 从业务角度来说,如果你设计的是全局弹幕组件,你要考虑以下几点。
- 容器的高度?
- 容器层次结构划分?
- 渲染弹幕的方式,使用组件的人应该传递什么数据?
- 是否支持全屏弹幕?
- 是否支持弹幕关闭和开启?
- 是否需要重置弹幕?
- 是否支持暂停弹幕?
- 是否需要集成发送功能?
设计方案考虑完整了以后,你将可以开始考虑 数据层
的设计
1.2 数据层
1.2.1 : 从数据角度来说每一条弹幕无非是一个element
,然后把弹幕内容放到这个element
元素中,并且给 element
添加动画,那接下来,你应该这样考虑。
弹幕是JS对象?它的属性有哪些?
谁去管理这些弹幕?如何让他能够支持暂停和关闭?
你如何把后台的数据,与你前台的一些静态数据进行合并,创造出一个完整对象?
你怎么去渲染这些弹幕?
你想要几秒创建一次弹幕并在容器内显示和运行?
弹幕具备哪些灵活的属性?
运行动画时间 , 用户自己发布的弹幕样式定制?
又或者,弹幕需要几条弹道内运行等等这些你都需要考虑。
数据设计方案考虑完整了以后,你将可以开始考虑 数据管理层
的设计
1.3 数据管理层
1.3.1 从管理的角度来说,外界调用某些方法,你即可快速的响应操作,例如外界调用 open
方法,你就播放弹幕,调用Stop
方法,你就关闭弹幕 接下来,你应该考虑以下几点。
- 面向对象设计,应该提供哪些方法,具备哪些功能?
- 调用了指定的方法,应该怎么对数据进行操作。
- 如何对弹幕做性能优化?
到这里 , 我们设计方案基本完成,接下来我们可以开始编写代码。
2: 代码实现
2.1 : 数据层设计方案实现
我们需要构建一个 Barrage
类 ,我们每次去创建一个弹幕的时候都会 new Barrage
,让他帮助我们生成一些弹幕属性。
export class Barrage {
constructor(obj) {
// 每次 new Barrage() 传入一个 后台返回的数据对象 obj
const { barrageId, speed, level, top, jumpUrl, barrageContent, animationPlayState, ...args } = obj
this.barrageId = barrageId; // id : 每条弹幕的唯一id
this.speed = speed; // speed : 弹幕运行的速度,由外界控制
this.level = level; // level : 弹幕的层级 --> 弹幕可分为设计可分为 上下 1 , 1 两个层级 ,可决定弹幕的显示是全屏还是半屏显示
this.top = top; // top :弹幕生成的位置相对于 level 的层级 决定 ,相对于 Level 层级 盒子距离顶部的位置
this.jumpUrl = jumpUrl; // jumpUrl :点击弹幕需要跳转的链接
this.barrageContent = barrageContent; // barrageContent : 弹幕的内容
this.animationPlayState = ''; // 设计弹幕 是否可 点击暂停功能
this.color = '#FFF' // 弹幕颜色
this.args = args // 除去Barrage类之外的一些数据属性全部丢到这里,例如后台返回的数据
}
}
2.1 : 数据管理层设计方案实现
2.1.1 :我们在这里实现了 , 弹幕的 增加
,删除
,初始化
,重置
,关闭
, 开启
功能
1. 实现弹幕开启功能.
BarrageManager.js
export class BarrageManager {
constructor(barrageVue) {
this.barrages = []; // 填弹幕的数组
this.barragesIds = [] // 批量删除弹幕的数组id
this.sourceBarrages = [] // 源弹幕数据
this.timer = null //控制弹幕的开启和关
this.barrageVue = barrageVue // 弹幕组件实例
this.deleteCount = 0, // 销毁弹幕的总数
this.lastDeleteCount = 0, // 最后可销毁的数量
this.row = 0,
this.count = 0
}
init(barrages) {
this.sourceBarrages = barrages
this.deleteCount = parseInt(this.sourceBarrages.length / deleteQuantity.FIFTY) // 计算可删除数量
this.lastDeleteCount = this.sourceBarrages.length % deleteQuantity.FIFTY // 计算 最后一次可删除数量
}
/**
*
* @param {*} barrages 接收一个弹幕数组数据
* @description 循环创建 弹幕对象 ,将后台数据与 创建弹幕的属性结合 存入弹幕数组
*/
loopCreateBarrage(barrages) {
const { rows, createTime, crearteBarrageObject } = this.barrageVue
let maxRows = rows / 2 // 最大的弹幕行数
this.timer = setInterval(() => {
for (let i = 0; i < 1; i++) {
let barrageItem = barrages[this.count]
if (this.row >= maxRows) { this.row = 0 } // 如果当前已经到了 最大的弹幕行数临界点则 回到第0 行弹道继续 创建
if (!barrageItem) return clearInterval(this.timer) // 如果取不到了则证明没数据了 , 结束弹幕展示
const item = crearteBarrageObject({ row: this.row, ...barrageItem }) // 添加对象到 弹幕数组中
this.addBarrage(item)
this.count++ // 用于取值 ,取了多少条
this.row++ // 用于弹道
}
}, createTime * 1000);
}
/**
* @param {*} barrages 传入一个弹幕数组数据
* @returns 无返回值
* @description 调用 该方法 开始播放弹幕
*/
open(barrages) {
if (barrages.length === 0) return
this.init(barrages)
this.loopCreateBarrage(this.sourceBarrages)
}
}
在这里我们初始化了一个 open 方法,并接收一个数组 ,并调用了 init
方法 去做初始化操作,并调用了 循环创建的方法,没 createTime 秒创建一条弹幕,加入到弹幕数组中。
- 连接视图层
2.1 : 视图层 | 业务层设计方案实现
index.vue
<template>
<div class="barrage">
<div class="barrage-container" ref="barrageContainer">
<div class="barrage-half-screen" ref="halfScreenContainer">
<template v-for="item in barrageFiltering.level1">
<barrage-item
:item="item" :class="{pausedAnimation : paused }"
:options='barrageTypeCallback(item)'
@destory="destoryBraageItem" :key="item.barrageId">
</barrage-item>
</template>
</div>
<div class="barrage-full-screen" v-if="fullScreen">
<template v-for="item in barrageFiltering.level2">
<barrage-item
:item="item" :class="{pausedAnimation : paused }"
:options='barrageTypeCallback(item)'
@destory="destoryBraageItem" :key="item.barrageId">
</barrage-item>
</template>
</div>
</div>
<user-input ref="publishBarrage" v-if="openPublishBarrage" @onBlur="handleBlur">
<template #user-operatio-right>
<!-- 处理兼容性问题 ios 和 安卓 触发点击事件 -->
<div class="send" @click="sendBarrage($event)" v-if="IOS">
<slot name="rightRegion"></slot>
</div>
<div class="send" @mousedown="sendBarrage($event)" v-else>
<slot name="rightRegion"></slot>
</div>
</template>
</user-input>
</div>
</template>
export default {
created () {
this.barrageManager = new BarrageManager(this)
},
mounted() {
// 初始化弹幕渲染数据
this.initBarrageRenderData();
},
data() {
return {
barrageManager : null,
isClickSend: false,
paused : false
};
},
methods : {
initBarrageRenderData() {
this.barrageManager.open(this.barrages);
},
},
computed : {
barrageFiltering() {
return {
level1:
this.barrageManager.barrages.filter(
item => item.level === barrageLevel.LEVEL1
) || [],
level2:
this.barrageManager.barrages.filter(
item => item.level === barrageLevel.LEVEL2
) || []
};
},
}
}
视图层知识点回顾
在这里我们在弹幕组件创建的时候去创建了一个 弹幕管理对象
,并且在挂载的时候去初始化了以下 弹幕渲染的数据,于是我们调用了 弹幕管理类
的 open
方法,这样当组件挂载时,就会去渲染 barrageFiltering
数据,这里我们是在管理类中拿到了管理类中
循环创建的数据。
open 方法实现
到这里我们的弹幕的开启基本上已经完成了,可以看得出,如果你是这样设计的,你只需要在组件中调用管理类的一些方法,它就能帮你完成一些功能。
3: 实现弹幕关闭功能
barrageManager.js
class BarrageManager {
constructor(barrageVue) {
this.barrages = []; // 填弹幕的数组
this.barragesIds = [] // 批量删除弹幕的数组id
this.sourceBarrages = [] // 源弹幕数据
this.timer = null //控制弹幕的开启和关
this.barrageVue = barrageVue // 弹幕组件实例
this.deleteCount = 0, // 销毁弹幕的总数
this.lastDeleteCount = 0, // 最后可销毁的数量
this.row = 0,
this.count = 0
}
/**
* @return 无返回值
* @description 调用close 方法 关闭弹幕
*/
close() {
clearInterval(this.timer)
this.removeAllBarrage()
}
/**
* @description 删除全部的弹幕数据
*/
removeAllBarrage() {
this.barrages = []
}
}
关闭功能知识点回顾
在这里我们可以看到,关闭弹幕的功能其实很简单,你只需要把开启弹幕时的定时器关闭,并且把弹幕数组数据清空就可以了
4: 实现弹幕添加功能
index.vue
addBarrage(barrageContent) {
// 获取当前 定时器正在创建的 一行
let currentRow = this.barrageManager.getRow();
let row = currentRow === this.rows / 2 ? 0 : currentRow + 1;
if (row === this.rows / 2) {
row = 0;
}
let myBarrage = {
row,
barrageId: '1686292223004',
barrageContent,
style: this.style,
type: "mySelf", // 用户自己发布的弹幕类型
barrageCategory: this.userBarrageType
};
const item = this.crearteBarrageObject(myBarrage);
this.barrageManager.addBarrage(item); // 数据准备好了 调用添加方法
console.info("发送成功")
this.barrageManager.setRow(row + 1);
},
barrageManager.js
class BarrageManager {
constructor(barrageVue) {
this.barrages = []; // 填弹幕的数组
this.barragesIds = [] // 批量删除弹幕的数组id
this.sourceBarrages = [] // 源弹幕数据
this.timer = null //控制弹幕的开启和关
this.barrageVue = barrageVue // 弹幕组件实例
this.deleteCount = 0, // 销毁弹幕的总数
this.lastDeleteCount = 0, // 最后可销毁的数量
this.row = 0,
this.count = 0
}
/**
*
* @param {*} obj 合并完整的的弹幕对象
* @param {...any} args 开发者以后可能需要传递的剩余参数
*/
addBarrage(obj, ...args) {
const barrage = new Barrage(obj, ...args)
this.barrages.push(barrage)
}
}
添加功能知识点回顾
在这里我们可以看到,添加的时候,我们 组件 只需要去调用 addBarrage
方法进行弹幕添加,并且在调用的过程中我们去 new Barrage
这个类 , 也就是我们之前准备好的 弹幕数据类 | 数据层设计
5: 实现弹幕删除功能
class BarrageManager {
constructor(barrageVue) {
this.barrages = []; // 填弹幕的数组
this.barragesIds = [] // 批量删除弹幕的数组id
this.sourceBarrages = [] // 源弹幕数据
this.timer = null //控制弹幕的开启和关
this.barrageVue = barrageVue // 弹幕组件实例
this.deleteCount = 0, // 销毁弹幕的总数
this.lastDeleteCount = 0, // 最后可销毁的数量
this.row = 0,
this.count = 0
}
/**
*
* @param {*} barrageId // 入参 弹幕id
* @returns 无返回值
* @description 添加需要批量删除的 id 到 批量删除的栈中 barragesIds
*/
addBatchRemoveId(barrageId) {
this.barragesIds.push(barrageId)
this.batchRemoveHandle()
}
/**
*
* @param {*} start 你需要从第几位开始删除
* @param {*} deleteCount // 删除的总数是多少个
* @returns 无返回值
*/
batchRemoveBarrage(start, deleteCount) {
if (this.barrages.length === 0) return
this.barrages.splice(start, deleteCount)
}
batchRemoveId(start, deleteCount) {
if (this.barragesIds.length === 0) return
this.barragesIds.splice(start, deleteCount)
}
/**
* @param {*} barrageId 弹幕 id 针对单个删除弹幕时 使用
*/
removeBarrage(barrageId) {
let index = this.barrages.findIndex(item => item.barrageId === barrageId)
this.barrages.splice(index, 1)
}
/**
* @description 删除全部的弹幕数据
*/
removeAllBarrage() {
this.barrages = []
}
// 批量移除逻辑处理
batchRemoveHandle() {
if (this.deleteCount === 0 || this.deleteCount === 0) {
if (this.barragesIds.length === this.lastDeleteCount) {
this.batchRemoveBarrage(0, this.lastDeleteCount)
this.batchRemoveId(0, this.lastDeleteCount)
}
} else {
if (this.barragesIds.length === deleteQuantity.FIFTY) {
this.batchRemoveBarrage(0, deleteQuantity.FIFTY)
this.batchRemoveId(0, deleteQuantity.FIFTY)
this.deleteCount--
}
}
}
}
删除功能知识点回顾
在这里我们可以看到,删除的时候我们把每一个弹幕id加入到了一个数组中 , 当 弹幕id数组长度达到我想要删除的数量的时候, 调用 splice
方法 执行批量删除操作,当数据发生更新,视图也会更新,这样我们只需要执行一次dom操作,不需要每一次删除弹幕更新dom,造成不必要的性能消耗。
5: 实现弹幕重置功能
到这里,我相信你已经明白了我的设计,如果现在让你实现一个 重置弹幕方法 你会怎么做 ? 是不是只需要,调用一下 close
方法 , 然后再去 调用 open
方法就可以了,ok 接下来我会将完整版代码 放入我的github仓库,小伙伴们可以去拉取 仓库链接,具体代码还需要小伙伴们自己从头阅读一次,这里只是说明了部分内容 , 阅读完成后 , 你就会彻底理解。
关于 barrageTypeCallback 函数
这个方法主要是可以解决弹幕样式定制的问题,你可以根据每个弹幕的类型 做不同的样式对象返回,我们会自动帮你渲染。
barrageTypeCallback ( {args} ) {
const { barrageCategary } = args
if(barrageCategary === 'type1'){
retun {
className : 'classOne',
children : {
show : false
i : {
showIcon : false,
style : {
color : 'red'
}
}
}
}
}
else{
return { className : 'default' }
}
}
结束语
前面的所有代码只是想告诉大家这个设计思想,当你的思维模型出来以后,其实很轻松。
我是 前端小张同学,
来源:juejin.cn/post/7243680440694980668