import { nextTick } from 'vue'; /** * 滚动控制工具类 */ export class ScrollController { constructor(containerSelector = '.chat', options = {}) { this.containerSelector = containerSelector; this.options = { threshold: 100, scrollDelay: 100, retryDelays: [50, 150], ...options }; this.scrollTimer = null; this.isUserScrolling = false; this.shouldAutoScroll = true; this.isProgrammaticScroll = false; // Bind the context of 'this' for the event handler this.handleScroll = this.handleScroll.bind(this); } /** * 获取滚动容器 * @returns {Element|null} */ getContainer() { return document.querySelector(this.containerSelector); } /** * 检查是否在底部 * @returns {boolean} */ isAtBottom() { const container = this.getContainer(); if (!container) return false; const { threshold } = this.options; return container.scrollHeight - container.scrollTop - container.clientHeight <= threshold; } /** * 处理滚动事件 */ handleScroll() { if (this.scrollTimer) { clearTimeout(this.scrollTimer); } // 如果是程序性滚动,忽略此次事件 if (this.isProgrammaticScroll) { this.isProgrammaticScroll = false; return; } // 标记用户正在滚动 this.isUserScrolling = true; // 检查是否在底部 this.shouldAutoScroll = this.isAtBottom(); // 滚动结束后一段时间重置用户滚动状态 this.scrollTimer = setTimeout(() => { this.isUserScrolling = false; }, this.options.scrollDelay); } /** * 智能滚动到底部 * @param {boolean} force - 是否强制滚动 */ async scrollToBottom(force = false) { await nextTick(); // 只有在应该自动滚动时才执行(除非强制) if (!force && !this.shouldAutoScroll) return; const container = this.getContainer(); if (!container) return; // 标记为程序性滚动 this.isProgrammaticScroll = true; const scrollOptions = { top: container.scrollHeight, behavior: 'smooth' }; // 立即滚动 container.scrollTo(scrollOptions); // 多次重试确保滚动成功 this.options.retryDelays.forEach((delay, index) => { setTimeout(() => { if (force || this.shouldAutoScroll) { this.isProgrammaticScroll = true; const behavior = index === this.options.retryDelays.length - 1 ? 'auto' : 'smooth'; container.scrollTo({ top: container.scrollHeight, behavior }); } }, delay); }); } async scrollToBottomStaticForce() { const container = this.getContainer(); if (!container) return; // 标记为程序性滚动 this.isProgrammaticScroll = true; const scrollOptions = { top: container.scrollHeight, behavior: 'auto' }; container.scrollTo(scrollOptions); } /** * 启用自动滚动 */ enableAutoScroll() { this.shouldAutoScroll = true; } /** * 禁用自动滚动 */ disableAutoScroll() { this.shouldAutoScroll = false; } /** * 获取滚动状态 */ getScrollState() { return { isUserScrolling: this.isUserScrolling, shouldAutoScroll: this.shouldAutoScroll, isAtBottom: this.isAtBottom() }; } /** * 清理定时器 */ cleanup() { if (this.scrollTimer) { clearTimeout(this.scrollTimer); this.scrollTimer = null; } } /** * 重置滚动状态 */ reset() { this.cleanup(); this.isUserScrolling = false; this.shouldAutoScroll = true; this.isProgrammaticScroll = false; } } /** * 创建默认的滚动控制器实例 */ export const createScrollController = (containerSelector, options) => { return new ScrollController(containerSelector, options); }; export default ScrollController;