// 弹幕功能封装 (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; } Artalk.use((ctx) => { ctx.on('list-loaded', () => { console.log('评论列表加载完毕'); const commentsData = ctx.getComments(); if (commentsData) { resolve(commentsData); } else { reject('获取评论数据失败'); } }); }); }); } // 弹幕类 (基于 min-barrage 逻辑) class BarrageEngine { constructor(options = {}) { this.container = typeof options.container === 'string' ? document.querySelector(options.container) : options.container; if (!this.container) { console.warn('[Barrage] 错误: 找不到弹幕容器'); return; } this.data = []; this.index = 0; this.timer = null; this.isPaused = false; } // 开始发送 start(data = []) { console.log(`[Barrage] 启动弹幕,共 ${data.length} 条`); this.data = data; if (this.data.length === 0) return; this.clear(); this.loop(); } // 清空 clear() { if (this.timer) clearTimeout(this.timer); // 清除容器内所有弹幕元素 if (this.container) { const items = this.container.querySelectorAll('.barrage-item'); items.forEach(item => item.remove()); } this.index = 0; } // 循环发射 loop() { // 如果容器不在文档中(PJAX切换后),尝试重新获取容器 // (注意:this.container 是 DOM 引用,如果页面重绘,旧引用可能失效) if (!document.body.contains(this.container)) { console.warn('[Barrage] 容器失效,尝试重新获取...'); const newContainer = document.querySelector('.barrager'); if (newContainer) { this.container = newContainer; console.log('[Barrage] 容器重新获取成功,继续播放'); } else { console.warn('[Barrage] 容器彻底丢失,停止播放'); return; // 真的没了再停止 } } // 发射逻辑 if (!this.isPaused) { const itemData = this.data[this.index]; this.shoot(itemData); this.index = (this.index + 1) % this.data.length; } // 随机间隔 1s - 3s const interval = 1000 + Math.random() * 2000; this.timer = setTimeout(() => this.loop(), interval); } // 发射单条 shoot(itemData) { const { text, avatar, url, id } = itemData; const item = document.createElement('div'); item.className = 'barrage-item'; // 构造内部 HTML 结构:头像 + 气泡 let html = ''; // 1. 头像 if (avatar) { html += ``; } // 2. 气泡 (包裹文本) html += `
${text}
`; item.innerHTML = html; // 点击跳转 (通用处理) const jumpHandler = (e) => { e.stopPropagation(); e.preventDefault(); console.log(`[Barrage] 点击跳转: ${url}`); // 检查目标评论是否在当前页面 const targetId = url.split('#')[1]; const targetEl = document.getElementById(targetId); if (targetEl) { // 在当前页,直接滚动 targetEl.scrollIntoView({ behavior: 'smooth', block: 'center' }); // 高亮一下 targetEl.classList.add('atk-flash'); setTimeout(() => targetEl.classList.remove('atk-flash'), 2000); } else { // 不在当前页,或者需要跳转 window.location.href = url; } }; if (url && url !== '#') { item.addEventListener('click', jumpHandler); item.style.cursor = 'pointer'; } // 随机轨道 (Top 5% - 95%) - 避免太靠边 const tracks = 15; const trackHeight = 100 / tracks; const trackIndex = Math.floor(Math.random() * tracks); const top = trackIndex * trackHeight + (Math.random() * 2); item.style.top = `${top}%`; // 随机速度 (18s - 25s) - 更慢一点,更自然 const duration = 18 + Math.random() * 7; item.style.animation = `barrage-slide-right ${duration}s linear forwards`; // 鼠标悬停暂停该弹幕 item.addEventListener('mouseenter', () => { item.style.animationPlayState = 'paused'; item.style.zIndex = '99999'; }); item.addEventListener('mouseleave', () => { item.style.animationPlayState = 'running'; item.style.zIndex = '10'; }); // 动画结束移除 item.addEventListener('animationend', () => { item.remove(); }); this.container.appendChild(item); } } // 挂载到 window,使用 BarrageEngine 避免冲突 window.BarrageEngine = BarrageEngine; // 从 Artalk 评论数据生成弹幕 async function createBarragerFromArtalk(commentsData) { const barragerContainer = document.querySelector('.barrager'); if (!barragerContainer) { console.warn('[Barrage] 未找到弹幕容器 .barrager'); return; } // 1. 数据预处理 const barrageList = commentsData.map(comment => { // 清理 HTML 标签 const contentText = comment.content.replace(/<[^>]+>/g, "").substring(0, 50); // 获取头像逻辑优化: 优先从 DOM 获取 Artalk 已渲染的头像,其次使用 API 数据 let avatarUrl = ''; // 尝试从 DOM 获取 (最准确,因为是 Artalk 自己渲染的) const commentEl = document.getElementById(`atk-comment-${comment.id}`); if (commentEl) { const imgEl = commentEl.querySelector('.atk-avatar img'); if (imgEl && imgEl.src) { avatarUrl = imgEl.src; } } // 如果 DOM 没取到,尝试从数据获取 if (!avatarUrl) { if (comment.avatar) { avatarUrl = comment.avatar; } else if (comment.ua && comment.ua.avatar) { avatarUrl = comment.ua.avatar; } else { avatarUrl = 'https://cravatar.cn/avatar/default?d=mp'; } } return { text: contentText, avatar: avatarUrl, url: `#atk-comment-${comment.id}`, // 跳转锚点 id: comment.id }; }).filter(item => item.text.trim() !== ""); if (barrageList.length === 0) { console.log('[Barrage] 没有有效的评论弹幕数据'); return; } // 2. 初始化 Barrage 实例 // 挂载到 window 以便调试或清理 if (window.myBarrage) { window.myBarrage.clear(); } // 使用 window.BarrageEngine if (window.BarrageEngine) { window.myBarrage = new window.BarrageEngine({ container: barragerContainer }); window.myBarrage.start(barrageList); } else { console.error('[Barrage] window.BarrageEngine 未定义'); } } // 加载弹幕主函数 async function loadBarrager() { try { const commentsData = await initArtalkAndGetComments(); if (commentsData) { await createBarragerFromArtalk(commentsData); } } catch (error) { console.error('加载弹幕时发生错误:', error); } } // 初始化监听 document.addEventListener('DOMContentLoaded', function() { loadBarrager(); // 页面首次加载时启动弹幕 }); document.addEventListener('pjax:complete', function() { // PJAX 跳转后重新加载弹幕 loadBarrager(); }); })();