vue+node.js手把手教你搭建一个直播平台(七)实现弹幕轨道功能

如果生活有奇迹,那一定是努力的轨迹!
复制代码

Hello,大家好,我是小羽同学,一个平凡而又不甘于平凡的前端开发工程师~

嘿嘿嘿,好兄弟们,是不是很意外,该系列文章突然又更新了!!!!

img

其实吧有两个原因。

一是阿宽连小册都出来了,而我的第一篇系列文差点太监了,使得我备受打击。

二则是看到小伙伴们的学习热情,没想到我断更了大半年,还是有不少小伙伴找上来和小羽聊聊天,小羽还是挺开心的 。虽然小羽目前是转了react技术栈,但是为了不辜负小伙伴们对该系列文章的‘期盼’吧,就先把主流程完善一下吧。

image-20210608214725588

image-20210608214801277

image-20210608214854764

image-20210608214950174

image-20210608215140613

image-20210608215243052

本期的话主要就是和小伙伴们聊聊如何实现弹幕轨道

1.什么是弹幕轨道?

在上一期中,咱们已经简单的实现了弹幕的功能,但是存在这么一个问题,就是所有的弹幕都在同一条水平线上播放的。当弹幕的数量稍微多一点点,就看不清弹幕中的内容。那咱们有没有方法实现类似于直播平台的那种可以全屏幕的弹幕播放,而又尽量不会影响到其他的弹幕呢?

答案是有的,小羽称这种方式为弹幕轨道

如下图所示,就是将咱们直播视屏的可视区域动态划分为几个轨道。然后咱们将咱们的弹幕随机推送到这些弹幕轨道中,然后弹幕按顺序从右侧移动到左侧,即可完成咱们的弹幕功能。

image-20210608221842087

2.新增弹幕轨道组件

src/views/live中新增barrageStream.vue

image-20210608235910112

<!--
 * @Description: 
 * @Author: 小羽
 * @LastEditors: 小羽
 * @Date: 2021-06-07 21:53:30
 * @LastEditTime: 2021-06-08 23:53:25
-->
<template>
  <div class="barrage-stream" ref="barrageStream">
    
  </div>
</template>

<script>
import { mapState, mapMutations } from "vuex";
export default {
  name: "barrageStream",
  data() {
    return {
      barrageStreamRect: {},
      barrageStreamList: [],// 弹幕轨道
      barrageStreamListNum: null,// 弹幕轨道数量
    };
  },
  computed: {
    ...mapState({
      barrageMsgList: (state) => state.barrage.barrageMsgList,
    }),
  },
  methods: {
  },
  watch: {
  },
  mounted() {
  },
};
</script>

<style lang="less" scoped>
.barrage-stream {
  position: absolute;
  width: 100%;
  height: 100%;
  overflow: hidden;
  z-index: 9999;
  .barrage-block {
    z-index: 1;
    position: relative;
    height: 40px;
    //border-bottom: #fff 1px solid;
    width: 100%;
    color: #fff;
    &-item {
      position: absolute;
      display: inline-block;
      width: 200px;
      animation: barrage 5s linear;
      animation-fill-mode: forwards;
    }
  }
}
@keyframes barrage {
  from {
    left: 100%;
    transform: translateX(0);
  }
  to {
    left: 0;
    transform: translateX(-200%);
  }
}
</style>
复制代码

3.动态计算弹幕轨道的数量

img

为啥要动态计算弹幕轨道的数量呢?我直接写死轨道的数量,简单快捷,它不香吗???

好兄弟们,想一下,咱们现在屏幕的种类是不是很多,然后分辨率的大小也不一致吧?不同分辨率显示的弹幕轨道数量总该是不一样的吧?此外,咱们的用户也不一定是全屏来观看直播的。说不定就喜欢开个小窗口来观看呀。为了解决这类问题,咱们就需要动态的计算弹幕轨道的数量了。

在计算弹幕轨道前,咱们先了解一个api——getBoundingClientRect()

该方法会返回元素的大小以及其相对于视窗的位置。

标准盒子模型(box-sizing: content-box),元素的尺寸等于width/height + padding + border-width的总和。在弹性盒子模型(box-sizing: border-box),元素的的尺寸等于 width/height

简单的通过ref/id获取咱们的直播视屏区域的DOM结构,然后调用getBoundingClientRect()方法,在控制台中输出。可以看到会得到DOM结构的宽高以及在视窗中的位置信息

image-20210608234240232

再结合弹幕的高度,以及底部播放条的显示区域高度,咱们不难得到这么一个方法

methods: {
    // 获取弹幕轨道播放流的数量
    getBarrageStreamList() {
      console.log(this.$refs.barrageStream.getBoundingClientRect());
      // 获取dom元素的信息
      this.barrageStreamRect = this.$refs.barrageStream.getBoundingClientRect();
      // 计算轨道的数量
      this.barrageStreamListNum = Math.floor(
        (this.barrageStreamRect.height - 100) / 36
      );
      // 初始化轨道
      this.barrageStreamList = [];
      for (let i = 0; i < this.barrageStreamListNum; i++) {
        this.barrageStreamList.push([]);
      }
      console.log(this.barrageStreamList);
    },
  },
复制代码

好了,现在方法有了,那咱们该什么时候调用该方法呢?

初始化的时候该调用到吧?那就加上。

mounted() {
    this.getBarrageStreamList();
  },
复制代码

但是现在问题来了,有些用户就喜欢捣蛋,就喜欢随意缩放浏览器的大小,这样一来咱们初始化时候的轨道数量就不对了,这时候怎么搞?

img

其实很简单,咱们只需要监听窗口的大小变化就好啦。

那监听窗口的变化需要用到什么方法?

window.addEventListener("resize",function())

那既然都添加了监听,那咱们离开该组件的时候是不是也该把这个监听给移除掉?

移除用什么方法?

window.removeEventListener("resize",function())

ok,那咱们很容易就可以得到以下这段代码

mounted() {
    this.getBarrageStreamList();
    window.addEventListener("resize", this.getBarrageStreamList);
  },
  beforeDestroy() {
    window.removeEventListener("resize", this.getBarrageStreamList);
  },
复制代码

4.随机分配弹幕到弹幕轨道中

问:什么时候需要分配弹幕到轨道中?

答:在弹幕新增的时候。

问:怎么知道弹幕中数据发生变化?

答:监听vuex中的弹幕数组

所以我们对弹幕的分配操作应该就是在监听中实现的。

插入一个vue面试题:vue中怎么监听一个数组的变化?以及怎么实现初始化监听?

答案很简单,js 中会有基本数据类型和引用数据类型,而watch默认是监听基本数据类型的变化,而对引用数据类型只会监听其对应的存储地址,既不会对数组里面的数据进行监听。当咱们想对数组里面的数据进行监听时,添加deep:true即可实现深度监听。而初始化监听则是添加另外一个参数immediate:true即可

然后我们是怎么获取随机轨道呢?

可以结合Math.randon()方法,然后再将弹幕push到咱们的随机轨道中。

为了节省后续渲染上的开销,我们显示过的弹幕是不是不该继续存在于我们的DOM中?

所以结合setTimeout将那些渲染过的弹幕移除。

最后得到以下代码

watch: {
    barrageMsgList: {
      handler(newval) {
        if (newval.length) {
          // 获取随机轨道下标
          let randomNum = Math.floor(Math.random() * this.barrageStreamListNum);
          // 将弹幕推送到随机轨道中
          this.barrageStreamList[randomNum].push(newval[newval.length - 1]);
          // 延时5s后,删除该弹幕。(时间与弹幕的播放时间有关)
          setTimeout(() => {
            this.barrageStreamList[randomNum].shift();
          }, 5000);
        }
      },
      deep: true,      
    },
  },
复制代码

5.渲染弹幕

修改template中的代码,保存。

<template>
  <div class="barrage-stream" ref="barrageStream">
    <div
      class="barrage-block"
      v-for="(stream, streamIndex) of barrageStreamList"
      :key="'barrageStreamList' + streamIndex"
    >
      <div
        class="barrage-block-item"
        v-for="(item, index) of stream"
        :key="'barrage-block-item' + index"
      >
        {{ item.msg }}
      </div>
    </div>
  </div>
</template>
复制代码

弹幕轨道(错误)

打开页面,输入弹幕,弹幕轨道出来了

但是小伙伴们,仔细观察后悔发现有一些弹幕还没播放完就消失了,这是怎么回事呀?

小羽在这里将结合一道vue的面试题进行解答

v-for中为什么不建议使用index做key值?

主要是因为数组操作会导致显示异常。

让我们来结合我们的弹幕来进行讲解把。

如下图所示,主要是因为key值是绑定了index值,而某些css样式却是通过index进行绑定的。当咱们删除数组中的某个值时,key值和index值重新绑定了我们的弹幕内容,样式也随之变化,从而引起这种显示异常。

image-20210610001033903

要解决这个问题其实也很简单,就是不用index作为key值即可。

<template>
  <div class="barrage-stream" ref="barrageStream">
    <div
      class="barrage-block"
      v-for="(stream, streamIndex) of barrageStreamList"
      :key="'barrageStreamList' + streamIndex"
    >
      <div class="barrage-block-item" v-for="item of stream" :key="item.id">
        {{ item.msg }}
      </div>
    </div>
  </div>
</template>
复制代码

修改发送弹幕的方法(添加一个随机数作为id即可),路径src/views/live/barrage.vue

//发送弹幕
    chatLiveRoom() {
      console.log(this.currentUser);
      if (!this.currentUser.id) {
        this.$Message.error("请登录后再发言~");
      } else if (this.battageMsg) {
        let data = {
          room: this.livingRoom,
          func: "chatLiveRoom",
          data: {
            id: common.getCode(8),
            user: this.currentUser.name,
            msg: this.battageMsg,
          },
        };
        this.$sockBarrage.roomChat(data);
        this.battageMsg = "";
      } else {
        this.$Message.error("请输入弹幕内容!");
      }
    },
复制代码

小结

距离小羽上次写本系列文已经是半年以前的事情了。

真的挺久了,久到小羽都忘了代码的内容了。

外加来到了新公司,工作还是比较忙的。

但还是抽空看了下代码和前几期的内容,终于把这期的文章啃了出来。

想想还是挺唏嘘的,哈哈哈。

如果看这系列文章后,感觉有收获的小伙伴们可以点赞+关注哦~

img

如果想和小羽交流技术可以加下wx,也欢迎小伙伴们来和小羽唠唠嗑,嘿嘿~

ps:纯原创,转载请标明出处

最后奉上github链接,可以的话帮忙点个小星星,谢谢老铁~

前端:github.com/sulgweb/myl…

后端:github.com/sulgweb/myl…

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享