前端、vue、Vue3弹幕实现;前端CSS实现弹幕

前端基于CSS3实现弹幕

基于CSS3动画
  1. 根据 Google Developer,渲染线程分为 主线程 (main thread) 和 合成线程 (compositor thread)。如果 CSS 动画只是改变 transforms 和 opacity,这时整个 CSS 动画得以在 合成线程 完成(而JS动画则会在 主线程 执行,然后触发合成线程进行下一步操作),在 JS 执行一些昂贵的任务时,主线程繁忙,CSS 动画由于使用了合成线程可以保持流畅
  2. 在许多情况下,也可以由合成线程来处理 transforms 和 opacity 属性值的更改
  3. 对于帧速表现不好的低版本浏览器,CSS3可以做到自然降级,而JS则需要撰写额外代码
  4. CSS动画有天然事件支持(TransitionEnd、AnimationEnd,但是它们都需要针对浏览器加前缀),JS则需要自己写事件
  5. 如果有任何动画触发绘画,布局或两者,则需要 “主线程” 才能完成工作。 这对于基于 CSS 和 JavaScript 的动画都是如此,布局或绘制的开销可能会使与 CSS 或 JavaScript 执行相关的任何工作相形见绌,这使得问题没有实际意义
    选择原因参考自:点点点
弹幕可配置内容大小颜色

基于sass变量实现

弹幕可自定义弹幕内容

在这里插入图片描述

弹幕可插入图片

原理同上

弹幕可配置弹道数量、自适应播放器高度

可配置、但不可超过容器高度;默认填充满屏幕
默认取值方式:容器高度/每行弹道高度 (向下取整 ,下方代码可查看 barrageNum 变量相关)

弹幕可配置弹幕速度

实现思想:将弹道分解为栅格,计算格子数量:播放器宽度/字体宽度 = 格子数量;barrageSpeed 变量控制的是一个文本走一个格子需要的时间;
由此得出: ( 此处提出的为实现思想,内容详细的秒/毫秒记得转换哦,单位不同意容易出问题
所需动画时间 = (内容实际宽度 + 容器宽度)/ 字体大小 * 走一个格子需要的时间
弹幕执行完时间(用于销毁弹幕DOM)=所需动画时间 + 实际发送弹幕时间
下次可向弹道发送消息时间(避免弹幕堆叠) = 时间戳 +(内容实际宽度 + 内外边距)/ 字体大小 * 走一个格子需要的时间 )
下方代码可查看 barrageSpeed 变量相关,手动的修改一下值看下效果 更直观

弹幕可配置弹道密度、弹道间距

基于sass动态配置变量

弹幕可配置弹幕开关

实现机制很多 最简单的就是直接v-if的你的弹幕容器

弹幕已处理弹幕内容堆叠问题 (计算发送机制)

配置弹幕速度处以说明解决机制

先看效果在这里插入图片描述

弹幕视频

直接上代码!!!!CV走即可

基于 Vue3 SASS

HTML代码处

      <!-- 播放器容器 -->
      <div class="anchorVideo" id="anchorVideoContent"  >
        <!-- 视频 -->
        <video class="anchorVideo w100" controls style="height:300px" id="anchorVideo"></video> 
        <!-- 弹幕 -->
        <div class="anchor-barrage">
          <!-- 直播弹道 -->
          <ul  class="barrage-trajectory">
            <li v-for="(a,index) in barrageNum">
              <template v-if="trajectoryData[index]">
                <p 
                  v-for="item of trajectoryData[index]" 
                  v-autoDestroy="{item,$Index:index}"
                  :style="{
                    '--animationTime':item.animationTime,
                    '--msgWidth':item.msgWidth,
                    color:item.testColor
                  }"
                  :key="item.customKey"
                  :customKey="item.customKey"
                >
                  {
   
   {item.content.content}}  
                </p>
              </template> 
            </li>
          </ul>
        </div>
      </div>  

CSS代码

    .anchorVideo {
    
    
      width: 100%;
      position: relative;
      .anchor-barrage{
    
    
        position: absolute;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        background: #dd818178;
        overflow: hidden;
        @keyframes scrollTo {
    
     
            0% {
    
      
              
            } 
            100% {
    
      
              right: 100%;
              // transform: translateX(-100%);
            }
        }
        .barrage-trajectory{
    
    
          max-height: 100%;
          width: 100%;
          box-sizing: border-box;
          padding: 12px 0;
          & > li {
    
    
            width: 100%;
            height: v-bind('barrageHeight');
            display: flex;
            justify-content: start; 
            position: relative;
            & > p{
    
     
              position: absolute;; 
              min-width: var(--msgWidth);
              font-size: v-bind('barrageFontSize');
              display: flex;
              align-items: center;;
              color:blue;
              // transform: translateX(-100%);
              white-space: nowrap;
              text-shadow: 2px 2px 3px rgb(248, 81, 20); // 文字阴影  

              margin-right: v-bind('barrageGap');
              right: calc(1px - var(--msgWidth) -  v-bind('barrageGap'));
              animation: scrollTo linear var(--animationTime) 1; //动画
              animation-fill-mode: forwards;
	            animation-timing-function:linear; 
            }
          }
        }
      }
    }

script代码

//#region 弹道变量声明
  // 弹道数据
  const trajectoryData = reactive({
    
    })
  // 直播容器宽度
  const videoWidth = ref('') 
  // 直播容器高度
  const videoHeight = ref('') 
  // 弹道数量
  const barrageNum = ref(0) 
  // 弹道高度 ( 一行多高 )
  const barrageHeight = ref('32px') 
  // 弹幕字体大小 
  const barrageFontSize = ref('24px') 
  // 弹幕速度
  // const barrageSpeed = ref('5s') 
  const barrageSpeed = ref(0.1) 
  // 弹幕间距
  const barrageGap = ref('32px') 
  // 弹幕ID
  const barrageId = ref(0) 
//#endregion

//#region 弹幕相关 
  function initBarrage(){
    
     
    // 计算容器高度
    videoWidth.value = document.getElementById('anchorVideoContent').clientWidth + 'px'
    videoHeight.value = document.getElementById('anchorVideoContent').clientHeight - 32/* 减去padding */ + 'px'
    // 求出最大弹道数量
    barrageNum.value = parseInt(parseInt(videoHeight.value) / parseInt(barrageHeight.value)) 
    if(Object.keys(trajectoryData).length > 0){
    
     
      deleteData(1)
      function deleteData(i){
    
    
        if(trajectoryData[i] && barrageNum.value<i){
    
    
          delete trajectoryData[i]
          deleteData(i++)
        }
      }
    }else{
    
     
      // 初始化弹道数据
      for(let i = 1;i <= barrageNum.value;i++){
    
     trajectoryData[i] = []} 
    }
  }
  // 弹幕发送
  function sendBarrage(msgs){
    
    
    if(!Array.isArray(msgs))return;  
    for(let i = 1;i <= barrageNum.value;i++){
    
      
      if(msgs.length == 0)return;
      let msg = msgs[0]  
      // 计算消息长度
      let msgLength = msgs[0].content.content.length 
      let chinaText = (msgs[0].content.content || '').match(/[\u4e00-\u9fa5]/g) || ''
      msgLength = (msgLength-chinaText.length)/2 + chinaText.length 
      // 本条弹幕的总长度
      let currentMsgLength = parseInt(barrageGap.value) + msgLength*parseInt(barrageFontSize.value) 
      // 计算动画时间 
      let animationTime = ((currentMsgLength + parseInt(videoWidth.value))/parseInt(barrageFontSize.value) * barrageSpeed.value).toFixed(2);
 
      msg = {
    
    
        ...msg,
        animationTime:animationTime+'s',
        msgWidth:msgLength * parseInt(barrageFontSize.value) + 'px',// 弹幕宽度
      }
      
      let nextSendTime = 0
      if(trajectoryData[i].length == 0 || trajectoryData[i].at(-1).nextSendTime < new Date().getTime()){
    
    
        // 下次可发送弹幕时间
        nextSendTime = ((currentMsgLength/parseInt(barrageFontSize.value)) * barrageSpeed.value).toFixed(2)*1000 
        msg.nextSendTime = new Date().getTime() + nextSendTime   
        // 可销毁时间
        msg.destroyTime = new Date().getTime() + parseInt(animationTime)*1000

        msg.customKey = 'customKey' + barrageId.value++  
        
        msg.testColor = getRandomColor()
        
        trajectoryData[i].push(msg)
        utilMsg()
        return sendBarrage(msgs) 
      } 
      // 若循环后仍无可用弹道
      if(i == barrageNum.value){
    
    
        return setTimeout(()=>{
    
      
          sendBarrage(msgs)  
        },500) 
      }
      // 
    }  
    // 删除一条
    function utilMsg(){
    
     msgs.splice(0,1) }
  }
  // 定时清理弹道数据(也可自定义指令实现清理弹道数据方法、后续完善补充上)
  function clearTData(){
    
    
    for(let i = 1;i <= barrageNum.value;i++){
    
     
      trajectoryData[i] = trajectoryData[i].filter(item=>{
    
    
          return item.destroyTime > new Date().getTime()
      }) 
    }
    setTimeout(()=>{
    
    
      clearTData()
    },3000 ) 
  }      
  /**
    * 获取随机颜色  十六进制
    */
  function getRandomColor(){
    
    
    return '#' + Math.floor( Math.random() * 0xffffff ).toString(16)
  }

//#endregion

// 初始化弹幕
initBarrage()

//#region 页面事件监听 (有需要就加上该事件监听 无需要就删除)
  window.addEventListener("resize",resizeScreen)
  // 监听屏幕缩放
  function resizeScreen(){
    
    
    if(window.resizeScreenTimer)clearTimeout(window.resizeScreenTimer);

    window.resizeScreenTimer = setTimeout(()=>{
    
     
      initBarrage()
    },200) 
  }
//#endregion

调用(可手动调用/监听回调)

   // 发送弹幕 (弹幕目前传参格式如下代码所示、也可根据自己需求修改格式简化/添加新字段)
   sendBarrage([
    {
    
    
         content:{
    
    
            "content": "这是要发的消息内容",
            "extra": "",
            "mentionedInfo": {
    
    
                "userIdList": [],
                "type": 1,
                "mentionedContent": ""
            }
        }
    },
    {
    
    
         content:{
    
    
            "content": "这也是消息内容哦",
            "extra": "",
            "mentionedInfo": {
    
    
                "userIdList": [],
                "type": 1,
                "mentionedContent": ""
            }
        }
    },
])

猜你喜欢

转载自blog.csdn.net/lys20000913/article/details/127976370