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

相关推荐
李鸿耀17 分钟前
仅用几行 CSS,实现优雅的渐变边框效果
前端
码事漫谈37 分钟前
解决 Anki 启动器下载错误的完整指南
前端
im_AMBER1 小时前
Web 开发 27
前端·javascript·笔记·后端·学习·web
蓝胖子的多啦A梦1 小时前
低版本Chrome导致弹框无法滚动的解决方案
前端·css·html·chrome浏览器·版本不同造成问题·弹框页面无法滚动
玩代码1 小时前
vue项目安装chromedriver超时解决办法
前端·javascript·vue.js
訾博ZiBo2 小时前
React 状态管理中的循环更新陷阱与解决方案
前端
StarPrayers.2 小时前
旅行商问题(TSP)(2)(heuristics.py)(TSP 的两种贪心启发式算法实现)
前端·人工智能·python·算法·pycharm·启发式算法
一壶浊酒..2 小时前
ajax局部更新
前端·ajax·okhttp
DoraBigHead3 小时前
React 架构重生记:从递归地狱到时间切片
前端·javascript·react.js
彩旗工作室4 小时前
WordPress 本地开发环境完全指南:从零开始理解 Local by Flywhee
前端·wordpress·网站