AI Chat Auto-Scroll Blocker

Prevents ChatGPT, Gemini, and Claude from auto-scrolling during conversations, giving you full control over your reading experience

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         AI Chat Auto-Scroll Blocker
// @name:zh-CN   锁定聊天页面滚动 ChatGPT & Gemini & Claude
// @namespace    https://greasyfork.org/
// @version      1.0
// @description  Prevents ChatGPT, Gemini, and Claude from auto-scrolling during conversations, giving you full control over your reading experience
// @description:zh-CN  每次在ChatGPT或Gemini提问时,页面总是自动滚动到底部,我还没看完上一个答案就被强制跳到新答案了,非常影响体验
// @author       Efficient Lazy Panda
// @match        https://chatgpt.com/*
// @match        https://chat.openai.com/*
// @match        https://gemini.google.com/*
// @match        https://bard.google.com/*
// @match        https://claude.ai/*
// @grant        none
// @run-at       document-start
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    let scrollBlocked = true; // 默认阻止自动滚动
    let originalScrollIntoView = null;
    let originalScrollTo = null;
    let originalScrollTop = null;

    // 阻止自动滚动
    function blockAutoScroll() {
        // 保存原始函数
        if (!originalScrollIntoView) {
            originalScrollIntoView = Element.prototype.scrollIntoView;
            originalScrollTo = window.scrollTo;
            originalScrollTop = Object.getOwnPropertyDescriptor(Element.prototype, 'scrollTop') ||
                              Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'scrollTop');
        }

        Element.prototype.scrollIntoView = function(options) {
            if (scrollBlocked) {
                console.log('阻止了 scrollIntoView 调用');
                return false;
            }
            return originalScrollIntoView.call(this, options);
        };

        window.scrollTo = function(x, y) {
            if (scrollBlocked) {
                // 只阻止向下滚动
                if (y > window.scrollY) {
                    console.log('阻止了向下 scrollTo 调用:', x, y);
                    return false;
                }
            }
            return originalScrollTo.call(this, x, y);
        };

        if (originalScrollTop) {
            Object.defineProperty(Element.prototype, 'scrollTop', {
                get: function() {
                    return originalScrollTop.get ? originalScrollTop.get.call(this) : 0;
                },
                set: function(value) {
                    if (scrollBlocked && value > this.scrollTop) {
                        console.log('阻止了向下滚动设置 scrollTop:', value);
                        return;
                    }
                    if (originalScrollTop && originalScrollTop.set) {
                        originalScrollTop.set.call(this, value);
                    }
                }
            });
        }
    }

    // 智能滚动阻止系统
    function preventScrollEvents() {
        let lastScrollTime = 0;
        let scrollAttempts = 0;

        // 阻止自动滚动,但允许手动滚动
        document.addEventListener('scroll', function(e) {
            const currentTime = Date.now();
            const timeDiff = currentTime - lastScrollTime;

            if (scrollBlocked) {
                // 如果滚动间隔太短(可能是自动滚动)
                if (timeDiff < 50) {
                    scrollAttempts++;
                    if (scrollAttempts > 3) {
                        console.log('检测到连续自动滚动,已阻止');
                        e.preventDefault();
                        e.stopPropagation();
                        return false;
                    }
                } else {
                    scrollAttempts = 0;
                }
            }

            lastScrollTime = currentTime;
            window.lastScrollY = window.scrollY;
        }, { capture: true, passive: false });

        // 监听页面滚动行为变化
        const originalSetTimeout = window.setTimeout;

        window.setTimeout = function(callback, delay, ...args) {
            if (scrollBlocked && delay > 0 && delay < 1000) {
                // 检查回调函数是否可能执行滚动
                const callbackStr = callback.toString();
                if (callbackStr.includes('scroll') ||
                    callbackStr.includes('scrollIntoView') ||
                    callbackStr.includes('scrollTop')) {
                    console.log('阻止可能的自动滚动定时器');
                    return originalSetTimeout(() => {}, delay);
                }
            }
            return originalSetTimeout.call(this, callback, delay, ...args);
        };
    }

    // ChatGPT 特定的防滚动修复
    function applyChatGPTFix() {
        let lastScrollPosition = 0;

        const observer = new MutationObserver((mutations) => {
            if (scrollBlocked) {
                mutations.forEach((mutation) => {
                    if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
                        mutation.addedNodes.forEach((node) => {
                            if (node.nodeType === 1) {
                                const selectors = [
                                    '[data-testid^="conversation-turn"]',
                                    '[role="presentation"]',
                                    '.group.w-full',
                                    '.flex.w-full.items-center',
                                    'article',
                                    '.prose',
                                    '[data-message-author-role]'
                                ];

                                let foundMessage = false;
                                selectors.forEach(selector => {
                                    if (node.matches && node.matches(selector) ||
                                        node.querySelector && node.querySelector(selector)) {
                                        foundMessage = true;
                                    }
                                });

                                if (foundMessage) {
                                    console.log('检测到 ChatGPT 新消息,阻止自动滚动');

                                    setTimeout(() => {
                                        if (window.scrollY > lastScrollPosition + 50) {
                                            window.scrollTo(0, lastScrollPosition);
                                        }
                                    }, 100);
                                }
                            }
                        });
                    }
                });
            }
            lastScrollPosition = window.scrollY;
        });

        window.addEventListener('scroll', () => {
            if (!scrollBlocked) {
                lastScrollPosition = window.scrollY;
            }
        });

        observer.observe(document.body, {
            childList: true,
            subtree: true,
            attributes: true
        });

        setInterval(() => {
            if (scrollBlocked) {
                const currentScroll = window.scrollY;
                if (currentScroll > lastScrollPosition + 100) {
                    console.log('ChatGPT: 阻止意外的大幅滚动');
                    window.scrollTo(0, lastScrollPosition);
                }
            }
        }, 200);
    }

    function applyGeminiFix() {
        let lastScrollPosition = 0;

        const observer = new MutationObserver((mutations) => {
            if (scrollBlocked) {
                mutations.forEach((mutation) => {
                    if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
                        mutation.addedNodes.forEach((node) => {
                            if (node.nodeType === 1) {
                                const selectors = [
                                    '[data-message-id]',
                                    '.model-response-container',
                                    '.conversation-container',
                                    '[role="log"]',
                                    '.response-container'
                                ];

                                let foundMessage = false;
                                selectors.forEach(selector => {
                                    if (node.matches && node.matches(selector) ||
                                        node.querySelector && node.querySelector(selector)) {
                                        foundMessage = true;
                                    }
                                });

                                if (foundMessage) {
                                    console.log('检测到 Gemini 新消息,阻止自动滚动');
                                    setTimeout(() => {
                                        if (window.scrollY > lastScrollPosition + 50) {
                                            window.scrollTo(0, lastScrollPosition);
                                        }
                                    }, 100);
                                }
                            }
                        });
                    }
                });
            }
            lastScrollPosition = window.scrollY;
        });

        window.addEventListener('scroll', () => {
            if (!scrollBlocked) {
                lastScrollPosition = window.scrollY;
            }
        });

        observer.observe(document.body, {
            childList: true,
            subtree: true,
            attributes: true
        });

        setInterval(() => {
            if (scrollBlocked) {
                const currentScroll = window.scrollY;
                if (currentScroll > lastScrollPosition + 100) {
                    console.log('Gemini: 阻止意外的大幅滚动');
                    window.scrollTo(0, lastScrollPosition);
                }
            }
        }, 200);
    }

    // Claude AI 的防滚动修复
    function applyClaudeFix() {
        let lastScrollPosition = 0;

        const observer = new MutationObserver((mutations) => {
            if (scrollBlocked) {
                mutations.forEach((mutation) => {
                    if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
                        mutation.addedNodes.forEach((node) => {
                            if (node.nodeType === 1) {
                                const selectors = [
                                    '[data-testid="message"]',
                                    '.font-user-message',
                                    '.font-claude-message',
                                    '.message',
                                    '.conversation-message'
                                ];

                                let foundMessage = false;
                                selectors.forEach(selector => {
                                    if (node.matches && node.matches(selector) ||
                                        node.querySelector && node.querySelector(selector)) {
                                        foundMessage = true;
                                    }
                                });

                                if (foundMessage) {
                                    console.log('检测到 Claude AI 新消息,阻止自动滚动');
                                    setTimeout(() => {
                                        if (window.scrollY > lastScrollPosition + 50) {
                                            window.scrollTo(0, lastScrollPosition);
                                        }
                                    }, 100);
                                }
                            }
                        });
                    }
                });
            }
            lastScrollPosition = window.scrollY;
        });

        window.addEventListener('scroll', () => {
            if (!scrollBlocked) {
                lastScrollPosition = window.scrollY;
            }
        });

        observer.observe(document.body, {
            childList: true,
            subtree: true,
            attributes: true
        });

        setInterval(() => {
            if (scrollBlocked) {
                const currentScroll = window.scrollY;
                if (currentScroll > lastScrollPosition + 100) {
                    console.log('Claude AI: 阻止意外的大幅滚动');
                    window.scrollTo(0, lastScrollPosition);
                }
            }
        }, 200);
    }

    // 检测网站类型并应用相应的防滚动策略
    function detectSiteAndApplyFix() {
        const hostname = window.location.hostname;

        if (hostname.includes('chatgpt.com') || hostname.includes('openai.com')) {
            console.log('检测到 ChatGPT 网站,启用防自动滚动');
            applyChatGPTFix();
        } else if (hostname.includes('gemini.google.com') || hostname.includes('bard.google.com')) {
            console.log('检测到 Gemini 网站,启用防自动滚动');
            applyGeminiFix();
        } else if (hostname.includes('claude.ai')) {
            console.log('检测到 Claude AI 网站,启用防自动滚动');
            applyClaudeFix();
        }
    }

    // 初始化脚本
    function init() {
        console.log('ChatGPT & Gemini & Claude 防自动滚动脚本 v1.0 已启动');

        // 应用滚动阻止
        blockAutoScroll();

        // 阻止滚动事件
        preventScrollEvents();

        // 检测并应用网站特定修复
        detectSiteAndApplyFix();
    }

    // 立即运行初始化
    init();

})();