From 532b19aadfefa2aa38e1e927f30500cf321912c0 Mon Sep 17 00:00:00 2001
From: Vaica <94612053+zqlit@users.noreply.github.com>
Date: Thu, 29 Jan 2026 21:05:27 +0800
Subject: [PATCH] =?UTF-8?q?fixed=20=E9=87=8D=E5=86=99=E5=BC=B9=E5=B9=95?=
=?UTF-8?q?=E9=80=BB=E8=BE=91?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
themes/Ying/assets/js/barrage.js | 489 +++++++++++++---------
themes/Ying/layouts/partials/barrage.html | 9 +-
2 files changed, 298 insertions(+), 200 deletions(-)
diff --git a/themes/Ying/assets/js/barrage.js b/themes/Ying/assets/js/barrage.js
index 34b45566..f7d789b3 100644
--- a/themes/Ying/assets/js/barrage.js
+++ b/themes/Ying/assets/js/barrage.js
@@ -1,51 +1,121 @@
// 弹幕功能封装
(function() {
- // 异步初始化 Artalk 并获取评论数据
- function initArtalkAndGetComments() {
- return new Promise((resolve, reject) => {
- // 检查 Artalk 是否存在,增加轮询等待机制
- if (typeof Artalk === 'undefined') {
- let attempts = 0;
- const maxAttempts = 50; // 最多等待 5 秒
- const interval = setInterval(() => {
- attempts++;
- if (typeof Artalk !== 'undefined') {
- clearInterval(interval);
- Artalk.use((ctx) => {
- ctx.on('list-loaded', () => {
- console.log('评论列表加载完毕');
- const commentsData = ctx.getComments();
- if (commentsData) {
- resolve(commentsData);
- } else {
- reject('获取评论数据失败');
- }
- });
- });
- } else if (attempts >= maxAttempts) {
- clearInterval(interval);
- console.warn('Artalk 加载超时');
- reject('Artalk 未定义 (加载超时)');
- }
- }, 100);
- return;
+ let barrageInstance = null; // 单例实例引用
+
+ // 获取 Artalk 评论数据
+ function getArtalkComments() {
+ return new Promise((resolve) => {
+ let isResolved = false;
+
+ const tryResolve = (data, source) => {
+ if (isResolved) return;
+
+ // 调试日志
+ if (data && Array.isArray(data)) {
+ console.log(`[Barrage] 数据检查 (${source}): ${data.length} 条`);
+ } else {
+ console.log(`[Barrage] 数据检查 (${source}): 无效数据`, data);
+ }
+
+ if (Array.isArray(data) && data.length > 0) {
+ isResolved = true;
+ resolve(data);
+ }
+ };
+
+ // 1. 立即检查
+ if (window.artalk && typeof window.artalk.getComments === 'function') {
+ tryResolve(window.artalk.getComments(), 'immediate');
+ } else {
+ console.log('[Barrage] Artalk 实例未就绪 (Immediate)');
}
- Artalk.use((ctx) => {
- ctx.on('list-loaded', () => {
- console.log('评论列表加载完毕');
- const commentsData = ctx.getComments();
- if (commentsData) {
- resolve(commentsData);
- } else {
- reject('获取评论数据失败');
- }
+ if (isResolved) return;
+
+ // 2. 监听加载完成事件 (推荐)
+ if (window.artalk && typeof window.artalk.on === 'function') {
+ window.artalk.on('list-loaded', (data) => {
+ console.log('[Barrage] list-loaded 事件触发');
+ // 优先使用事件传递的数据(如果有),否则调用 getComments
+ const comments = (Array.isArray(data) && data.length > 0) ? data : window.artalk.getComments();
+ tryResolve(comments, 'event');
});
- });
+ }
+
+ // 3. 轮询 (后备)
+ let pollCount = 0;
+ const interval = setInterval(() => {
+ if (isResolved) {
+ clearInterval(interval);
+ return;
+ }
+ pollCount++;
+ if (window.artalk && typeof window.artalk.getComments === 'function') {
+ // 仅在有数据时才 resolve,避免轮询到空数据提前结束
+ const data = window.artalk.getComments();
+ if (Array.isArray(data) && data.length > 0) {
+ tryResolve(data, `poll-${pollCount}`);
+ }
+ }
+ }, 500);
+
+ // 4. 超时处理 (5秒)
+ setTimeout(() => {
+ if (!isResolved) {
+ clearInterval(interval);
+ console.warn('[Barrage] 等待评论数据超时 (5s)');
+
+ // 尝试从 DOM 获取作为最后兜底
+ const domComments = document.querySelectorAll('.atk-comment-wrap');
+ if (domComments.length > 0) {
+ console.log(`[Barrage] 从 DOM 挽救数据: ${domComments.length} 条`);
+ const fallbackData = Array.from(domComments).map(el => {
+ const idStr = el.id || ''; // atk-comment-123
+ const id = idStr.replace('atk-comment-', '');
+ const contentEl = el.querySelector('.atk-content');
+ const avatarEl = el.querySelector('.atk-avatar img');
+
+ if (!id || !contentEl) return null;
+
+ return {
+ id: id,
+ content: contentEl.innerText || contentEl.textContent,
+ avatar: avatarEl ? avatarEl.src : '',
+ // 模拟 Artalk 数据结构
+ ua: null
+ };
+ }).filter(Boolean);
+
+ if (fallbackData.length > 0) {
+ isResolved = true;
+ resolve(fallbackData);
+ return;
+ }
+ }
+
+ resolve([]); // 真的没有数据
+ }
+ }, 5000);
});
}
- // 弹幕类 (基于 min-barrage 逻辑)
+ // 获取 Hitokoto 一言
+ async function fetchHitokoto(url) {
+ try {
+ const res = await fetch(url);
+ if (!res.ok) throw new Error('Network response was not ok');
+ const data = await res.json();
+ return {
+ id: `hitokoto-${data.id}`,
+ content: data.hitokoto,
+ from: data.from
+ };
+ } catch (e) {
+ console.warn('[Barrage] Hitokoto fetch failed:', e);
+ return null;
+ }
+ }
+
class BarrageEngine {
constructor(options = {}) {
this.container = typeof options.container === 'string'
@@ -62,238 +132,261 @@
this.timer = null;
this.isPaused = false;
this.isDestroyed = false;
+
+ // 追踪当前屏幕上显示的弹幕 ID
+ this.activeIds = new Set();
}
- // 开始发送
start(data = []) {
- console.log(`[Barrage] 启动弹幕,共 ${data.length} 条`);
- this.data = data;
+ this.stop();
+ this.clearDOM();
+ this.isDestroyed = false;
+
+ // 数据预处理:去重 & ID 规范化
+ const uniqueData = [];
+ const seenIds = new Set();
+
+ data.forEach(item => {
+ const id = String(item.id);
+ if (!seenIds.has(id)) {
+ seenIds.add(id);
+ uniqueData.push({ ...item, id: id });
+ }
+ });
+
+ this.data = uniqueData;
+ console.log(`[Barrage] 启动: 原始 ${data.length}, 去重后 ${this.data.length}`);
+
if (this.data.length === 0) return;
- this.clear();
- this.isDestroyed = false;
this.loop();
}
- // 清空
- clear() {
- this.isDestroyed = true;
- if (this.timer) clearTimeout(this.timer);
- // 清除容器内所有弹幕元素
- if (this.container) {
- const items = this.container.querySelectorAll('.barrage-item');
- items.forEach(item => item.remove());
+ stop() {
+ if (this.timer) {
+ clearTimeout(this.timer);
+ this.timer = null;
}
- this.index = 0;
+ this.activeIds.clear();
+ }
+
+ clearDOM() {
+ if (this.container) {
+ this.container.innerHTML = '';
+ }
+ }
+
+ destroy() {
+ this.isDestroyed = true;
+ this.stop();
+ this.clearDOM();
}
- // 循环发射
loop() {
if (this.isDestroyed) return;
- // 如果容器不在文档中(PJAX切换后),停止播放
+ // 容器检测
if (!document.body.contains(this.container)) {
- console.warn('[Barrage] 容器失效,停止播放');
+ // 尝试重新获取容器 (可能被 PJAX 替换但 ID 没变?)
+ // 这里简单处理:如果失效则停止
+ // console.warn('[Barrage] 容器失效,停止');
+ this.destroy();
return;
}
- // 发射逻辑
- if (!this.isPaused) {
- const itemData = this.data[this.index];
- this.shoot(itemData);
- this.index = (this.index + 1) % this.data.length;
+ if (!this.isPaused && this.data.length > 0) {
+ this.tryShoot();
}
- // 随机间隔 1s - 3s
- const interval = 1000 + Math.random() * 2000;
+ // 随机间隔 2s - 4s
+ const interval = 2000 + Math.random() * 2000;
this.timer = setTimeout(() => this.loop(), interval);
}
- // 发射单条
+ tryShoot() {
+ let attempts = 0;
+ const maxAttempts = this.data.length;
+
+ while (attempts < maxAttempts) {
+ const item = this.data[this.index];
+ this.index = (this.index + 1) % this.data.length;
+
+ if (!this.activeIds.has(item.id)) {
+ this.shoot(item);
+ return;
+ }
+ attempts++;
+ }
+ }
+
shoot(itemData) {
const { text, avatar, url, id } = itemData;
+ this.activeIds.add(id);
+
const item = document.createElement('div');
item.className = 'barrage-item';
- // 构造内部 HTML 结构:头像 + 气泡
let html = '';
-
- // 1. 头像
if (avatar) {
html += ``;
}
-
- // 2. 气泡 (包裹文本)
html += `