likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 3

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

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

Github:github.com/feiafei27/f…

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

1:固定弹幕 left 的计算

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

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 的计算

fly-barrage 前端弹幕库(4):顶部、底部弹幕的设计与实现 这里我把整个 Canvas 分成上下等高的两部分,上半部分 [0, mid] 只能渲染顶部弹幕,下半部分 [mid, full] 只能渲染底部弹幕。

2-1:重要属性

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

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

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

2-2:重要方法

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

/**
 - 获取当前应该渲染的固定弹幕
 - @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 方法的源码如下所示:

/**
   * 封装通用的工具方法
   * @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 并维护到数组中,逻辑图如下所示: fly-barrage 前端弹幕库(4):顶部、底部弹幕的设计与实现