您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
更健壮地修复滚动问题,尝试处理延迟加载的图片和初始加载场景。
- // ==UserScript==
- // @name Telegram Web - Robust Scroll Fix (v1.4 - Image/Load Handling)
- // @name:zh-CN 电报网页版 - 健壮滚动修复 (v1.4 - 图片/加载处理)
- // @namespace http://tampermonkey.net/
- // @version 1.4
- // @description Attempts to fix scroll issues more robustly, handling late-loading images and initial load scenarios.
- // @description:zh-CN 更健壮地修复滚动问题,尝试处理延迟加载的图片和初始加载场景。
- // @author AI Assistant & You
- // @match *://web.telegram.org/k/*
- // @grant GM_addStyle
- // @icon https://web.telegram.org/favicon.ico
- // @run-at document-idle
- // ==/UserScript==
- (function() {
- 'use strict';
- // --- 配置 ---
- const SCROLL_THRESHOLD = 300; // 判断“接近底部”的阈值 (像素)
- const POST_CORRECTION_CHECK_DELAY = 150; // rAF修正后再次检查的延迟(ms), 处理图片等慢加载
- const INITIAL_SCROLL_DELAY = 1800; // 初始加载后检查滚动的延迟(ms), 增加时间
- const CHECK_INTERVAL = 1000; // 查找容器间隔
- const SCROLL_CONTAINER_SELECTOR = '#column-center .scrollable.scrollable-y'; // K 版滚动容器
- // --- --- ---
- let targetNode = null; // 滚动容器
- let observer = null;
- let correctionFrameId = null; // 用于 requestAnimationFrame
- let postCorrectionTimeoutId = null; // 后置检查计时器
- let wasNearBottom = false; // 由滚动事件更新
- let isScrolling = false; // 标记用户是否正在滚动
- let scrollDebounceTimeout = null;
- let styleAdded = false; // 标记样式是否已添加
- console.log("[ScrollFixer v1.4] Script initializing...");
- // 注入 CSS 样式
- function applyStyles() {
- if (!styleAdded) {
- try {
- GM_addStyle(`
- ${SCROLL_CONTAINER_SELECTOR} {
- overflow-anchor: auto !important;
- }
- .scrollable-y.script-scrolling {
- scroll-behavior: auto !important;
- }
- `);
- styleAdded = true;
- console.log("[ScrollFixer v1.4] Applied CSS via GM_addStyle.");
- } catch (e) {
- console.error("[ScrollFixer v1.4] GM_addStyle failed.", e);
- const styleElement = document.createElement('style');
- styleElement.textContent = `
- ${SCROLL_CONTAINER_SELECTOR} { overflow-anchor: auto !important; }
- .scrollable-y.script-scrolling { scroll-behavior: auto !important; }
- `;
- document.head.appendChild(styleElement);
- console.warn("[ScrollFixer v1.4] Using fallback style injection.");
- }
- }
- }
- function isNearBottom(element) {
- if (!element) return false;
- const isScrollable = element.scrollHeight > element.clientHeight;
- return !isScrollable || (element.scrollHeight - element.scrollTop - element.clientHeight < SCROLL_THRESHOLD);
- }
- // 检查是否 *明确地* 远离底部
- function isFarFromBottom(element) {
- if (!element) return false; // 如果没有元素,不认为远离 (避免错误滚动)
- const isScrollable = element.scrollHeight > element.clientHeight;
- // 只有可滚动且距离底部大于阈值才算远离
- return isScrollable && (element.scrollHeight - element.scrollTop - element.clientHeight >= SCROLL_THRESHOLD);
- }
- function scrollToBottom(element, reason = "correction", behavior = 'auto') {
- if (!element || !document.contains(element)) return;
- const currentScroll = element.scrollTop;
- const maxScroll = element.scrollHeight - element.clientHeight;
- // 增加容错
- if (maxScroll - currentScroll < 1) {
- // console.log(`[ScrollFixer v1.4] Already at bottom (${reason}), scroll skipped.`);
- // 即使跳过滚动,也要确保状态正确
- if (!wasNearBottom) { // 如果之前认为不在底部,现在强制设为在底部
- wasNearBottom = true;
- // console.log("[ScrollFixer v1.4] State corrected to wasNearBottom=true after skip.");
- }
- return;
- }
- console.log(`[ScrollFixer v1.4] Forcing scroll to bottom (Reason: ${reason}, Behavior: ${behavior}).`);
- element.classList.add('script-scrolling');
- element.scrollTo({ top: element.scrollHeight, behavior: behavior });
- // 滚动后立即更新状态
- wasNearBottom = true;
- requestAnimationFrame(() => {
- requestAnimationFrame(() => {
- if (element) element.classList.remove('script-scrolling');
- });
- });
- }
- // 处理滚动事件
- function handleScroll() {
- if (!targetNode) return;
- if (targetNode.classList.contains('script-scrolling')) {
- return; // 忽略脚本触发的滚动
- }
- isScrolling = true;
- clearTimeout(scrollDebounceTimeout);
- wasNearBottom = isNearBottom(targetNode);
- // console.log(`[ScrollFixer v1.4] Scroll event. Near bottom: ${wasNearBottom}`);
- scrollDebounceTimeout = setTimeout(() => {
- isScrolling = false;
- // console.log("[ScrollFixer v1.4] Scroll ended.");
- }, 150);
- }
- // 处理 DOM 变化的函数 - 即时 rAF + 后置检查
- function handleMutations(mutationsList, observer) {
- if (isScrolling) {
- return;
- }
- if (wasNearBottom && targetNode) {
- // console.log("[ScrollFixer v1.4] Mutation detected while near bottom. Scheduling immediate rAF correction.");
- // 取消上一个可能未执行的帧和后置检查
- if (correctionFrameId) cancelAnimationFrame(correctionFrameId);
- clearTimeout(postCorrectionTimeoutId); // 清除上一个后置检查
- // 立即请求下一帧执行修正
- correctionFrameId = requestAnimationFrame(() => {
- correctionFrameId = null; // 清除 ID
- if (targetNode && document.contains(targetNode)) {
- // console.log("[ScrollFixer v1.4] Performing immediate rAF scroll correction.");
- scrollToBottom(targetNode, "mutation_rAF", 'auto');
- // --- 新增:rAF 修正后,安排一个后置检查 ---
- postCorrectionTimeoutId = setTimeout(() => {
- postCorrectionTimeoutId = null; // 清除 ID
- if (targetNode && document.contains(targetNode) && !isScrolling) {
- // console.log("[ScrollFixer v1.4] Performing post-correction check.");
- if (!isNearBottom(targetNode)) { // 如果此时因为图片加载等原因又不在底部了
- console.warn("[ScrollFixer v1.4] Post-correction check failed. Scrolling again.");
- scrollToBottom(targetNode, "post_check", 'auto'); // 再次滚动
- } else {
- // console.log("[ScrollFixer v1.4] Post-correction check passed.");
- // 确保状态正确
- wasNearBottom = true;
- }
- }
- }, POST_CORRECTION_CHECK_DELAY);
- // --- --- ---
- } else {
- // console.warn("[ScrollFixer v1.4] Target node lost before immediate rAF execution.");
- }
- });
- }
- }
- // 查找并观察消息列表容器
- function findAndObserve() {
- const targetSelector = SCROLL_CONTAINER_SELECTOR;
- let potentialNode = document.querySelector(targetSelector);
- if (observer && targetNode === potentialNode && document.contains(targetNode)) {
- return;
- }
- // Cleanup
- if (observer) { /* ... 清理逻辑 ... */ }
- targetNode = potentialNode;
- if (targetNode) {
- console.log("[ScrollFixer v1.4] Chat scroll container found:", targetNode);
- applyStyles();
- // 初始滚动检查 - 更保守
- setTimeout(() => {
- if (targetNode && document.contains(targetNode)) {
- console.log("[ScrollFixer v1.4] Performing initial check...");
- // 只有当明确检测到远离底部时,才执行初始滚动
- if (isFarFromBottom(targetNode)) {
- console.log("[ScrollFixer v1.4] Far from bottom on initial check. Scrolling down.");
- scrollToBottom(targetNode, "initial_load", 'auto');
- wasNearBottom = true;
- } else {
- console.log("[ScrollFixer v1.4] Already near bottom or content not fully loaded? Initial scroll skipped/deferred.");
- // 确保状态与实际情况一致
- wasNearBottom = isNearBottom(targetNode);
- }
- console.log(`[ScrollFixer v1.4] Initial state - near bottom: ${wasNearBottom}`);
- // Setup listeners/observer
- if (!observer) {
- targetNode.addEventListener('scroll', handleScroll, { passive: true });
- console.log("[ScrollFixer v1.4] Scroll listener added.");
- observer = new MutationObserver(handleMutations);
- const config = { childList: true, subtree: true };
- observer.observe(targetNode, config);
- console.log("[ScrollFixer v1.4] MutationObserver started.");
- }
- } else {
- console.log("[ScrollFixer v1.4] Target node disappeared before initial setup.");
- }
- }, INITIAL_SCROLL_DELAY);
- } else {
- console.log(`[ScrollFixer v1.4] Chat container not found, retrying...`);
- setTimeout(findAndObserve, CHECK_INTERVAL);
- }
- }
- // --- SPA Navigation Handling & Initial Start ---
- // (与 v1.3 类似,注意清理 postCorrectionTimeoutId)
- let currentUrl = location.href;
- const urlObserver = new MutationObserver(() => {
- if (location.href !== currentUrl) {
- console.log(`[ScrollFixer v1.4] URL changed. Re-initializing for ${location.href}`);
- currentUrl = location.href;
- // Cleanup state and timers
- if (observer) { observer.disconnect(); observer = null; }
- if (targetNode && typeof targetNode.removeEventListener === 'function') {
- targetNode.removeEventListener('scroll', handleScroll);
- }
- if (correctionFrameId) { cancelAnimationFrame(correctionFrameId); correctionFrameId = null; }
- clearTimeout(postCorrectionTimeoutId); // 清理后置检查计时器
- targetNode = null;
- wasNearBottom = false;
- isScrolling = false;
- clearTimeout(scrollDebounceTimeout);
- // styleAdded = false; // GM_addStyle persists
- setTimeout(findAndObserve, 500);
- }
- });
- urlObserver.observe(document.body, { childList: true, subtree: false });
- setTimeout(findAndObserve, 750);
- })();