fly-barrage 前端弹幕库(4):顶部、底部弹幕的设计与实现

项目官网地址:fly-barrage.netlify.app/; 👑🐋🎉如果感觉项目还不错的话,还请点下 star 🌟🌟🌟。

Gitee:gitee.com/fei_fei27/f...(Gitee 官方推荐项目);

Github:github.com/feiafei27/f...

今天和大家说说顶部、底部弹幕的设计与实现,这块的逻辑相对简单些,为了方便,这里把顶部、底部弹幕统一叫固定弹幕。

1:固定弹幕 left 的计算

固定弹幕从开始渲染到渲染结束,它的 left 属性都是不变的,因为固定弹幕是居中的,所以它的算法如下所示:

typescript 复制代码
export default class FixedBarrage extends BaseBarrage {
  constructor(fixedBarrageOptions: FixedBarrageOptions, barrageRenderer: BarrageRenderer) {
    this.calcFixedBarrageLeft();
  }

  /**
   * 计算固定弹幕的 left 属性
   */
  calcFixedBarrageLeft() {
    this.left = (this.br.canvasSize.width - this.width) / 2;
  }
}

使用 Canvas 的宽减去固定弹幕的宽,然后除以 2 即可。

2:固定弹幕 top 的计算

这里我把整个 Canvas 分成上下等高的两部分,上半部分 [0, mid] 只能渲染顶部弹幕,下半部分 [mid, full] 只能渲染底部弹幕。

2-1:重要属性

在源码中,设计了一个专门的类用于固定弹幕的布局计算。

typescript 复制代码
/**
 * 用于处理顶部弹幕和底部弹幕的布局问题
 */
export default class FixedBarrageLayout {
  // 用于维护当前渲染的顶部弹幕(从上往下排序)
  topRenderBarrages: FixedBarrage[] = [];
  // 用于维护当前渲染的底部弹幕(从下往上排序)
  bottomRenderBarrages: FixedBarrage[] = [];
}

这个类中最重要的就是 topRenderBarrages 和 bottomRenderBarrages 两个属性,这两个属性是数组类型的,用于维护当前正在渲染的固定弹幕。

2-2:重要方法

接下来说说这个 class 中最重要的两个方法,getRenderFixedBarrages 和 insertFixedBarrage,这两个方法围绕着 topRenderBarrages 和 bottomRenderBarrages 两个数组进行一系列的计算处理。

typescript 复制代码
/**
 - 获取当前应该渲染的固定弹幕
 - @param allFixedBarrages 所有的固定弹幕数组
 - @param time 视频播放的时间点
   */
  getRenderFixedBarrages(allFixedBarrages: FixedBarrage[], time: number): FixedBarrage[] {
    // 1:能够渲染的顶部弹幕和底部弹幕
    const nextTopRenderBarrages = allFixedBarrages
      .filter(barrage => barrage.barrageType === 'top' && time >= barrage.time && time <= barrage.endTime);
    const nextBottomRenderBarrages = allFixedBarrages
      .filter(barrage => barrage.barrageType === 'bottom' && time >= barrage.time && time <= barrage.endTime);

    // 2:移除接下来不用渲染的弹幕
    this.topRenderBarrages = this.topRenderBarrages
      .filter(barrage => nextTopRenderBarrages.includes(barrage));
    this.bottomRenderBarrages = this.bottomRenderBarrages
      .filter(barrage => nextBottomRenderBarrages.includes(barrage));

    // 3:获取新增的需要渲染的弹幕,之前已经渲染的不用处理,因为之前已经计算过 top 属性了
    const newTopRenderBarrages = nextTopRenderBarrages
      .filter(barrage => !this.topRenderBarrages.includes(barrage));
    const newBottomRenderBarrages = nextBottomRenderBarrages
      .filter(barrage => !this.bottomRenderBarrages.includes(barrage));

    // 4:遍历新增的弹幕,看能不能插入,能插入的话,计算 top
    newTopRenderBarrages.forEach(newTopBarrage => {
      this.insertFixedBarrage(newTopBarrage);
    });
    newBottomRenderBarrages.forEach(newBottomBarrage => {
      this.insertFixedBarrage(newBottomBarrage);
    });

    return [...this.topRenderBarrages, ...this.bottomRenderBarrages];
  }

这个方法的作用就是:获取当前应该渲染的固定弹幕,它的计算步骤如下所示:

  • 1:先获取能够渲染的顶部弹幕和底部弹幕,这里根据固定弹幕的 barrageType 和 time 计算出当前可以被渲染出的顶部弹幕和底部弹幕;
  • 2:移除接下来不用渲染的弹幕,之前渲染的固定弹幕,是保存在 topRenderBarrages 和 bottomRenderBarrages 中的,此时新的 time,之前渲染的弹幕在新的 time 有可能已经不需要渲染了,所以这里需要把不需要渲染的弹幕从数组中移除掉;
  • 3:获取新增的需要渲染的弹幕,之前已经渲染的不用处理,因为之前已经计算过 top 属性并且已经维护在数组中了;
  • 4:遍历新增的弹幕,看能不能插入,能插入的话,计算 top,能不能插入的逻辑在 insertFixedBarrage 方法中;
  • 5:上面的逻辑都执行完了后,topRenderBarrages 和 bottomRenderBarrages 数组中的固定弹幕就是当前应该被渲染出来的弹幕,将他们组合成一个新的数组 return 出去即可,接下来看看 insertFixedBarrage 方法的逻辑;

insertFixedBarrage 方法的源码如下所示:

typescript 复制代码
/**
   * 封装通用的工具方法
   * @param barrage 弹幕实例
   */
  insertFixedBarrage(barrage: FixedBarrage) {
    // 标识 barrage 是否插入成功
    let inserted = false;
    if (barrage.barrageType === 'top') {
      // 如果当前没有渲染的顶部弹幕的话,判断 topRange 能不能插进去
      if (this.topRenderBarrages.length === 0) {
        if (this.topRangeLength >= barrage.height) {
          barrage.top = this.topRange[0];
          this.topRenderBarrages.push(barrage);
          inserted = true;
        }
      } else {
        // 判断顶部弹幕的间隙能不能插入,遍历存在的顶部弹幕
        for (let i = 0; i < this.topRenderBarrages.length; i++) {
          const currentTopBarrage = this.topRenderBarrages[i];
          // 如果当前是第一个的话,判断第一个上面能否插入
          if (i === 0) {
            if (currentTopBarrage.top - this.topRange[0] >= barrage.height) {
              barrage.top = this.topRange[0];
              this.topRenderBarrages.unshift(barrage);
              inserted = true;
              break;
            }
          }
          // 判断下面能不能插入
          // 先计算下面间隙的高度
          const belowGapHeight = (i === this.topRenderBarrages.length - 1)
            ? (this.topRange[1] - currentTopBarrage.top - currentTopBarrage.height)
            : (this.topRenderBarrages[i + 1].top - currentTopBarrage.top - currentTopBarrage.height);
          if (belowGapHeight >= barrage.height) {
            barrage.top = currentTopBarrage.top + currentTopBarrage.height;
            this.topRenderBarrages.splice(i + 1, 0, barrage);
            inserted = true;
            break;
          }
        }
      }
    } else {
      // 底部弹幕的计算,和顶部弹幕的逻辑一致,所以这里忽略掉
    }
  }

这块的逻辑很简单,就是遍历相邻固定弹幕之间的间隙,判断这些间隙能不能插入新的固定弹幕,如果能插入的话,便计算对应 top 并维护到数组中,逻辑图如下所示:

相关推荐
滕青山23 分钟前
URL编码/解码 核心JS实现
前端·javascript·vue.js
麦麦大数据1 小时前
M004_基于Langchain+RAG的银行智能客服系统设计与开发
typescript·langchain·flask·vue3·faiss·rag
菜鸟小芯1 小时前
【GLM-5 陪练式前端新手入门】第五篇:响应式适配 —— 让个人主页 “见机行事”
前端·人工智能
无名之逆2 小时前
你可能不需要WebSocket-服务器发送事件的简单力量
java·开发语言·前端·后端·计算机·rust·编程
加农炮手Jinx2 小时前
Flutter for OpenHarmony:web_socket_channel 全平台 WebSocket 通信标准库,从原理到鸿蒙实战(3000字深度解析)
android·前端·网络·websocket·flutter·华为·harmonyos
王码码20352 小时前
Flutter for OpenHarmony:web_socket 纯 Dart 标准 WebSocket 客户端(跨平台兼容性之王) 深度解析与鸿蒙
android·前端·websocket·网络协议·flutter·华为·harmonyos
柳杉2 小时前
使用AI从零打造炫酷的智慧城市大屏(开源):React + Recharts 实战分享
前端·javascript·数据可视化
A_B_C_Q3 小时前
StringBuilder 与 StringBuffer的区别
java·前端
洋洋技术笔记3 小时前
vue3+vite+elementplus简单介绍
前端
Joker Zxc3 小时前
【前端基础(Javascript部分)】2、JavaScript的变量和数据类型
开发语言·前端·javascript