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 并维护到数组中,逻辑图如下所示:

相关推荐
我要洋人死1 小时前
导航栏及下拉菜单的实现
前端·css·css3
科技探秘人1 小时前
Chrome与火狐哪个浏览器的隐私追踪功能更好
前端·chrome
科技探秘人1 小时前
Chrome与傲游浏览器性能与功能的深度对比
前端·chrome
JerryXZR1 小时前
前端开发中ES6的技术细节二
前端·javascript·es6
七星静香1 小时前
laravel chunkById 分块查询 使用时的问题
java·前端·laravel
q2498596931 小时前
前端预览word、excel、ppt
前端·word·excel
小华同学ai1 小时前
wflow-web:开源啦 ,高仿钉钉、飞书、企业微信的审批流程设计器,轻松打造属于你的工作流设计器
前端·钉钉·飞书
Gavin_9152 小时前
【JavaScript】模块化开发
前端·javascript·vue.js
懒大王爱吃狼3 小时前
Python教程:python枚举类定义和使用
开发语言·前端·javascript·python·python基础·python编程·python书籍
逐·風7 小时前
unity关于自定义渲染、内存管理、性能调优、复杂物理模拟、并行计算以及插件开发
前端·unity·c#