Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
738b5c4452 | ||
|
|
a45004bfb2 | ||
|
|
f468cf4b94 |
@@ -12,8 +12,10 @@ services:
|
||||
working_dir: /app
|
||||
volumes:
|
||||
- /opt/lunar/production/current:/app
|
||||
- /opt/lunar/production/data:/app/data
|
||||
- /opt/lunar/production/.env:/app/.env
|
||||
ports:
|
||||
- "127.0.0.1:8080:8080" # 只监听本机,对外由 nginx 反代 lunar.neatcn.com
|
||||
- "127.0.0.1:8081:8080" # 只监听本机,对外由 nginx 反代 lunar.neatcn.com
|
||||
env_file:
|
||||
- /opt/lunar/production/.env
|
||||
restart: unless-stopped
|
||||
|
||||
@@ -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()}`,
|
||||
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: 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);
|
||||
},
|
||||
|
||||
@@ -45,14 +45,14 @@
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
|
||||
<!-- 弹幕层(超出30条的心愿缓缓飘过,每行一条完毕再出下一条) -->
|
||||
<!-- 弹幕层(超出30条的心愿缓缓飘过,前一条完全进入后出下一条) -->
|
||||
<view class="danmaku-layer">
|
||||
<block wx:for="{{currentDanmaku}}" wx:key="key" wx:if="{{item}}">
|
||||
<view
|
||||
class="danmaku-item {{item.type === 'paid' ? 'paid' : ''}}"
|
||||
style="top: {{item.top}}%; animation-duration: {{item.duration}}s;"
|
||||
>{{item.content}} · {{item.author}}</view>
|
||||
</block>
|
||||
<view
|
||||
wx:for="{{currentDanmaku}}"
|
||||
wx:key="key"
|
||||
class="danmaku-item {{item.type === 'paid' ? 'paid' : ''}}"
|
||||
style="top: {{item.top}}%; animation-duration: {{item.duration}}s;"
|
||||
>{{item.content}} · {{item.author}}</view>
|
||||
</view>
|
||||
|
||||
<!-- 边缘暗角 -->
|
||||
|
||||
Reference in New Issue
Block a user