Instant Scroll

在浏览器中添加悬浮翻页按钮,实现瞬时滚动。兼容即刻、小红书等单页面应用(SPA)的内部区域滚动。

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

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

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name         Instant Scroll
// @namespace    http://tampermonkey.net/
// @version      2.5.1
// @description  在浏览器中添加悬浮翻页按钮,实现瞬时滚动。兼容即刻、小红书等单页面应用(SPA)的内部区域滚动。
// @author       chen
// @match        https://*/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // 避免在非顶层窗口(如 iframe)中加载
    if (window.top !== window.self) return;

    // 创建悬浮容器
    const container = document.createElement('div');
    container.style.position = 'fixed';
    // 最小 60px,首选屏幕高度的 8%,最大不超过 100px
    container.style.bottom = 'clamp(60px, 8vh, 100px)';
    // 最小 10px,首选屏幕宽度的 3%,最大不超过 40px
    container.style.left = 'clamp(10px, 3vw, 40px)';
    container.style.zIndex = '999999';
    container.style.display = 'flex';
    container.style.flexDirection = 'column';
    container.style.gap = 'clamp(10px, 2vmin, 20px)';

    // 统一样式函数
    function createButton(text) {
        const btn = document.createElement('button');
        btn.innerText = text;

        // 按钮大小:最小 35px,理想情况屏幕较短边的 8%,最大 60px
        const size = 'clamp(35px, 8vmin, 60px)';
        btn.style.width = size;
        btn.style.height = size;

        btn.style.borderRadius = '50%';
        btn.style.backgroundColor = 'transparent';
        btn.style.color = '#000';
        btn.style.border = 'solid #333333';
        btn.style.textShadow = `
            -1px -1px 0 #ffffff,  
            1px -1px 0 #ffffff,
            -1px 1px 0 #ffffff,
            1px 1px 0 #ffffff,
            0px 1px 0 #ffffff,
            0px -1px 0 #ffffff,
            -1px 0px 0 #ffffff,
            1px 0px 0 #ffffff
        `;

        // 字体大小:最小 14px,首选 3vmin,最大 22px
        btn.style.fontSize = 'clamp(14px, 3vmin, 22px)';
        // 优化:防止频繁点击时选中文本
        btn.style.userSelect = 'none';
        btn.style.cursor = 'pointer';

        // 关闭移动端触摸时产生的灰色高亮背景(最关键的一步)
        btn.style.webkitTapHighlightColor = 'transparent';

        return btn;
    }

    const btnUp = createButton('▲');
    const btnDown = createButton('▼');

    // === 核心改进逻辑:动态检测并记录当前激活的滚动容器 ===

    let activeScrollContainer = window;

    // 向上遍历DOM树,寻找具有滚动条的容器
    function getScrollContainer(node) {
        let current = node;
        while (current && current !== document && current !== document.body && current !== document.documentElement) {
            // 确保是元素节点
            if (current.nodeType === 1) {
                const style = window.getComputedStyle(current);
                const overflowY = style.overflowY;
                const isScrollable = (overflowY === 'auto' || overflowY === 'scroll' || overflowY === 'overlay');
                // 判断是否具有可滚动属性,并且内容确实超出了容器高度
                if (isScrollable && current.scrollHeight > current.clientHeight) {
                    return current;
                }
            }
            current = current.parentNode;
        }
        return window;
    }

    // 监听用户的点击或触摸事件,更新目标滚动区域
    function updateActiveContainer(e) {
        // 如果点击的是悬浮按钮自身,则不更新
        if (container.contains(e.target)) return;

        let target = e.target;
        // 如果点到了文本节点,取它的父元素
        if (target.nodeType !== 1) target = target.parentElement;

        activeScrollContainer = getScrollContainer(target);
    }

    // 使用捕获阶段 (true) 监听鼠标点击与平板触摸,确保能第一时间捕捉焦点
    document.addEventListener('mousedown', updateActiveContainer, true);
    document.addEventListener('touchstart', updateActiveContainer, true);

    // 智能获取要滚动的容器
    function getTargetContainer() {
        // 1. 如果之前记录的容器依然有效且还在 DOM 树中,直接使用
        if (activeScrollContainer && activeScrollContainer !== window && document.contains(activeScrollContainer)) {
            return activeScrollContainer;
        }

        // 2. 智能回退:如果没有主动点击页面(刚刷新网页),尝试获取屏幕中心的元素来推测滚动容器
        const centerX = window.innerWidth / 2;
        const centerY = window.innerHeight / 2;
        const el = document.elementFromPoint(centerX, centerY);
        if (el) {
            const centerContainer = getScrollContainer(el);
            if (centerContainer !== window) {
                activeScrollContainer = centerContainer;
                return centerContainer;
            }
        }

        // 3. 默认回退使用 window
        return window;
    }

    // 执行滚动的统一下发函数
    function doScroll(direction) {
        const target = getTargetContainer();
        // direction: -1 为向上,1 为向下
        const distance = direction * window.innerHeight * 0.85;

        if (target === window) {
            window.scrollBy({ top: distance, behavior: 'instant' });
        } else {
            target.scrollBy({ top: distance, behavior: 'instant' });
        }
    }

    // 绑定 Page Up 滚动逻辑 (向上滚动当前屏幕高度的85%)
    btnUp.addEventListener('click', (e) => {
        e.preventDefault();
        e.stopPropagation();
        doScroll(-1);
    });

    // 绑定 Page Down 滚动逻辑 (向下滚动当前屏幕高度的85%)
    btnDown.addEventListener('click', (e) => {
        e.preventDefault();
        e.stopPropagation();
        doScroll(1);
    });

    // 将按钮添加到页面
    container.appendChild(btnUp);
    container.appendChild(btnDown);
    document.body.appendChild(container);
})();