简易实现uni-app弹幕功能(支持小程序,app需要进行调整)

1,590 阅读1分钟

最近项目中遇到了实现直播弹幕的功能,现有的插件要么是太过繁琐,要么是不支持小程序,于是自己手写了一个简易的弹幕功能,技术很菜,不喜可过。话不多说,先上代码。

<template>
  <view
    class="barrage-container"
    :style="{
      width: barrageObject.barrageContainerWidth + 'rpx',
      height: barrageObject.barrageContainerHeight + 'rpx'
    }"
  >
    <view
      v-for="index in orbitalNum"
      :key="index"
      :id="`orbital${index + 1}`"
      class="barrage-orbital"
      :style="{ height: barrageObject.barrageItemContainerHeight + 'px' }"
    >
      <view
        class="barrage-item"
        :style="{
          backgroundColor: item.backgroundColor,
        }"
        v-for="item of orbitalBarrage[`orbital${index + 1}`]"
        :key="item.id"
        :id="item.id"
      >{{ item.content }}</view>
    </view>
  </view>
</template>

<script>
  export default {
    name: 'Barrage',
    data() {
      return {
        barrageObject: {
          barrageContainerWidth: 750,
          barrageContainerHeight: 244,
          barrageContainerTime: 27, //单位s 控制速度,值越大越慢,修改之后需要修改css中动画执行时间
          barrageItemContainerHeight: 62, //一条弹幕容器高度
          maxLength: 30 // 弹幕最大保存数量,超过之后从头部开始删除
        },
        barrageList: [],
        color: {
          message_user: 'rgba(0, 0, 0, 0.3)',
          message_apply: 'rgba(251, 73, 107, 0.7)',
          message_join: 'rgba(144, 139, 226, 0.7)'
        },
        barrageSpeed: 100,
        transformTime: 0,
        orbitalNum: 1, // 弹幕轨道数量
        orbitalListObject: [], // 弹幕轨道状态数组
        orbitalBarrage: {},
        screenWidth: 0,
        moveScreenTime: 0
      }
    },
    props: {
      barrage: {
        required: false,
        type: Object
      }
    },
    created() {
      this.barrageObject = { ...this.barrageObject, ...this.barrage }
    },
    mounted() {
      this.screenWidth = wx.getSystemInfoSync().windowWidth
      this.barrageInit()
    },
    watch: {
      barrage(val) {
        // 合并传入的弹幕相关属性和默认属性
        this.barrageObject = { ...this.barrageObject, ...this.barrage }
      },
      barrageList(val) {
        if (val.length) {
          // 弹幕队列中还存在弹幕就去执行挂载事件
          this.checkOrbital(val)
        } else {
          // 如果弹幕队列已经清空就不执行任何操作
          return
        }
      }
    },
    methods: {
      // 初始化相关数据
      barrageInit() {
        // 初始化速度
        // 计算弹幕轨道数量
        this.orbitalNum = Math.floor(
          this.barrageObject.barrageContainerHeight / this.barrageObject.barrageItemContainerHeight
        )
        //初始化轨道状态
        for (var key = 0; key < this.orbitalNum; key++) {
          this.orbitalListObject.push(true)
          this.orbitalBarrage[`orbital${key + 1}`] = []
        }
      },
      // 生成key
      getUuiD() {
        return (Math.random() + new Date().getTime()).toString(32).slice(0, 16)
      },

      // 弹幕消息是一个队列,用数组模拟队列
      inQuene(val) {
        if (this.barrageList.length >= this.barrageObject.maxLength) {
          this.barrageList.shift()
        }
        if (val instanceof Object) {
          this.barrageList.push({
            id: this.getUuiD(),
            content: val.content,
            backgroundColor: this.color[val.type]
          })
        } else {
          this.barrageList.push({
            id: this.getUuiD(),
            content: val,
            backgroundColor: this.color['message_user']
          })
        }
      },
      outQuene() {
        return this.barrageList.shift() // 拿出第一个元素并返回
      },
      // 选择弹幕轨道
      checkOrbital() {
        // 如果轨道有空-任意一条轨道中最后一条弹幕距离最右边的距离大于0,在这里为了视觉效果设定为大于10
        // 如果所有都有空,那么就采用随机的方式
        if (!this.barrageList.length) {
          return
        }
        if (this.orbitalListObject.includes(true)) {
          let orbitalIndex
          // 如果所有轨道都有空
          if (this.orbitalListObject.every((item) => item)) {
            // 获取一个随机数
            orbitalIndex = Math.floor(Math.random() * this.orbitalNum) + 1
          } else {
            //仅仅是存在空轨道的话就拿出第一个空轨道
            orbitalIndex = this.orbitalListObject.findIndex((item) => item) + 1
          }
          // 调用挂载事件
          this.mountBarrage(orbitalIndex)
        }
        // 如果轨道没空
        return false
      },
      // 挂载弹幕至轨道
      mountBarrage(orbitalIndex) {
        const data = this.outQuene()
        this.orbitalBarrage[`orbital${orbitalIndex}`].push(data)
        this.orbitalBarrage = JSON.parse(JSON.stringify(this.orbitalBarrage))
        // 轨道设置为false
        this.orbitalListObject[orbitalIndex - 1] = false
        var query = wx.createSelectorQuery().in(this)
        let currentBarrageWidth
        this.$nextTick(() => {
          query
            .selectAll(`.barrage-item`)
            .boundingClientRect((rect) => {
              try {
                currentBarrageWidth = Math.ceil(rect.find((item) => item.id === data.id).width)
                let timer1 = setTimeout(() => {
                  clearTimeout(timer1)
                  timer1 = null
                  this.orbitalListObject[orbitalIndex - 1] = true
                  this.checkOrbital()
                }, Math.ceil((currentBarrageWidth / (2000 / 27)) * 1000 + 200))
              } catch (error) {
                this.orbitalListObject[orbitalIndex - 1] = true
                this.orbitalBarrage[`orbital${orbitalIndex}`].pop()
                this.checkOrbital()
              }
            })
            .exec()
        })
        // 销毁元素
        let timer2 = setTimeout(() => {
          clearTimeout(timer2)
          timer2 = null
          this.orbitalBarrage[`orbital${orbitalIndex}`].shift()
          this.orbitalBarrage = JSON.parse(JSON.stringify(this.orbitalBarrage))
        }, 21 * 1000) // 多3秒,容错
      }
    }
  }
</script>

<style>
  .barrage-container {
    display: flex;
    margin: auto;
    overflow: hidden;
    flex-direction: column;
    justify-content: space-between;
  }

  .barrage-orbital {
    position: relative;
    height: 62rpx;
  }

  .barrage-item {
    position: absolute;

    /* 2px容错,报错的时候直接销毁元素用,这样视觉上看不出来 */
    left: calc(100% + 2px);
    display: inline-block;
    height: 62rpx;
    padding: 0 30rpx;
    line-height: 62rpx;
    color: #fff;
    white-space: nowrap;
    border-radius: 40rpx;

    /* 1.5倍时间 */
    animation: move 27s linear;
  }
  @keyframes move {
    from {
      /* left: 100%; */
      transform: translate(0);
    }

    to {
      /* left: -300%; */
      transform: translate(-2000px);
    }
  }
</style>

*通过外部传参的方式可以修改barrageObject对象中定义的参数,同时也可以直接修改组件内的参数来达到相应的效果。弹幕轨道数量是根据弹幕显示的范围和单个弹幕的高度计算得出的,在外部通过调用inQuene方法来添加弹幕,同时为弹幕设置了最大缓存数量为三十条,可根据业务具体需求进行更改,这里设置了三种弹幕类型,不同的弹幕类型对应不同的背景颜色,可以自己修改,删除与新增。同时,在高版本ios手机中因为使用wx获取dom的api会出现偶然性报错,所以ios手机会存在偶尔被吞弹幕的情况,概率较低。 *