Scroll page on double tap (mobile)

This userscript is designed for mobile browsers, and scrolls page on double tap. Top half of the screen scrolls up, and bottom half scrolls down. Single taps are ignored for a specified timeout while script waits for a second tap, and then get dispatched programmatically.

目前为 2024-10-30 提交的版本。查看 最新版本

// ==UserScript==
// @name         Scroll page on double tap (mobile)
// @description  This userscript is designed for mobile browsers, and scrolls page on double tap. Top half of the screen scrolls up, and bottom half scrolls down. Single taps are ignored for a specified timeout while script waits for a second tap, and then get dispatched programmatically.
// @version      1.0.0
// @author       emvaized
// @license      MIT
// @namespace    scroll_page_on_double_tap
// @match        <all_urls>
// @grant        none
// @run-at       document-start
// ==/UserScript==

(function() {
    'use strict';

    const amountToScroll = 0.8; // Ratio to screen height, e.g. '0.8' means 80% of screen height
    const doubleTapTimeout = 200; // Timeout for second tap in milliseconds
    const maxTapMovement = 10; // Maximum movement (in pixels) to qualify as a tap

    let lastTapTime = 0; // To track timing between taps
    let firstTapEvent = null; // To store the first tap event details
    let startX = 0; // Start position for touch
    let startY = 0;

    document.addEventListener('touchstart', function(event) {
        // Only handle single-finger touches
        if (event.touches.length === 1) {
            startX = event.touches[0].clientX;
            startY = event.touches[0].clientY;
        }
    }, true);

    document.addEventListener('touchend', function(event) {
        // Only proceed if it’s a single-finger touch and movement is minimal
        if (event.changedTouches.length > 1) return; // Ignore multi-touch events

        const endX = event.changedTouches[0].clientX;
        const endY = event.changedTouches[0].clientY;
        const deltaX = Math.abs(endX - startX);
        const deltaY = Math.abs(endY - startY);

        // If movement exceeds maxTapMovement, consider it a scroll/swipe and ignore
        if (deltaX > maxTapMovement || deltaY > maxTapMovement) return;

        const currentTime = new Date().getTime();
        const tapInterval = currentTime - lastTapTime;

        // Prevent default action (including link navigation)
        event.preventDefault(); 
        event.stopPropagation();

        if (tapInterval < doubleTapTimeout && tapInterval > 0) {
            // Double-tap detected within timeout
            // Execute custom double-tap action: scroll down
            window.scrollBy({
                top: (event.changedTouches[0].clientY > (window.screen.height / 2) ? 1 : -1) * window.visualViewport.height * amountToScroll, 
                behavior: "smooth"
            })

            // Reset stored event and timing
            lastTapTime = 0;
            firstTapEvent = null;
        } else {
            // First tap: store the event and set timeout for single-tap action
            firstTapEvent = event;
            lastTapTime = currentTime;

            setTimeout(() => {
                // If no second tap, execute single-tap action
                if (firstTapEvent) {
                    // Trigger click only if it's a single tap, re-dispatching it as a new event
                    const singleClickEvent = new MouseEvent('click', {
                        bubbles: true,
                        cancelable: true,
                        view: window
                    });
                    firstTapEvent.target.dispatchEvent(singleClickEvent);
                }
                firstTapEvent = null;
            }, doubleTapTimeout);
        }
    }, true);
})();