fix(wish-tree): 弹幕一行一条排队播放,不再重叠
Publish Mini Program Dev Version / publish (push) Successful in 1m4s
Publish Mini Program Dev Version / publish (push) Successful in 1m4s
- 顶部滚动行上移紧贴置顶轮播(16%→12%) - 树桩3行改为68/73/78%,更紧凑 - 核心修复重叠:弃用CSS无限循环+负delay(同屏多条), 改为JS定时器按行调度,同一行一条飘完再出下一条,循环播放 - 弹幕动画改为只播一次,key含时间戳保证切换时重新触发动画 - 页面onHide/onUnload停止定时器,onShow恢复
This commit is contained in:
@@ -75,6 +75,21 @@ Page({
|
|||||||
this.loadProducts();
|
this.loadProducts();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onUnload() {
|
||||||
|
this.stopDanmakuScheduler();
|
||||||
|
},
|
||||||
|
|
||||||
|
onHide() {
|
||||||
|
this.stopDanmakuScheduler();
|
||||||
|
},
|
||||||
|
|
||||||
|
onShow() {
|
||||||
|
// 从后台/下一页返回时,若已有弹幕数据则恢复调度
|
||||||
|
if (this.danmakuRows && this.danmakuRows.length && !(this.danmakuTimers && this.danmakuTimers.length)) {
|
||||||
|
this.startDanmakuScheduler(this.danmakuRows);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
onPullDownRefresh() {
|
onPullDownRefresh() {
|
||||||
const { currentTreeIndex, trees } = this.data;
|
const { currentTreeIndex, trees } = this.data;
|
||||||
const tree = trees[currentTreeIndex];
|
const tree = trees[currentTreeIndex];
|
||||||
@@ -224,16 +239,75 @@ Page({
|
|||||||
this.setData({
|
this.setData({
|
||||||
[`trees[${index}].hangWishes`]: cache.hangWishes,
|
[`trees[${index}].hangWishes`]: cache.hangWishes,
|
||||||
topWishes: cache.top,
|
topWishes: cache.top,
|
||||||
currentDanmaku: cache.danmaku,
|
});
|
||||||
|
// 启动弹幕调度:每行一条完毕后再出下一条
|
||||||
|
this.startDanmakuScheduler(cache.danmaku);
|
||||||
|
},
|
||||||
|
|
||||||
|
// 启动弹幕调度:每行同一时刻只播一条,前一条飘完再出下一条(循环)
|
||||||
|
startDanmakuScheduler(rows) {
|
||||||
|
this.stopDanmakuScheduler();
|
||||||
|
this.danmakuRows = rows || [];
|
||||||
|
this.danmakuTimers = [];
|
||||||
|
|
||||||
|
if (!this.danmakuRows.length) {
|
||||||
|
this.setData({ currentDanmaku: [] });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const GAP = 1.5; // 同一行前一条飘完后,间隔再出下一条(秒)
|
||||||
|
|
||||||
|
// 每行独立调度:row 行的第 idx 条播完后,隔 GAP 秒播第 idx+1 条
|
||||||
|
const playRow = (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;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 初始化 currentDanmaku 数组长度,并启动每行
|
||||||
|
this.setData({ currentDanmaku: new Array(this.danmakuRows.length).fill(null) });
|
||||||
|
this.danmakuRows.forEach((_, row) => {
|
||||||
|
// 各行错开启动,避免 4 行同时从右侧涌出
|
||||||
|
const startTimer = setTimeout(() => playRow(row, 0), row * 1200);
|
||||||
|
this.danmakuTimers.push(startTimer);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 停止弹幕调度
|
||||||
|
stopDanmakuScheduler() {
|
||||||
|
if (this.danmakuTimers) {
|
||||||
|
this.danmakuTimers.forEach((t) => clearTimeout(t));
|
||||||
|
this.danmakuTimers = [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
// 构建弹幕:共 4 行(行 0-2 树桩处三行,行 3 顶部置顶轮播下方)
|
// 构建弹幕:共 4 行(行 0-2 树桩处三行,行 3 顶部置顶轮播下方)
|
||||||
// 同行统一慢速,按条数均匀错开起始位置,避免互相盖住
|
// 按行分组返回,由 JS 定时器控制:同一行一条完毕后再出下一条,不重叠
|
||||||
buildDanmaku(list) {
|
buildDanmaku(list) {
|
||||||
const DURATION_BOTTOM = 32; // 树桩处一行飘完全屏的秒数(慢速)
|
const DURATION_BOTTOM = 30; // 树桩处一条飘完全屏的秒数(慢速)
|
||||||
const DURATION_TOP = 26; // 顶部行稍快一点
|
const DURATION_TOP = 24; // 顶部行稍快一点
|
||||||
const ROW_TOPS = [64, 71, 78, 16]; // 前三行靠近树桩,第四行在顶部
|
// 行位置(屏幕百分比):
|
||||||
|
// - 行3 顶部行紧贴置顶轮播下方(稍有空隙)
|
||||||
|
// - 行0-2 树桩处三行,第一条68%,三行紧凑(间隔5%)
|
||||||
|
const ROW_TOPS = [68, 73, 78, 12];
|
||||||
|
const ROW_DURATIONS = [DURATION_BOTTOM, DURATION_BOTTOM, DURATION_BOTTOM, DURATION_TOP];
|
||||||
|
|
||||||
// 轮流分配到 4 行
|
// 轮流分配到 4 行
|
||||||
const rows = [[], [], [], []];
|
const rows = [[], [], [], []];
|
||||||
@@ -241,24 +315,17 @@ Page({
|
|||||||
rows[i % 4].push(w);
|
rows[i % 4].push(w);
|
||||||
});
|
});
|
||||||
|
|
||||||
const items = [];
|
// 每行存该行的所有弹幕(带位置/时长),供定时器轮流播放
|
||||||
rows.forEach((rowList, row) => {
|
return rows.map((rowList, row) => ({
|
||||||
if (rowList.length === 0) return;
|
top: ROW_TOPS[row],
|
||||||
const duration = row === 3 ? DURATION_TOP : DURATION_BOTTOM;
|
duration: ROW_DURATIONS[row],
|
||||||
rowList.forEach((w, idx) => {
|
list: rowList.map((w) => ({
|
||||||
items.push({
|
id: w.id,
|
||||||
id: w.id,
|
content: w.content,
|
||||||
content: w.content,
|
author: w.author || '匿名',
|
||||||
author: w.author || '匿名',
|
type: w.type,
|
||||||
type: w.type,
|
})),
|
||||||
top: ROW_TOPS[row],
|
})).filter((r) => r.list.length > 0);
|
||||||
duration,
|
|
||||||
// 均匀间隔:同屏内前后错开,不会盖住其他弹幕
|
|
||||||
delay: -(idx * duration / rowList.length),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return items;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// 格式化许愿
|
// 格式化许愿
|
||||||
@@ -482,4 +549,10 @@ Page({
|
|||||||
path: '/pages/wish-tree/wish-tree',
|
path: '/pages/wish-tree/wish-tree',
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onShareTimeline() {
|
||||||
|
return {
|
||||||
|
title: '快来许愿树许下你的愿望吧!'
|
||||||
|
};
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -45,14 +45,14 @@
|
|||||||
</swiper-item>
|
</swiper-item>
|
||||||
</swiper>
|
</swiper>
|
||||||
|
|
||||||
<!-- 弹幕层(超出50条的心愿缓缓飘过) -->
|
<!-- 弹幕层(超出30条的心愿缓缓飘过,每行一条完毕再出下一条) -->
|
||||||
<view class="danmaku-layer">
|
<view class="danmaku-layer">
|
||||||
<view
|
<block wx:for="{{currentDanmaku}}" wx:key="key" wx:if="{{item}}">
|
||||||
wx:for="{{currentDanmaku}}"
|
<view
|
||||||
wx:key="id"
|
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; animation-delay: {{item.delay}}s;"
|
>{{item.content}} · {{item.author}}</view>
|
||||||
>{{item.content}} · {{item.author}}</view>
|
</block>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 边缘暗角 -->
|
<!-- 边缘暗角 -->
|
||||||
|
|||||||
@@ -142,7 +142,8 @@
|
|||||||
text-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.6);
|
text-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.6);
|
||||||
animation-name: danmaku;
|
animation-name: danmaku;
|
||||||
animation-timing-function: linear;
|
animation-timing-function: linear;
|
||||||
animation-iteration-count: infinite;
|
animation-iteration-count: 1;
|
||||||
|
animation-fill-mode: both;
|
||||||
will-change: transform;
|
will-change: transform;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -608,7 +609,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 弹幕:从屏幕右侧飘到左侧 */
|
/* 弹幕:从屏幕右侧飘到左侧。每条只播一次,由 JS 控制一行一条排队出现。 */
|
||||||
@keyframes danmaku {
|
@keyframes danmaku {
|
||||||
0% {
|
0% {
|
||||||
transform: translateX(100vw);
|
transform: translateX(100vw);
|
||||||
|
|||||||
Reference in New Issue
Block a user