From 738b5c44522727aa0324d4ec5d08cdb99f1de8f5 Mon Sep 17 00:00:00 2001 From: gouki Date: Mon, 10 Aug 2026 21:44:09 +0000 Subject: [PATCH] =?UTF-8?q?tweak(wish-tree):=20=E5=BC=B9=E5=B9=95=E5=90=84?= =?UTF-8?q?=E8=A1=8C=E9=80=9F=E5=BA=A6=E9=94=99=E8=90=BD=EF=BC=8C=E6=AF=8F?= =?UTF-8?q?=E8=A1=8C=E5=B1=8F=E4=B8=8A2~3=E6=9D=A1=E4=B8=8D=E5=8E=8B?= =?UTF-8?q?=E7=9D=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 顶部行回到12% - 每行不同基准速度:树桩三行26/33/40秒、顶部22秒,不再整齐划一 - 同行弹幕速度也带±20%随机错落(seeded保证渲染一致) - 每行屏上保持约2.5条:间隔=duration/2.5,但不小于完全进入时间 (duration*0.3,保证下一条不压着前一条)和最小间隔3秒 --- mini/pages/wish-tree/wish-tree.js | 42 +++++++++++++++++-------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/mini/pages/wish-tree/wish-tree.js b/mini/pages/wish-tree/wish-tree.js index d1eea70..3a830b3 100644 --- a/mini/pages/wish-tree/wish-tree.js +++ b/mini/pages/wish-tree/wish-tree.js @@ -245,7 +245,7 @@ Page({ }, // 启动弹幕调度:每行一个发射定时器,前一条完全进入屏幕后就出下一条, - // 同一时刻屏上可有多条,但首尾相接不重叠(自然弹幕效果) + // 同一行屏上保持 2~3 条,首尾相接不压着;各行速度不同、同行弹幕速度也错落 startDanmakuScheduler(rows) { this.stopDanmakuScheduler(); this.danmakuRows = rows || []; @@ -257,15 +257,16 @@ Page({ return; } - // 弹幕完全进入屏幕大约需要飘过总路程的 35%(从右屏外到尾部越过右边缘) - // 此时下一条即可开始从右侧进入,与前一条首尾相接不重叠 - const ENTER_RATIO = 0.35; - const MIN_INTERVAL = 4; // 同一行相邻两条最小间隔(秒),避免过密 + // 屏上目标条数:同一行同时约 2.5 条 + const ON_SCREEN = 2.5; + // 弹幕完全进入屏幕约需飘过总路程的 30%(不压着前一条的最小间隔) + const ENTER_RATIO = 0.3; + const MIN_INTERVAL = 3; // 同一行相邻两条最小间隔(秒) - // 往某行发射一条弹幕 + // 往某行发射一条弹幕,返回该条的 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]; const seq = ++this.danmakuSeq; const danmaku = { @@ -275,7 +276,7 @@ Page({ author: item.author, type: item.type, top: rowData.top, - duration: rowData.duration, + duration: item.duration, }; // 追加到屏上弹幕列表(构造新数组,避免索引冲突) this.setData({ currentDanmaku: this.data.currentDanmaku.concat(danmaku) }); @@ -283,17 +284,20 @@ Page({ const removeTimer = setTimeout(() => { const list = this.data.currentDanmaku.filter((d) => d.key !== danmaku.key); this.setData({ currentDanmaku: list }); - }, rowData.duration * 1000); + }, item.duration * 1000); this.danmakuTimers.push(removeTimer); + return item.duration; }; - // 每行独立调度:每隔 interval 秒发一条,循环 + // 每行独立调度:按刚发射这条的速度决定下一条间隔,循环 this.danmakuRows.forEach((rowData, row) => { - const interval = Math.max(rowData.duration * ENTER_RATIO, MIN_INTERVAL); let idx = 0; const loop = () => { - emit(row, idx); + 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); }; @@ -316,15 +320,14 @@ Page({ }, // 构建弹幕:共 4 行(行 0-2 树桩处三行,行 3 顶部置顶轮播下方) - // 按行分组返回,由 JS 定时器控制:前一条完全进入屏幕后就出下一条,首尾相接不重叠 + // 每行速度不同、同行弹幕速度也带随机错落,避免太整齐;前一条完全进入后出下一条 buildDanmaku(list) { - const DURATION_BOTTOM = 30; // 树桩处一条飘完全屏的秒数(慢速) - const DURATION_TOP = 24; // 顶部行稍快一点 // 行位置(屏幕百分比): // - 行3 顶部行紧贴置顶轮播下方(稍有空隙) // - 行0-2 树桩处三行,第一条70%,三行紧凑(间隔3%) - const ROW_TOPS = [70, 73, 76, 14]; - const ROW_DURATIONS = [DURATION_BOTTOM, DURATION_BOTTOM, DURATION_BOTTOM, DURATION_TOP]; + const ROW_TOPS = [70, 73, 76, 12]; + // 每行基准速度(秒/次):各行不同,避免整齐划一 + const ROW_BASE_DURATION = [26, 33, 40, 22]; // 轮流分配到 4 行 const rows = [[], [], [], []]; @@ -335,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); },