fly-barrage 前端弹幕库(3):滚动弹幕的设计与实现

2024-03-01 23:52

本文主要是介绍fly-barrage 前端弹幕库(3):滚动弹幕的设计与实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

项目官网地址:https://fly-barrage.netlify.app/;
👑🐋🎉如果感觉项目还不错的话,还请点下 star 🌟🌟🌟。
Gitee:https://gitee.com/fei_fei27/fly-barrage(Gitee 官方推荐项目);
Github:https://github.com/feiafei27/fly-barrage;

其他系列文章:
fly-barrage 前端弹幕库(1):项目介绍
fly-barrage 前端弹幕库(2):弹幕内容支持混入渲染图片的设计与实现
fly-barrage 前端弹幕库(3):滚动弹幕的设计与实现

今天和大家说说滚动弹幕的设计与实现,为了便于理解,我会由简到难的一步步解析说明,主要包括以下几块内容:

  • 实现一条滚动弹幕的计算与渲染;
  • 实现多条滚动弹幕的计算与渲染;
  • 实现多条滚动弹幕相同字号并且不允许弹幕重叠的计算与渲染;
  • 实现多条滚动弹幕不同字号并且不允许弹幕重叠的计算与渲染;

1:实现一条滚动弹幕的计算与渲染

滚动弹幕具有如下两个特性:

export type BaseBarrageOptions = {// 弹幕的出现时间(毫秒为单位)time: number;
}
export type RenderConfig = {// 弹幕运行速度,仅对滚动弹幕有效(每秒多少像素)speed: number;
}

time 属性表示这个弹幕在视频播放器的时间是 time 的时候,这个滚动弹幕的左侧应该紧贴 Canvas 的右侧,此时弹幕的左侧距离 Canvas 的左侧距离正好是 Canvas 的宽,逻辑图如下所示:
在这里插入图片描述
speed 属性用于描述滚动弹幕的速度,意为每秒会移动多少像素。

每个滚动弹幕都有一个 originalLeft 属性,它标识着当播放进度是 0 的时候,滚动弹幕左侧距离 Canvas 左侧的距离,它的算法如下所示:

// 计算公式是:Canvas 元素的宽 + 弹幕出现时间 * 弹幕速度
this.originalLeft = this.br.canvasSize.width + (this.time / 1000) * this.br.renderConfig.speed;

如果某一个滚动弹幕的 time 属性为 0 的话,它的 originalLeft 正好等于 Canvas 的宽。

除了计算 originalLeft,还需要计算弹幕文本的宽,用于辅助计算某条滚动弹幕应不应该被 Canvas 实际渲染出来,弹幕宽度的计算方式可以看我的上一篇博客。

然后随着视频的播放,我们需要根据当前的播放进度计算出滚动弹幕左移的距离,计算方式如下所示:

// 弹幕整体向左移动的总距离,时间 * 速度
const translateX = (time / 1000) * this.br.renderConfig.speed;

左移的距离计算出来之后,我们就可以计算出这个滚动弹幕此时左侧距离 Canvas 左侧的距离,计算方式如下所示:

barrage.originalLeft - translateX

当滚动弹幕的左侧在 Canvas 右侧的左面并且滚动弹幕的右侧在 Canvas 左侧的右面的话,这个滚动弹幕就应该被渲染出来,借此我们可以计算出滚动弹幕是否会被渲染出来,计算方式如下所示:

const isShouldRender =// 弹幕的 originalRight 属性等于弹幕的 originalLeft 加上弹幕的宽度barrage.originalRight - translateX >= 0 &&barrage.originalLeft - translateX < this.br.canvasSize.width

如果 isShouldRender 变量为 true 的话,对弹幕进行绘制渲染操作即可(由于只有一条弹幕,所以暂不讨论弹幕渲染时的 top,随便设置一个 top 即可)。

2:实现多条滚动弹幕的计算与渲染

弹幕除了 left,还有 top 属性需要关注。在 B 站看视频,可以发现弹幕都是在固定的轨道中前后滚动的,就像现实生活中的高速公路一样,这样处理的好处是可以让播放的弹幕更加工整有层次,如果弹幕太乱的话,也会影响视频的浏览体验。

所以这里需要引入轨道的概念,借助轨道辅助计算弹幕的 top,让弹幕的滚动都是在轨道中。

轨道的高度等于滚动弹幕的高度,计算公式如下所示:

// 弹幕的高度等于弹幕字号乘以行高
const trackHeight = barrage.fontSize * barrage.lineHeight;

然后计算轨道的个数,计算公式如下所示:

// 计算总跑道数(canvas 高度 / 一个弹幕的高度)
const trackNum = Math.floor(canvas.height / trackHeight);

第 n 个轨道的 top 计算公式如下所示:

const n = 10;
const trackTop = (n - 1) * trackHeight;

然后我们遍历滚动弹幕,随机 push 进某一个轨道,将轨道的 top 设置给弹幕的 top 属性即可实现多条弹幕在轨道中滚动的效果。

3:实现多条滚动弹幕相同字号并且不允许弹幕重叠的计算与渲染(轨道算法)

当滚动弹幕的字号都是相同的时候,此时进行的不重叠计算是很简单的,计算步骤如下所示:

  1. 将所有的滚动弹幕按照 time 属性进行一次排序,time 小的排在前面;
  2. 遍历所有的滚动弹幕,判断当前循环的滚动弹幕能不能放到某一个实际轨道中,从上往下判断哪一个实际轨道能放进去;
  3. 如果当前判断的实际轨道中还没有弹幕或者当前实际轨道的最后一个弹幕和当前新增弹幕不重叠的话(是否会发生重叠可以通过两个滚动弹幕的 originalLeft 和 originalRight 计算出来 lastScrollBarrage.originalRight <= barrage.originalLeft),则表明当前循环的弹幕可以放到当前这个实际轨道中并且不会发生重叠的现象;
  4. 如果找到了能放进去的轨道的话,则将这个轨道的 top 设置给弹幕的 top 属性,并将当前这个弹幕的实例 push 到对应轨道的数组中即可;
  5. 如果没有找到能放进去的轨道的话,则不渲染当前这个弹幕,因为没有地方可以不重叠的渲染这个滚动弹幕;
  6. 至此滚动弹幕的不重叠 top 就计算出来了,再结合第一小节的知识进行渲染即可;

相关逻辑图如下所示:
初始阶段,所有轨道都为空:
初始阶段
第一个滚动弹幕能直接放到第一个轨道中:
第一个滚动弹幕
第二个滚动弹幕,和第一条轨道的最后一个弹幕有重叠,第二条轨道没有弹幕,所以放到了第二条轨道中:
第二个滚动弹幕
第三个滚动弹幕和第一个轨道的最后一个弹幕不重叠,所以能放到第一个轨道中:
第三个滚动弹幕
后面的弹幕按此逻辑逐个计算即可。

4:实现多条滚动弹幕不同字号并且不允许弹幕重叠的计算与渲染(虚拟轨道算法)

虚拟轨道算法的相关概念

实际轨道

这里的实际轨道和上面说的轨道是一个东西,只不过他的高度不能简单的视为 字号 x 行高,因为每个弹幕的字号和行高在极限情况下可以都是不同的。

这里规定实际轨道的高度是:所有弹幕字号 x 行高形成数字数组中的众数。

虚拟轨道

虚拟轨道是相邻实际轨道的合并,例如,我们现在有 4 个实际轨道,依次是:rt1、rt2、rt3、rt4,能够组成的虚拟轨道有 10 个分别是:
高度为 1 的:[rt1]、[rt2]、[rt3]、[rt4]
高度为 2 的:[rt1, rt2]、[rt2, rt3]、[rt3, rt4]
高度为 3 的:[rt1, rt2, rt3]、[rt2, rt3, rt4]
高度为 4 的:[rt1, rt2, rt3, rt4]
每个虚拟轨道都有一个对应的数组,用于存放弹幕,弹幕只能存放到虚拟轨道中,不要放到实际轨道中。

计算过程

  1. 计算弹幕高度,判断它适合放在高度为几的虚拟轨道,假设它有两个实际轨道的高度,那么它应该放在高度为2的虚拟轨道;
  2. 遍历高度为 2 的 3 个虚拟轨道,判断有没有虚拟轨道能放下,这里先判断 [rt1, rt2] 能不能放下,能放下的条件是:所有包含 rt1 或者 rt 2 两个实际轨道的虚拟轨道的最后一个弹幕和当前新增弹幕不重叠,如果满足这个条件的话,则 [rt1, rt2] 能放下,否则放不下,继续判断下一个虚拟轨道 [rt2, rt3] 能不能放下,如果高度为 2 的 3 个虚拟轨道都遍历完了,还没有的话,就说明当前新增弹幕无法不重叠的放入;
  3. 如果能够找到不重叠虚拟轨道的话,根据虚拟轨道计算 top,并将当前的弹幕实例 push 到对应虚拟轨道对应的数组中;

相关逻辑图如下所示:
初始阶段,所有轨道都为空:
初始阶段,所有轨道都为空
第一个弹幕的高度是2个实际轨道,能放到 [rt1, rt2]:
第一个弹幕的高度是2个实际轨道,能放下
第二个弹幕的高度是1个实际轨道, [rt1] 和 [rt2] 都放不下,因为第一个弹幕 [rt1, rt2] 占据了这 2 个实际轨道,不过能放到 [rt3]:
放失败的场景
放成功的场景
第三个弹幕的高度是3个实际轨道,它哪个虚拟轨道都放不下:
在这里插入图片描述
后续的新弹幕逻辑以此类推,对应的代码实现如下所示:

/*** 进行不允许重叠的布局* @param scrollBarrages 滚动弹幕实例数组*/
avoidOverlapLayout(scrollBarrages: ScrollBarrage[]) {const startTime = Date.now();// 将所有虚拟轨道中的存储弹幕清空this.virtualTracks.forEach(vt => vt.clearBarrage());// 遍历进行布局计算scrollBarrages.forEach(barrage => {// 遍历 grade 属性为 grade 的虚拟轨道,判断能不能放进去const fittedVirtualTracks = this.gradeToVtsMap.get(barrage.grade) || [];for (let i = 0; i < fittedVirtualTracks.length; i++) {// 当前遍历的 virtualTrackconst virtualTrack = fittedVirtualTracks[i];// 判断当前遍历的 virtualTrack 能不能放进去;// 能放进去的条件是:包含 virtualTrack 内部任一实际轨道的虚拟轨道(grade <= maxGrade)的最后一个弹幕和当前新增弹幕都不重叠const canPush = (this.vtToVtsMap.get(virtualTrack) || []).every(item => {// 获取最后一个滚动弹幕const lastScrollBarrage = item.getLastBarrage();// 如果当前轨道没有滚动弹幕的话,说明和最后一个弹幕不可能重叠,直接 return true 即可if (!lastScrollBarrage) return true;// 有的话,判断是否会重叠return lastScrollBarrage.originalRight + this.minSpace <= barrage.originalLeft;});if (canPush) {barrage.show = true;virtualTrack.push(barrage);// 计算该滚动弹幕的 topbarrage.top = virtualTrack.top;break;} else {barrage.show = false;}}// 重要的弹幕,如果没有虚拟轨道能插进去的话,则随机一个实际轨道if (barrage.prior && !barrage.show) {this.randomTrackBarrage(barrage);}});this.isLogKeyData && console.log(`虚拟轨道算法花费时间:${(Date.now() - startTime)}ms`);
}

这篇关于fly-barrage 前端弹幕库(3):滚动弹幕的设计与实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/764253

相关文章

Nginx 配置跨域的实现及常见问题解决

《Nginx配置跨域的实现及常见问题解决》本文主要介绍了Nginx配置跨域的实现及常见问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来... 目录1. 跨域1.1 同源策略1.2 跨域资源共享(CORS)2. Nginx 配置跨域的场景2.1

Python中提取文件名扩展名的多种方法实现

《Python中提取文件名扩展名的多种方法实现》在Python编程中,经常会遇到需要从文件名中提取扩展名的场景,Python提供了多种方法来实现这一功能,不同方法适用于不同的场景和需求,包括os.pa... 目录技术背景实现步骤方法一:使用os.path.splitext方法二:使用pathlib模块方法三

CSS place-items: center解析与用法详解

《CSSplace-items:center解析与用法详解》place-items:center;是一个强大的CSS简写属性,用于同时控制网格(Grid)和弹性盒(Flexbox)... place-items: center; 是一个强大的 css 简写属性,用于同时控制 网格(Grid) 和 弹性盒(F

CSS实现元素撑满剩余空间的五种方法

《CSS实现元素撑满剩余空间的五种方法》在日常开发中,我们经常需要让某个元素占据容器的剩余空间,本文将介绍5种不同的方法来实现这个需求,并分析各种方法的优缺点,感兴趣的朋友一起看看吧... css实现元素撑满剩余空间的5种方法 在日常开发中,我们经常需要让某个元素占据容器的剩余空间。这是一个常见的布局需求

CSS Anchor Positioning重新定义锚点定位的时代来临(最新推荐)

《CSSAnchorPositioning重新定义锚点定位的时代来临(最新推荐)》CSSAnchorPositioning是一项仍在草案中的新特性,由Chrome125开始提供原生支持需... 目录 css Anchor Positioning:重新定义「锚定定位」的时代来了! 什么是 Anchor Pos

CSS中的Static、Relative、Absolute、Fixed、Sticky的应用与详细对比

《CSS中的Static、Relative、Absolute、Fixed、Sticky的应用与详细对比》CSS中的position属性用于控制元素的定位方式,不同的定位方式会影响元素在页面中的布... css 中的 position 属性用于控制元素的定位方式,不同的定位方式会影响元素在页面中的布局和层叠关

HTML5 getUserMedia API网页录音实现指南示例小结

《HTML5getUserMediaAPI网页录音实现指南示例小结》本教程将指导你如何利用这一API,结合WebAudioAPI,实现网页录音功能,从获取音频流到处理和保存录音,整个过程将逐步... 目录1. html5 getUserMedia API简介1.1 API概念与历史1.2 功能与优势1.3

Java实现删除文件中的指定内容

《Java实现删除文件中的指定内容》在日常开发中,经常需要对文本文件进行批量处理,其中,删除文件中指定内容是最常见的需求之一,下面我们就来看看如何使用java实现删除文件中的指定内容吧... 目录1. 项目背景详细介绍2. 项目需求详细介绍2.1 功能需求2.2 非功能需求3. 相关技术详细介绍3.1 Ja

使用Python和OpenCV库实现实时颜色识别系统

《使用Python和OpenCV库实现实时颜色识别系统》:本文主要介绍使用Python和OpenCV库实现的实时颜色识别系统,这个系统能够通过摄像头捕捉视频流,并在视频中指定区域内识别主要颜色(红... 目录一、引言二、系统概述三、代码解析1. 导入库2. 颜色识别函数3. 主程序循环四、HSV色彩空间详解

PostgreSQL中MVCC 机制的实现

《PostgreSQL中MVCC机制的实现》本文主要介绍了PostgreSQL中MVCC机制的实现,通过多版本数据存储、快照隔离和事务ID管理实现高并发读写,具有一定的参考价值,感兴趣的可以了解一下... 目录一 MVCC 基本原理python1.1 MVCC 核心概念1.2 与传统锁机制对比二 Postg