@@ -244,48 +244,65 @@ Page({
this . startDanmakuScheduler ( cache . danmaku ) ;
} ,
// 启动弹幕调度:每行同一时刻只播一条,前一条飘完再出下一条(循环)
// 启动弹幕调度:每行一个发射定时器,前一条完全进入屏幕后就出下一条,
// 同一行屏上保持 2~3 条,首尾相接不压着;各行速度不同、同行弹幕速度也错落
startDanmakuScheduler ( rows ) {
this . stopDanmakuScheduler ( ) ;
this . danmakuRows = rows || [ ] ;
this . danmakuTimers = [ ] ;
this . danmakuSeq = 0 ; // 全局自增序号,保证每条 key 唯一
if ( ! this . danmakuRows . length ) {
this . setData ( { currentDanmaku : [ ] } ) ;
return ;
}
const GAP = 1.5 ; // 同一行前一条飘完后,间隔再出下一条(秒)
// 屏上目标条数:同一行同时约 2.5 条
const ON _SCREEN = 2.5 ;
// 弹幕完全进入屏幕约需飘过总路程的 30%(不压着前一条的最小间隔)
const ENTER _RATIO = 0.3 ;
const MIN _INTERVAL = 3 ; // 同一行相邻两条最小间隔(秒)
// 每行独立调度:row 行的第 idx 条播完后,隔 GAP 秒播第 idx+1 条
const playRow = ( row , idx ) => {
// 往某行发射一条弹幕,返回该条的 duration(供调度下一条用)
const emit = ( row , idx ) => {
const rowData = this . danmakuRows [ row ] ;
if ( ! rowData ) return ;
if ( ! rowData ) return 0 ;
const item = rowData . list [ idx % rowData . list . length ] ;
// 更新该行当前弹幕(用行号作为 key 保证 setData 局部刷新)
this . setData ( {
[ ` currentDanmaku[ ${ row } ] ` ] : {
key : ` ${ row } - ${ idx % rowData . list . length } - ${ Date . now ( ) } ` ,
const seq = ++ this . danmakuSeq ;
const danmaku = {
key : ` d ${ seq } ` ,
id : item . id ,
content : item . content ,
author : item . author ,
type : item . type ,
top : rowData . top ,
duration : rowData . duration ,
} ,
} ) ;
// 前一条飘完 + 间隔后,播下一条
const timer = setTimeout ( ( ) => {
playRow ( row , idx + 1 ) ;
} , ( rowData . duration + GAP ) * 1000 ) ;
this . danmakuTimers [ row ] = timer ;
duration : item . duration ,
} ;
// 追加到屏上弹幕列表(构造新数组,避免索引冲突)
this . setData ( { currentDanmaku : this . data . currentDanmaku . concat ( danmaku ) } ) ;
// 动画播完(飘到屏外)后,把这条从列表移除
const removeTimer = setTimeout ( ( ) => {
const list = this . data . currentDanmaku . filter ( ( d ) => d . key !== danmaku . key ) ;
this . setData ( { currentDanmaku : list } ) ;
} , item . duration * 1000 ) ;
this . danmakuTimers . push ( removeTimer ) ;
return item . duration ;
} ;
// 初始化 currentDanmaku 数组长度,并启动每行
this . setData ( { currentDanmaku : new Array ( this . danmakuRows . length ) . fill ( null ) } ) ;
this . danmakuRows . forEach ( ( _ , row ) => {
// 每行独立调度:按刚发射这条的速度决定下一条间隔,循环
this . danmakuRows . forEach ( ( rowData , row ) => {
let idx = 0 ;
const loop = ( ) => {
const duration = emit ( row , idx ) ;
idx += 1 ;
// 下一条间隔:屏上保持约 2.5 条(duration/2.5),
// 但不小于“完全进入”时间(duration*0.3,不压着)和最小间隔
const interval = Math . max ( duration / ON _SCREEN , duration * ENTER _RATIO , MIN _INTERVAL ) ;
const timer = setTimeout ( loop , interval * 1000 ) ;
this . danmakuTimers . push ( timer ) ;
} ;
// 各行错开启动,避免 4 行同时从右侧涌出
const startTimer = setTimeout ( ( ) => playRow ( row , 0 ) , row * 1200 ) ;
const startTimer = setTimeout ( loop , row * 1500 ) ;
this . danmakuTimers . push ( startTimer ) ;
} ) ;
} ,
@@ -296,18 +313,21 @@ Page({
this . danmakuTimers . forEach ( ( t ) => clearTimeout ( t ) ) ;
this . danmakuTimers = [ ] ;
}
// 清空屏上弹幕,避免恢复时残留未播完的项
if ( this . data . currentDanmaku && this . data . currentDanmaku . length ) {
this . setData ( { currentDanmaku : [ ] } ) ;
}
} ,
// 构建弹幕:共 4 行(行 0-2 树桩处三行,行 3 顶部置顶轮播下方)
// 按行分组返回,由 JS 定时器控制:同一行一条完毕后再出下一条,不重叠
// 每行速度不同、同行弹幕速度也带随机错落,避免太整齐;前一条完全进入后出下一条
buildDanmaku ( list ) {
const DURATION _BOTTOM = 30 ; // 树桩处一条飘完全屏的秒数(慢速)
const DURATION _TOP = 24 ; // 顶部行稍快一点
// 行位置(屏幕百分比):
// - 行3 顶部行紧贴置顶轮播下方(稍有空隙)
// - 行0-2 树桩处三行,第一条68 %,三行紧凑(间隔5 %)
const ROW _TOPS = [ 68 , 73 , 78 , 12 ] ;
const ROW _DURATIONS = [ DURATION _BOTTOM , DURATION _BOTTOM , DURATION _BOTTOM , DURATION _TOP ] ;
// - 行0-2 树桩处三行,第一条70 %,三行紧凑(间隔3 %)
const ROW _TOPS = [ 70 , 73 , 76 , 12 ] ;
// 每行基准速度(秒/次):各行不同,避免整齐划一
const ROW _BASE _DURATION = [ 26 , 33 , 40 , 22 ] ;
// 轮流分配到 4 行
const rows = [ [ ] , [ ] , [ ] , [ ] ] ;
@@ -318,12 +338,13 @@ Page({
// 每行存该行的所有弹幕(带位置/时长),供定时器轮流播放
return rows . map ( ( rowList , row ) => ( {
top : ROW _TOPS [ row ] ,
duration : ROW _DURATIONS [ row ] ,
list : rowList . map ( ( w ) => ( {
list : rowList . map ( ( w , i ) => ( {
id : w . id ,
content : w . content ,
author : w . author || '匿名' ,
type : w . type ,
// 同行弹幕速度也带 ±20% 错落(seeded 保证每次渲染一致)
duration : Math . round ( ROW _BASE _DURATION [ row ] * ( 0.8 + seeded ( w . id != null ? w . id : i , row + 7 ) * 0.4 ) ) ,
} ) ) ,
} ) ) . filter ( ( r ) => r . list . length > 0 ) ;
} ,