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

news2024/12/23 5:42:01

项目官网地址: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++) {
      // 当前遍历的 virtualTrack
      const 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);
        // 计算该滚动弹幕的 top
        barrage.top = virtualTrack.top;
        break;
      } else {
        barrage.show = false;
      }
    }
    // 重要的弹幕,如果没有虚拟轨道能插进去的话,则随机一个实际轨道
    if (barrage.prior && !barrage.show) {
      this.randomTrackBarrage(barrage);
    }
  });
  this.isLogKeyData && console.log(`虚拟轨道算法花费时间:${(Date.now() - startTime)}ms`);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1482804.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

电子电器架构新趋势 —— 最佳着力点:域控制器

电子电器架构新趋势 —— 最佳着力点&#xff1a;域控制器 我是穿拖鞋的汉子&#xff0c;魔都中坚持长期主义的汽车电子工程师&#xff08;Wechat&#xff1a;gongkenan2013&#xff09;。 老规矩&#xff0c;分享一段喜欢的文字&#xff0c;避免自己成为高知识低文化的工程师…

Ubuntu进入python时报错:找不到命令 “python”,“python3” 命令来自 Debian 软件包 python3

一、错误描述 二、解决办法 进入”/usr/bin”目录下&#xff0c;查看/usr/bin目录中所有与python相关的文件和链接&#xff1a; cd /usr/bin ls -l | grep python 可以看到Python3指向的是Python3.10&#xff0c;而并无指向python3的软连接 只需要在python与python3之间手动…

探索数字未来:DApp钱包Defi引领新纪元

​小编介绍&#xff1a;10年专注商业模式设计及软件开发&#xff0c;擅长企业生态商业模式&#xff0c;商业零售会员增长裂变模式策划、商业闭环模式设计及方案落地&#xff1b;扶持10余个电商平台做到营收过千万&#xff0c;数百个平台达到百万会员&#xff0c;欢迎咨询。 随…

LINUX基础培训二十八之Shell正则表达式

一、何为正则表达式 在编写处理字符串的程序或网页时&#xff0c;经常会有查找符合某些复杂规则的字符串的需要。正则表达式就是用于描述这些规则的工具。换句话说&#xff0c;正则表达式就是记录文本规则的代码。 在Windows/Dos下用于文件查找的通配符&#xff08; wildcard…

借助ChatGPT使用Python搭建一个工具网站

文章目录 前言网站搭建过程总结 前言 不知不觉ChatGPT已经风靡一年多了&#xff0c;现在基本每天工作时都会用到&#xff0c;相比于传统的搜索引擎它究竟强在哪呢&#xff1f;我觉得以往的搜索引擎是一个机器&#xff0c;你给它关键信息它能返回匹配关键词的内容数据&#xff…

【C语言】动态内存管理常用函数

前言 我们在之前学习的数组开辟的空间是固定不变的&#xff0c;有时候我们需要的空间⼤⼩在程序运⾏的时候才能知道~ c语言中的动态内存开辟&#xff0c;让程序员⾃⼰可以根据实际需求申请和释放相应空间&#xff0c;这使得空间的开辟变得灵活了许多。 欢迎关注个人主页&#x…

管家婆云上管家新帐套建立步骤

第一步&#xff0c;客户服务管理&#xff1b; 第二步&#xff0c;添加帐套&#xff1b; 第三步&#xff0c;点击“管理”进入&#xff1b;第四步&#xff0c;添加帐套信息&#xff1b; 第五步&#xff0c;找到客户&#xff0c;查看帐套信息&#xff1b; 第六步&#xff0c;确认…

微服务简介及其相关技术栈

目录 1、简介 2、技术栈 3、单体架构 4、分布式架构 5、微服务 6、总结 &#x1f343;作者介绍&#xff1a;双非本科大三网络工程专业在读&#xff0c;阿里云专家博主&#xff0c;专注于Java领域学习&#xff0c;擅长web应用开发、数据结构和算法&#xff0c;初步涉猎Pyth…

【Java实战项目】SpringBoot + Vue3打造你的在线电子书平台!

今天给大家分享一个基础的Java实战项目&#xff0c;用SpringBoot和Vue3开发一个电子书平台&#xff0c;大家可以尝试做一下这个项目&#xff0c;以此来检验这段时间的学习成果&#xff01;废话不多说&#xff0c;下面正式进入项目&#xff1a; 一、项目介绍 1. 项目简介 在线…

基于springboot+vue的公寓报修管理系统

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战&#xff0c;欢迎高校老师\讲师\同行交流合作 ​主要内容&#xff1a;毕业设计(Javaweb项目|小程序|Pyt…

机器人与AGI会撞出什么火花?

真正的科技变革是不是就要来临了&#xff1f;各方大佬都开始布局机器人&#xff0c;对于普通人的就业会造成什么影响&#xff1f; ​ 优牛企讯-企业动态信息监控专家 在优牛企讯-企业动态监控专家搜索可知&#xff0c;全国目前的机器人公司已经达到了26401家&#xff0c;近一年…

VSCode通过SSH连接Docker环境进行开发

文章目录 VSCode 插件Docker 镜像构建镜像部署环境 VSCode 连接本地Docker容器VSCode SSH连接Docker容器VSCode 打开容器内目录文件 VSCode 插件 Remote - SSH Docker 镜像 https://hub.docker.com/_/golang # Golang 镜像 docker pull golang:1.22构建镜像 Dockerfile F…

AVT Prosilica GC Vision Cameras 相机视觉说明使用安装。具体详情内容可参看PDF目录内容。

AVT Prosilica GC Vision Cameras 相机视觉说明使用安装。具体详情内容可参看PDF目录内容。

3.1日学习打卡----初学FastDFS(一)

3.1日学习打卡 目录: 3.1日学习打卡一. 为什么要使用分布式文件系统二. FastDFS简介核心概念上传机制下载机制FastDFS环境搭建_LinuxFastDFS指令 一. 为什么要使用分布式文件系统 单机时代 初创时期由于时间紧迫&#xff0c;在各种资源有限的情况下&#xff0c;通常就直接在项…

varFormatter 数据格式化库 以性能优先的 快速的 内存对象格式转换

varFormatter 数据格式化 技术 开源技术栏 对象/变量格式化工具库&#xff0c;其支持将一个对象进行按照 JSON XML HTML 等格式进行转换&#xff0c;并获取到结果字符串&#xff01; 目录 文章目录 varFormatter 数据格式化 技术目录介绍获取方式 使用实例格式化组件的基本使…

查看网络连接的netstat

netstat是一个监控TCP/IP网络的非常有用的工具&#xff0c;可以显示路由表、实际的网络连接&#xff0c;以及每一个网络接口设备的状态信息&#xff0c;可以让用户得知目前都有哪些网络连接正在运作。netstat用户显示与IP、TCP、UDP和ICMP协议相关的统计数据&#xff0c;一般用…

ESP32 web 对接华为云平台--MQTT协议

文章目录 前言一、MQTT协议二、如何使用MQTT协议对接华为云1.注册华为云账号2.设备接入中创建资源空间3.如何连接4.通过MQTT.fx工具做初步对接4.1 设置连接信息4.2 连接平台 5.查看平台设备信息 三. 设备测对接平台1.ESP测引入MQTT库2.编码2.1前端编码修改2.2 后端接口修改 3.M…

28.HarmonyOS App(JAVA)多页签的实现(Tab)

HarmonyOS App(JAVA)多页签的实现&#xff08;Tab&#xff09; 页面可左右滑动&#xff0c;点击界面1,2,3切换到对应界面 PageSlider的创建和使用 在layout目录下的xml文件中创建PageSlider。 <PageSlider ohos:id"$id:page_slider" ohos:height"300vp&…

UCSF DOCK 分子对接详细案例(01)- rigid, fixed anchor, flexible dock

欢迎浏览我的CSND博客&#xff01; Blockbuater_drug …点击进入 文章目录 前言一、操作环境二、研究背景三、受体-配体结构文件准备3.1准备文件夹DOCK_workdir, 下载晶体结构3.1.1 来自湿实验的受体配体共晶结构&#xff1a;3.1.2 来自深度学习和语言模型推理预测的蛋白结构&a…

DDS数据分发服务——提升汽车领域数据传输效率

1.引言 随着智能化技术的快速发展&#xff0c;汽车行业正经历着一场革命性的变革。如今的分布式系统变得越来越复杂且庞大&#xff0c;对网络通信基数要求在功能和性能层面越来越高。数据分发服务&#xff08;DDS&#xff09;作为一项先进的数据传输解决方案&#xff0c;在汽车…