diff --git a/mini/pages/wish-tree/wish-tree.js b/mini/pages/wish-tree/wish-tree.js index d45a80c..d1eea70 100644 --- a/mini/pages/wish-tree/wish-tree.js +++ b/mini/pages/wish-tree/wish-tree.js @@ -244,48 +244,61 @@ Page({ this.startDanmakuScheduler(cache.danmaku); }, - // 启动弹幕调度:每行同一时刻只播一条,前一条飘完再出下一条(循环) + // 启动弹幕调度:每行一个发射定时器,前一条完全进入屏幕后就出下一条, + // 同一时刻屏上可有多条,但首尾相接不重叠(自然弹幕效果) 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; // 同一行前一条飘完后,间隔再出下一条(秒) + // 弹幕完全进入屏幕大约需要飘过总路程的 35%(从右屏外到尾部越过右边缘) + // 此时下一条即可开始从右侧进入,与前一条首尾相接不重叠 + const ENTER_RATIO = 0.35; + const MIN_INTERVAL = 4; // 同一行相邻两条最小间隔(秒),避免过密 - // 每行独立调度:row 行的第 idx 条播完后,隔 GAP 秒播第 idx+1 条 - const playRow = (row, idx) => { + // 往某行发射一条弹幕 + const emit = (row, idx) => { const rowData = this.danmakuRows[row]; if (!rowData) return; const item = rowData.list[idx % rowData.list.length]; - // 更新该行当前弹幕(用行号作为 key 保证 setData 局部刷新) - this.setData({ - [`currentDanmaku[${row}]`]: { - key: `${row}-${idx % rowData.list.length}-${Date.now()}`, - 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; + 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, + }; + // 追加到屏上弹幕列表(构造新数组,避免索引冲突) + 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 }); + }, rowData.duration * 1000); + this.danmakuTimers.push(removeTimer); }; - // 初始化 currentDanmaku 数组长度,并启动每行 - this.setData({ currentDanmaku: new Array(this.danmakuRows.length).fill(null) }); - this.danmakuRows.forEach((_, row) => { + // 每行独立调度:每隔 interval 秒发一条,循环 + this.danmakuRows.forEach((rowData, row) => { + const interval = Math.max(rowData.duration * ENTER_RATIO, MIN_INTERVAL); + let idx = 0; + const loop = () => { + emit(row, idx); + idx += 1; + 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,17 +309,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 定时器控制:同一行一条完毕后再出下一条,不重叠 + // 按行分组返回,由 JS 定时器控制:前一条完全进入屏幕后就出下一条,首尾相接不重叠 buildDanmaku(list) { const DURATION_BOTTOM = 30; // 树桩处一条飘完全屏的秒数(慢速) const DURATION_TOP = 24; // 顶部行稍快一点 // 行位置(屏幕百分比): // - 行3 顶部行紧贴置顶轮播下方(稍有空隙) - // - 行0-2 树桩处三行,第一条68%,三行紧凑(间隔5%) - const ROW_TOPS = [68, 73, 78, 12]; + // - 行0-2 树桩处三行,第一条70%,三行紧凑(间隔3%) + const ROW_TOPS = [70, 73, 76, 14]; const ROW_DURATIONS = [DURATION_BOTTOM, DURATION_BOTTOM, DURATION_BOTTOM, DURATION_TOP]; // 轮流分配到 4 行 diff --git a/mini/pages/wish-tree/wish-tree.wxml b/mini/pages/wish-tree/wish-tree.wxml index dcb2cae..9ae0eea 100644 --- a/mini/pages/wish-tree/wish-tree.wxml +++ b/mini/pages/wish-tree/wish-tree.wxml @@ -45,14 +45,14 @@ - + - - {{item.content}} · {{item.author}} - + {{item.content}} · {{item.author}}