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

news2025/1/22 22:54:43

前端基于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": ""
            }
        }
    },
])

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/26045.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

PTA题目 最佳情侣身高差

专家通过多组情侣研究数据发现&#xff0c;最佳的情侣身高差遵循着一个公式&#xff1a;&#xff08;女方的身高&#xff09;1.09 &#xff08;男方的身高&#xff09;。如果符合&#xff0c;你俩的身高差不管是牵手、拥抱、接吻&#xff0c;都是最和谐的差度。 下面就请你写个…

HTTP Only限制XSS盗取cookie

今天继续给大家介绍渗透测试相关知识&#xff0c;本文主要内容是HTTP Only限制XSS盗取cookie。 免责声明&#xff1a; 本文所介绍的内容仅做学习交流使用&#xff0c;严禁利用文中技术进行非法行为&#xff0c;否则造成一切严重后果自负&#xff01; 再次强调&#xff1a;严禁对…

使用 Helm Cli 将 chart 推送到 Harbor

使用 Helm Cli 将 chart 推送到 Harbor 背景问题 努力寻找适用于特定版本的 Harbor 和 Helm 的文档。 我尝试添加我的仓库&#xff08;repo&#xff09; helm repo add harbor https://myharbor.mydomain.com/chartrepo/myproject --username myusername --password mypass…

NXP iMX8M Plus M7核心FreeRTOS开发

By Toradex胡珊逢 Toradex 的 Verdin iMX8M Plus 计算机模块采用 NXP 的 iMX8M Plus 处理器。该 CPU 除了有支持 AI 硬件加速单元的 NPU 外&#xff0c;还配置了一个 M7 微控制器。相比于 iMX 8 QuadMax 处理器上时钟频率为 266MHz 的M4 微控制器&#xff0c; 该M7 的时钟频率…

Java基础39 Object类(节选)

ObjectObject类一、 equals( )● 与 equals的对比&#xff08;☆&#xff09;二、hashCode( )三、toString( )四、finalize( )Object类 Object类&#xff1a;Java中lang包的类&#xff0c;是类层次结构的根类&#xff0c;每个类都使用Object作为超类。所有对象&#xff08;包…

【2015NOIP普及组】T3:求和 试题解析

【15NOIP普及组】求和 时间限制: 1000 ms 内存限制: 131072 KB 【题目描述】 一条狭长的纸带被均匀划分出了n个格子,格子编号从1到n。每个格子上都染了一种颜色colori用[1,m]当中的一个整数表示),并且写了一个数字numberi。 定义一种特殊的三元组:(x,y,z),其中x,…

Java架构该如何进阶?还在东拼西凑的学习?这份进阶指南相信会对你有所帮助,十多位资深大佬独家秘籍一并传授!

如何成为一名架构师? 笔者认为,想成为一名架构师&#xff0c;首先第一点必然是你的技术足够优秀&#xff0c;知识的深度和广度足够&#xff0c;遇到问题能很快从脑海中寻找出最合适的解决之道。其次,架构师会从整体上领导项目&#xff0c;与人打交道必不可少&#xff0c;因此…

区块链baas平台告警方案

前言 在《24*7动态化监管为“链”站岗》中&#xff0c;我们介绍了区块链动态化监控中心&#xff0c;如果说监控的作用是防患于未然&#xff0c;那么告警则是当异常发生时第一时间触发运维人员的关键&#xff0c;可充分降低监控对象异常的时间&#xff0c;最大化降低因异常给区…

深度解析KubeEdge EdgeMesh 高可用架构

摘要&#xff1a;通过高可用特性应用场景、高可用特性使用手册、课题总结、未来展望等四个部分的内容来向大家介绍新版本EdgeMesh的高可用架构。本文分享自华为云社区《KubeEdge EdgeMesh 高可用架构详解|KubeEdge云原生边缘计算社区》&#xff0c;作者&#xff1a;南开大学&am…

【计算机视觉OpenCV基础】实验二 基元检测

实验二 基元检测 计算机视觉OpenCV基础实验合辑&#xff08;实验1234扩展&#xff09; 资源下载地址&#xff1a; https://download.csdn.net/download/weixin_53403301 合辑&#xff1a;&#xff08;加在下载地址后面&#xff09; /87113581 讲义&#xff08;包括理论、图例、…

[附源码]SSM计算机毕业设计火车售票系统论文JAVA

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

antd table 表格滚动高度适配

文章の目录问题产生背景如何解决写在最后问题产生背景 在开发一个后台管理系统&#xff0c;某一页面用到了table表格&#xff0c;产品的需求是&#xff1a; table表格无论展示多少条数据&#xff0c;表头和分页信息要展示出来 此时我们一定首先想到了table表格的 scroll 属性…

【设计模式】原型模式

原型模式属于创建型模式&#xff0c;主要作用是利用一个原型对象的克隆方法&#xff0c;在保证性能的情况下创建多个重复的对象&#xff0c;本质就是通过克隆一个原有的对象来复制出一个新对象。 文章目录原型模式的介绍使用场景原型模式的实现类图实现方法第一步&#xff0c;编…

一文解决 Go 安装和常用环境变量的配置

一文解决 Go 安装和常用环境变量的配置下载链接Windows 操作系统下安装Linux 操作系统下安装安装多个 Go 版本常用环境变量的解释结尾耐心和持久胜过激烈和狂热。 下载链接 官网&#xff1a;https://golang.org/dl中国大陆的镜像站点&#xff1a;https://golang.google.cn/dl/…

跨境电商卖家只青睐亚马逊?其实你不知道,“备胎”早已选好!(Starday)

跨境电商平台作为一个新的国际贸易形态&#xff0c;打破传统贸易形态的方式&#xff0c;将互联网与外贸交易相结合&#xff0c;从而大大降低外贸的运营成本&#xff0c;因此在这两年逐渐燃爆电商行业&#xff0c;让许多行业的人趋之若鹜&#xff0c;从观察中我们可以发现&#…

线性插值方法

插值&#xff0c;通俗来说当在一个离散的事件中&#xff0c;想知道某一个位置确定的值时&#xff0c;就可以利用插值方式计算得到&#xff0c;即利用已知数据估计未知位置数值。插值的方式有很多&#xff0c;下面介绍几种常用的插值方式。 一、最近邻插值(Nearest Neighbour …

矩阵分析:特征值分解都在这里了

矩阵分析&#xff1a;特征值分解前置知识空间变换伸缩旋转对称矩阵对称矩阵对角化正交矩阵向量的基基变换不同基下的向量变换逆矩阵不同基下的空间变换内积的几何意义特征值、特征向量特征值分解代码前置知识 空间变换 伸缩 一个矩阵其实就是一个线性变换&#xff0c;因为一个…

借道元宇宙 一汽-大众揽巡打造沉浸式上市体验

11月18日&#xff0c;一汽-大众以元宇宙科技为载体&#xff0c;举行了行业首场元宇宙游戏形式的“巡梦之旅”线上虚拟发布会&#xff0c;宣布旗下“硬核大五座SUV”揽巡Tavendor正式上市。全新揽巡共提供330TSI 精英巡行版、330TSI 豪华巡逸版、380TSI 四驱R-Line 巡游版、380T…

代码库制作与使用

静态库 假定有以下目录结构&#xff0c;main.c 为src测试文件&#xff0c;所以要调用src目录下的四个文件。我们可以把src打包成一个静态库供main.c使用 tree 命令展示目录结构制作 1.先把目标代码编译生成.o文件 需要包含头文件&#xff0c;否则会报错&#xff0c;使用-I来…

dB family cheat sheet: dB, dBW, dBm, dBi, dBc, etc

目录 1. dB 2. dBi 3. dBW和dBm 4. dBc(Decibel relative to carrier power level) 5. dBuV(dB over 1 microvolt), dBmV(dB over 1 millivolt) 6. 功率、电压、匹配阻抗 7. dBFS 1. dB dB用于表示功率之间的对数比率&#xff0c;所以它没有单位&#xff0c;其定义为&am…