基于sass变量实现
原理同上
可配置、但不可超过容器高度;默认填充满屏幕
默认取值方式:容器高度/每行弹道高度 (向下取整 ,下方代码可查看 barrageNum 变量相关)
实现思想:将弹道分解为栅格,计算格子数量:播放器宽度/字体宽度 = 格子数量;barrageSpeed 变量控制的是一个文本走一个格子需要的时间;
由此得出: ( 此处提出的为实现思想,内容详细的秒/毫秒记得转换哦,单位不同意容易出问题 )
所需动画时间 = (内容实际宽度 + 容器宽度)/ 字体大小 * 走一个格子需要的时间
弹幕执行完时间(用于销毁弹幕DOM)=所需动画时间 + 实际发送弹幕时间
下次可向弹道发送消息时间(避免弹幕堆叠) = 时间戳 +(内容实际宽度 + 内外边距)/ 字体大小 * 走一个格子需要的时间 )
下方代码可查看 barrageSpeed 变量相关,手动的修改一下值看下效果 更直观
基于sass动态配置变量
实现机制很多 最简单的就是直接v-if的你的弹幕容器
配置弹幕速度处以说明解决机制
弹幕视频
基于 Vue3 SASS
<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>
.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;
}
}
}
}
}
//#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": ""
}
}
},
])