Mobile Pull Down to Refresh

Enables pull-down-to-refresh on mobile browsers. Swipe down to reload the page, mimicking native app behavior.

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Mobile Pull Down to Refresh
// @namespace    TW9iaWxlIFB1bGwgRG93biB0byBSZWZyZXNo
// @version      1.2
// @description  Enables pull-down-to-refresh on mobile browsers. Swipe down to reload the page, mimicking native app behavior.
// @author       smed79
// @license      GPLv3
// @icon         https://i25.servimg.com/u/f25/11/94/21/24/pd2r10.png
// @homepage     https://greasyfork.org/en/scripts/545016-mobile-pull-down-to-refresh
// @include      http://*
// @include      https://*
// @grant        none
// ==/UserScript==

(function () {
    'use strict';

    // Domains to exclude from pull-to-refresh
    // Examples:
    //   'example.com' excludes that domain
    //   'example.*' matches example.com, example.net, etc.
    const excludedDomains = [
        'greasyfork.org',
        'copilot.microsoft.com',
        'gemini.google.com',
        'grok.com',
        'translate.google.*',
    ];

    // Subdomains to exclude
    // Examples:
    //   '*.example.com' matches blog.example.com, shop.example.com, etc.
    const excludedSubdomains = [
        '*.translate.goog',
    ];

    // Check if current site matches any exclusion pattern
    function isExcludedSite(hostname) {
        for (const pattern of excludedSubdomains) {
            const regex = new RegExp(
                '^' + pattern.replace(/\./g, '\\.').replace('*', '[^.]+') + '$',
                'i'
            );
            if (regex.test(hostname)) return true;
        }

        for (const pattern of excludedDomains) {
            const regex = new RegExp(
                '^((www\\.)?)' + pattern.replace(/\./g, '\\.').replace('*', '[^.]+') + '$',
                'i'
            );
            if (regex.test(hostname)) return true;
        }

        return false;
    }

    // Exit early if current site is excluded
    if (isExcludedSite(location.hostname)) return;

    let startY = 0;
    let isPulling = false;
    const threshold = 80; // Minimum pull distance in pixels to trigger refresh
    let cooldown = false; // Prevents rapid reloads

    // Check if an element is scrollable (e.g. input or textarea)
    function isScrollableElement(el) {
        const tag = el.tagName.toLowerCase();
        if (tag === 'textarea' || tag === 'input') return true;

        const style = window.getComputedStyle(el);
        return (
            style.overflowY === 'scroll' ||
            style.overflowY === 'auto' ||
            el.scrollHeight > el.clientHeight
        );
    }

    // Detect start of touch gesture
    document.addEventListener('touchstart', (e) => {
        const target = e.target;

        // Skip if interacting with scrollable elements
        if (isScrollableElement(target)) {
            isPulling = false;
            return;
        }

        // Only activate if scrolled to top
        if (window.scrollY === 0 && !cooldown) {
            startY = e.touches[0].clientY;
            isPulling = true;
        }
    });

    // Detect pull gesture movement
    document.addEventListener('touchmove', (e) => {
        if (!isPulling) return;

        const currentY = e.touches[0].clientY;
        const distance = currentY - startY;

        if (distance > threshold) {
            isPulling = false;
            cooldown = true;
            location.reload();

            // Cooldown to prevent rapid reloads
            setTimeout(() => {
                cooldown = false;
            }, 3000); // 3 seconds
        }
    });

    // Reset gesture tracking on touch end
    document.addEventListener('touchend', () => {
        isPulling = false;
    });
})();