tweak(wish-tree): 弹幕改为自然首尾相接,不再一行一条
Publish Mini Program Dev Version / publish (push) Successful in 1m3s

- 顶部行 12%→14%,树桩3行 68/73/78→70/73/76(间隔3%)
- 核心:从'一行同一时刻只一条'改为'前一条完全进入屏幕后就出下一条'
  同一时刻屏上可有多条,但首尾相接不重叠,更接近真实弹幕
- 每行发射定时器间隔=duration*0.35(完全进入屏幕约需飘总路程35%)
- 弹幕播完后从列表移除,stop时清空屏上弹幕避免残留
This commit is contained in:
gouki
2026-08-10 21:28:47 +00:00
parent f468cf4b94
commit a45004bfb2
2 changed files with 52 additions and 35 deletions
+39 -22
View File
@@ -244,48 +244,61 @@ Page({
this.startDanmakuScheduler(cache.danmaku); this.startDanmakuScheduler(cache.danmaku);
}, },
// 启动弹幕调度:每行同一时刻只播一条,前一条飘完再出下一条(循环) // 启动弹幕调度:每行一个发射定时器,前一条完全进入屏幕后就出下一条,
// 同一时刻屏上可有多条,但首尾相接不重叠(自然弹幕效果)
startDanmakuScheduler(rows) { startDanmakuScheduler(rows) {
this.stopDanmakuScheduler(); this.stopDanmakuScheduler();
this.danmakuRows = rows || []; this.danmakuRows = rows || [];
this.danmakuTimers = []; this.danmakuTimers = [];
this.danmakuSeq = 0; // 全局自增序号,保证每条 key 唯一
if (!this.danmakuRows.length) { if (!this.danmakuRows.length) {
this.setData({ currentDanmaku: [] }); this.setData({ currentDanmaku: [] });
return; 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]; const rowData = this.danmakuRows[row];
if (!rowData) return; if (!rowData) return;
const item = rowData.list[idx % rowData.list.length]; const item = rowData.list[idx % rowData.list.length];
// 更新该行当前弹幕(用行号作为 key 保证 setData 局部刷新) const seq = ++this.danmakuSeq;
this.setData({ const danmaku = {
[`currentDanmaku[${row}]`]: { key: `d${seq}`,
key: `${row}-${idx % rowData.list.length}-${Date.now()}`,
id: item.id, id: item.id,
content: item.content, content: item.content,
author: item.author, author: item.author,
type: item.type, type: item.type,
top: rowData.top, top: rowData.top,
duration: rowData.duration, duration: rowData.duration,
}, };
}); // 追加到屏上弹幕列表(构造新数组,避免索引冲突)
// 前一条飘完 + 间隔后,播下一条 this.setData({ currentDanmaku: this.data.currentDanmaku.concat(danmaku) });
const timer = setTimeout(() => { // 动画播完(飘到屏外)后,把这条从列表移除
playRow(row, idx + 1); const removeTimer = setTimeout(() => {
}, (rowData.duration + GAP) * 1000); const list = this.data.currentDanmaku.filter((d) => d.key !== danmaku.key);
this.danmakuTimers[row] = timer; this.setData({ currentDanmaku: list });
}, rowData.duration * 1000);
this.danmakuTimers.push(removeTimer);
}; };
// 初始化 currentDanmaku 数组长度,并启动每行 // 每行独立调度:每隔 interval 秒发一条,循环
this.setData({ currentDanmaku: new Array(this.danmakuRows.length).fill(null) }); this.danmakuRows.forEach((rowData, row) => {
this.danmakuRows.forEach((_, 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 行同时从右侧涌出 // 各行错开启动,避免 4 行同时从右侧涌出
const startTimer = setTimeout(() => playRow(row, 0), row * 1200); const startTimer = setTimeout(loop, row * 1500);
this.danmakuTimers.push(startTimer); this.danmakuTimers.push(startTimer);
}); });
}, },
@@ -296,17 +309,21 @@ Page({
this.danmakuTimers.forEach((t) => clearTimeout(t)); this.danmakuTimers.forEach((t) => clearTimeout(t));
this.danmakuTimers = []; this.danmakuTimers = [];
} }
// 清空屏上弹幕,避免恢复时残留未播完的项
if (this.data.currentDanmaku && this.data.currentDanmaku.length) {
this.setData({ currentDanmaku: [] });
}
}, },
// 构建弹幕:共 4 行(行 0-2 树桩处三行,行 3 顶部置顶轮播下方) // 构建弹幕:共 4 行(行 0-2 树桩处三行,行 3 顶部置顶轮播下方)
// 按行分组返回,由 JS 定时器控制:同一行一条完毕后再出下一条,不重叠 // 按行分组返回,由 JS 定时器控制:前一条完全进入屏幕后就出下一条,首尾相接不重叠
buildDanmaku(list) { buildDanmaku(list) {
const DURATION_BOTTOM = 30; // 树桩处一条飘完全屏的秒数(慢速) const DURATION_BOTTOM = 30; // 树桩处一条飘完全屏的秒数(慢速)
const DURATION_TOP = 24; // 顶部行稍快一点 const DURATION_TOP = 24; // 顶部行稍快一点
// 行位置(屏幕百分比): // 行位置(屏幕百分比):
// - 行3 顶部行紧贴置顶轮播下方(稍有空隙) // - 行3 顶部行紧贴置顶轮播下方(稍有空隙)
// - 行0-2 树桩处三行,第一条68%,三行紧凑(间隔5% // - 行0-2 树桩处三行,第一条70%,三行紧凑(间隔3%
const ROW_TOPS = [68, 73, 78, 12]; const ROW_TOPS = [70, 73, 76, 14];
const ROW_DURATIONS = [DURATION_BOTTOM, DURATION_BOTTOM, DURATION_BOTTOM, DURATION_TOP]; const ROW_DURATIONS = [DURATION_BOTTOM, DURATION_BOTTOM, DURATION_BOTTOM, DURATION_TOP];
// 轮流分配到 4 行 // 轮流分配到 4 行
+3 -3
View File
@@ -45,14 +45,14 @@
</swiper-item> </swiper-item>
</swiper> </swiper>
<!-- 弹幕层(超出30条的心愿缓缓飘过,每行一条完毕再出下一条) --> <!-- 弹幕层(超出30条的心愿缓缓飘过,一条完全进入后出下一条) -->
<view class="danmaku-layer"> <view class="danmaku-layer">
<block wx:for="{{currentDanmaku}}" wx:key="key" wx:if="{{item}}">
<view <view
wx:for="{{currentDanmaku}}"
wx:key="key"
class="danmaku-item {{item.type === 'paid' ? 'paid' : ''}}" class="danmaku-item {{item.type === 'paid' ? 'paid' : ''}}"
style="top: {{item.top}}%; animation-duration: {{item.duration}}s;" style="top: {{item.top}}%; animation-duration: {{item.duration}}s;"
>{{item.content}} · {{item.author}}</view> >{{item.content}} · {{item.author}}</view>
</block>
</view> </view>
<!-- 边缘暗角 --> <!-- 边缘暗角 -->