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:实现多条滚动弹幕相同字号并且不允许弹幕重叠的计算与渲染(轨道算法)
当滚动弹幕的字号都是相同的时候,此时进行的不重叠计算是很简单的,计算步骤如下所示:
- 将所有的滚动弹幕按照 time 属性进行一次排序,time 小的排在前面;
- 遍历所有的滚动弹幕,判断当前循环的滚动弹幕能不能放到某一个实际轨道中,从上往下判断哪一个实际轨道能放进去;
- 如果当前判断的实际轨道中还没有弹幕或者当前实际轨道的最后一个弹幕和当前新增弹幕不重叠的话(是否会发生重叠可以通过两个滚动弹幕的 originalLeft 和 originalRight 计算出来 lastScrollBarrage.originalRight <= barrage.originalLeft),则表明当前循环的弹幕可以放到当前这个实际轨道中并且不会发生重叠的现象;
- 如果找到了能放进去的轨道的话,则将这个轨道的 top 设置给弹幕的 top 属性,并将当前这个弹幕的实例 push 到对应轨道的数组中即可;
- 如果没有找到能放进去的轨道的话,则不渲染当前这个弹幕,因为没有地方可以不重叠的渲染这个滚动弹幕;
- 至此滚动弹幕的不重叠 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]
每个虚拟轨道都有一个对应的数组,用于存放弹幕,弹幕只能存放到虚拟轨道中,不要放到实际轨道中。
计算过程
- 计算弹幕高度,判断它适合放在高度为几的虚拟轨道,假设它有两个实际轨道的高度,那么它应该放在高度为2的虚拟轨道;
- 遍历高度为 2 的 3 个虚拟轨道,判断有没有虚拟轨道能放下,这里先判断 [rt1, rt2] 能不能放下,能放下的条件是:所有包含 rt1 或者 rt 2 两个实际轨道的虚拟轨道的最后一个弹幕和当前新增弹幕不重叠,如果满足这个条件的话,则 [rt1, rt2] 能放下,否则放不下,继续判断下一个虚拟轨道 [rt2, rt3] 能不能放下,如果高度为 2 的 3 个虚拟轨道都遍历完了,还没有的话,就说明当前新增弹幕无法不重叠的放入;
- 如果能够找到不重叠虚拟轨道的话,根据虚拟轨道计算 top,并将当前的弹幕实例 push 到对应虚拟轨道对应的数组中;
相关逻辑图如下所示:
初始阶段,所有轨道都为空:

第一个弹幕的高度是2个实际轨道,能放到 [rt1, rt2]:

第二个弹幕的高度是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):滚动弹幕的设计与实现
项目官网地址:https://fly-barrage.netlify.app/; 👑🐋🎉如果感觉项目还不错的话,还请点下 star 🌟🌟🌟。 Gitee:https://gitee.com/fei_fei27/fly-barrage&a…...
Mysql面试总结
基础 1. 数据库的三范式是什么? 第一范式:强调的是列的原子性,即数据库表的每一列都是不可分割的原子数据项。第二范式:要求实体的属性完全依赖于主关键字。所谓完全 依赖是指不能存在仅依赖主关键字一部分的属性。第三范式&…...
【深圳五兴科技】Java后端面经
本文目录 写在前面试题总览1、java集合2、创建线程的方式3、对spring的理解4、Spring Boot 和传统 Spring 框架的一些区别5、springboot如何解决循环依赖6、对mybatis的理解7、缓存三兄弟8、接口响应慢的处理思路9、http的状态码 写在前面 关于这个专栏: 本专栏记录…...
画图(ccf201409-2)解题思路
解题思路 填充100*100二维数组,范围内的元素修改成1,最后累积求和。...
蓝桥杯刷题(一)
一、 import os import sys def dps(s):dp [0] * len(s)dp[0] ord(s[0]) - 96if len(s) 1:return dp[-1]dp[1] max(ord(s[0]) - 96, ord(s[1]) - 96)for i in range(2, len(s)):dp[i] max(dp[i - 1], dp[i - 2] (ord(s[i])) - 96)return dp[-1] s input() print(dps(s))…...
设计模式:策略模式 ⑥
一、策略模式思想 简介 策略模式(Strategy Pattern)属于对象的行为模式。其用意是针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。策略模式使得算法可以在不影响到客户端的情况下发生变化。…...
数据结构从入门到精通——顺序表
顺序表 前言一、线性表二、顺序表2.1概念及结构2.2 接口实现2.3 数组相关面试题2.4 顺序表的问题及思考 三、顺序表具体实现代码顺序表的初始化顺序表的销毁顺序表的打印顺序表的增容顺序表的头部/尾部插入顺序表的头部/尾部删除指定位置之前插入数据和删除指定位置数据顺序表元…...
001-CSS-水平垂直居中布局
水平垂直居中布局 方案一:弹性盒子布局方案二:绝对定位 transform方案三:margin 绝对定位,四个方向为零方案四:绝对定位 margin方案五:绝对定位 calc 方案一:弹性盒子布局 💡 T…...
【[STM32]标准库-自定义BootLoader】
[STM32]标准库-自定义BootLoader BootloaderBootloader的实现BOOTloader工程APP工程 Bootloader bootloader其实就是一段启动程序,它在芯片启动的时候最先被执行,可以用来做一些硬件的初始化或者用作固件热更新,当初始化完成之后跳转到对应的…...
Spring Boot项目中不使用@RequestMapping相关注解,如何动态发布自定义URL路径
一、前言 在Spring Boot项目开发过程中,对于接口API发布URL访问路径,一般都是在类上标识RestController或者Controller注解,然后在方法上标识RequestMapping相关注解,比如:PostMapping、GetMapping注解,通…...
Vue中有哪些优化性能的方法?
Vue是一款流行的JavaScript框架,用于构建交互性强的Web应用程序。在前端开发中,性能优化是一个至关重要的方面,尤其是当应用程序规模变大时。Vue提供了许多优化性能的方法,可以帮助开发人员提升应用程序的性能,从而提升…...
Python pandas遍历行数据的2种方法
背景 pandas在数据处理过程中,除了对整列字段进行处理之外,有时还需求对每一行进行遍历,来处理每行的数据。本篇文章介绍 2 种方法,来遍历pandas 的行数据 小编环境 import sysprint(python 版本:,sys.version.spli…...
Spring之@Transactional源码解析
前言 我们在日常开发的时候经常会用到组合注解,比如:EnableTransactionManagement Transactional、EnableAsync Async、EnableAspectJAutoProxy Aspect。今天我们就来抽丝剥茧,揭开Transactional注解的神秘面纱 EnableTransactionManagement注解的作用 当我们看到类似Ena…...
第三届国际亲子游泳学术峰会,麒小佑为亲游行业提供健康解决方案
第三届国际亲子游泳学术峰会大合影 2024年2月26—28日,第三届国际亲子游泳学术峰会在中国青岛成功召开。 第三届国际亲子游泳学术峰会是中国婴幼游泳行业最高标准的学术性会议,由亲游圈主办,旨在为本行业搭建一个高端圈层,帮助机…...
Python光速入门 - Flask轻量级框架
FlASK是一个轻量级的WSGI Web应用程序框架,Flask的核心包括Werkzeug工具箱和Jinja2模板引擎,它没有默认使用的数据库或窗体验证工具,这意味着用户可以根据自己的需求选择不同的数据库和验证工具。Flask的设计理念是保持核心简单,…...
C/C++ 说说引用这玩仍是干啥的
引用的本质就是给某个实例对象起个外号。生活中李逵,也叫黑旋风。诸葛亮,又叫孔明。 引用的方式: 类型& 引用名对象名 举个例子 int i0; int& ki;//这种方式就是引用----->i有了自己的小名,从次叫k了 std::cout<…...
swoole
php是单线程。php是靠多进程来处理任务,任何后端语言都可以采用多进程处理方式。如我们常用的php-fpm进程管理器。线程与协程,大小的关系是进程>线程>协程,而我们所说的swoole让php实现了多线程,其实在这里来说,就是好比让php创建了多个进程,每个进程执行一条…...
kubectl基础命令详解
管理名称空间资源 查看名称空间 [rootceshi-130 conf]# kubectl get ns [rootceshi-130 conf]# kubectl get namespace NAME STATUS AGE default Active 7d17h kube-node-lease Active 7d17h kube-public Active 7d17h kube-system …...
collection的遍历方式
增强for遍历 增强for的底层就是迭代器,为了简化迭代器的代码书写的。 他是jdk5之后出现的,其内部原理就是一个Iterator迭代器。 所有的单列集合和数组才能用增强for进行遍历。 package myCollection;import java.util.ArrayList; import java.util.C…...
SpringBoot中@Async使用注意事项
前言 Async这个注解想必大家都用过,是用来实现异步调用的。一个方法加上这个注解以后,当被调用时会使用新的线程来调用。但其实这里面也有一个坑。 问题 这个注解使用时存在如下问题:在没有自定义线程池的场景下,默认会采用Sim…...
Beyond Compare 5 本地密钥生成实用方案:告别试用限制的完整指南
Beyond Compare 5 本地密钥生成实用方案:告别试用限制的完整指南 【免费下载链接】BCompare_Keygen Keygen for BCompare 5 项目地址: https://gitcode.com/gh_mirrors/bc/BCompare_Keygen Beyond Compare 5 作为一款专业的文件对比工具,在试用期…...
Keepass2Android密码库完整性验证终极指南:如何确保你的密码安全无虞
Keepass2Android密码库完整性验证终极指南:如何确保你的密码安全无虞 【免费下载链接】keepass2android Password manager app for Android 项目地址: https://gitcode.com/gh_mirrors/ke/keepass2android 在当今数字化时代,密码管理器已成为保护…...
实战踩坑:在华为ENSP模拟器上配置OSPF NSSA区域,为什么外部路由没传出去?
华为ENSP模拟器中OSPF NSSA区域外部路由失效的深度排查指南 当你在华为ENSP模拟器中配置OSPF NSSA区域时,是否遇到过这样的困境:明明按照教程步骤操作,外部路由却像被黑洞吞噬一般无法传递到其他区域?本文将带你深入这个技术迷宫的…...
DeepSeek-Coder-V2-Lite-Instruct社区成功案例:开发者如何用AI助手实现项目突破
DeepSeek-Coder-V2-Lite-Instruct社区成功案例:开发者如何用AI助手实现项目突破 【免费下载链接】DeepSeek-Coder-V2-Lite-Instruct 开源代码智能利器——DeepSeek-Coder-V2,性能比肩GPT4-Turbo,全面支持338种编程语言,128K超长上…...
一键启动翻译服务:Hunyuan-MT-7B-WEBUI详细使用教程(附加速链接)
一键启动翻译服务:Hunyuan-MT-7B-WEBUI详细使用教程(附加速链接) 1. 为什么选择Hunyuan-MT-7B-WEBUI 在全球化交流日益频繁的今天,语言障碍成为许多企业和个人面临的现实挑战。传统翻译工具要么准确度不足,要么部署复…...
从PVT到CST:5种CiA402控制模式在机器人项目中的花式用法(附ROS2配置示例)
从PVT到CST:5种CiA402控制模式在机器人项目中的花式用法(附ROS2配置示例) 在工业机器人开发中,控制模式的灵活切换往往能解决80%的运动控制难题。当机械臂需要完成高精度装配时,CSP模式能保证微米级定位;执…...
Phi-4-mini-reasoning真实案例:教育机构自动批题与答案生成应用
Phi-4-mini-reasoning真实案例:教育机构自动批题与答案生成应用 1. 教育场景中的智能批改需求 在教育培训行业,教师每天需要花费大量时间批改作业和试卷。传统的人工批改方式存在几个明显痛点: 时间成本高:一位数学老师批改50份…...
Hotkey Detective:Windows热键冲突终极诊断指南
Hotkey Detective:Windows热键冲突终极诊断指南 【免费下载链接】hotkey-detective A small program for investigating stolen key combinations under Windows 7 and later. 项目地址: https://gitcode.com/gh_mirrors/ho/hotkey-detective 你是否曾经遇到…...
【仅限头部金融科技团队内部流通】FastAPI 2.0 AI流式响应安全加固方案:防内存溢出、防连接耗尽、防Token泄露(含OWASP ASVS v4.0合规对照表)
第一章:FastAPI 2.0 AI流式响应安全加固方案全景概览FastAPI 2.0 引入了对 Server-Sent Events(SSE)与异步生成器的原生增强支持,使大语言模型(LLM)的流式响应(如 token-by-token 输出ÿ…...
AI结对编程:与快马AI对话式迭代,智能优化你的系统ER图设计
AI结对编程:与快马AI对话式迭代,智能优化你的系统ER图设计 最近在做一个员工管理系统的数据库设计,发现ER图设计是个需要反复推敲的过程。传统方式下,每次修改都要手动调整图形,效率很低。直到尝试了InsCode(快马)平台…...
