您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
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. Double tap and move finger enables fast scrolling mode. When page is already scrolling, single tap will scroll it further.
// ==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. Double tap and move finger enables fast scrolling mode. When page is already scrolling, single tap will scroll it further. // @description:ru Этот скрипт разработан для мобильных браузеров, и прокручивает страницу при двойном нажатии. Верхняя половина экрана прокручивает вверх, а нижняя половина — вниз. Двойное нажатие и движение пальцем включает режим быстрой прокрутки. Когда страница уже прокручивается, одиночный тап прокрутит её дальше. // @version 1.0.2 // @author emvaized // @license MIT // @namespace scroll_page_on_double_tap // @match *://*/* // @grant none // @run-at document-start // ==/UserScript== /* *** Changelog 1.0.2 - switched to screenY instead of clientY to determine screen halves - reordered config variables for better relevance 1.0.1 - implemented fling (kinetic) scrolling - make preventing first tap toggleable, and disable by default - implemented fast scrolling on double tap + move (enabled by default) 1.0.0 Initial release *** */ (function() { 'use strict'; // Configs const fastScrollingEnabled = true; // To enter fast scrolling mode, double tap and move finger on a second tap const fastScrollingFriction = 0.93; // Multiplier for dy touch movement during fast scrolling const scrollVelocity = 13.5; // Initial speed of the scroll (higher values will make it faster) const scrollDecay = 0.98; // The rate at which the scroll slows down (values closer to 1 will make the scroll slower to decelerate) const scrollInterval = 8; // Time in milliseconds between each scroll step (16 ms gives approximately 60 frames per second) const doubleTapTimeout = 200; // Timeout for second tap in milliseconds const maxTapMovement = 10; // Maximum touch movement (in pixels) to qualify as a tap const preventFirstTap = false; // With this flag script will block the first tap on page and wait for the second tap. Allows to double tap links and images to scroll, but causes issues // Service variables let lastTapUpTime = 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; let isFastScrolling = false; // Manual scroll mode on double tap + move let lastMoveY; // Last Y position for manual scrolling let doubleTapDownTimeout; let lastTapDownTime = 0; // To track timing between taps let preventFlingScrolling = false; // Use to stop fling scroll when user manually scrolled page let isFlingScrolling = false; // To track if page is currently in kinetic scrolling state 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; // Reset variables from last taps lastMoveY = null; preventFlingScrolling = false; // Detect double tap for manual scrolling const currentTapDownTime = new Date().getTime(); const tapDownInterval = currentTapDownTime - lastTapDownTime; if (fastScrollingEnabled && tapDownInterval < doubleTapTimeout && tapDownInterval > 0) { // Double-tap detected within timeout cancelEvent(event); // Enable manual scrolling mode doubleTapDownTimeout = setTimeout(() => { isFastScrolling = true; }, doubleTapTimeout); } else { // First tap: store the event and set timeout for single-tap action lastTapDownTime = currentTapDownTime; isFastScrolling = false; if (preventFirstTap) cancelEvent(event); } } }, { passive: fastScrollingEnabled || preventFirstTap ? false : true}, true); if (fastScrollingEnabled) window.addEventListener('touchmove', function(event){ if ((isFastScrolling || isFlingScrolling) && event.touches.length === 1) { const currentMoveY = event.touches[0].clientY; if(!lastMoveY) lastMoveY = currentMoveY; const scrollDelta = lastMoveY - currentMoveY; const scrollDeltaAbs = Math.abs(scrollDelta); if(isFastScrolling){ // Prevent default action (scrolling page) cancelEvent(event); lastMoveY = currentMoveY; if(scrollDeltaAbs > 0.3) flingScroll( window, (fastScrollingFriction * scrollDelta) * 2, scrollDecay, scrollInterval ); } else if(isFlingScrolling && scrollDeltaAbs > 0.3) { isFlingScrolling = false; preventFlingScrolling = true; } } }, { passive: false }, true) document.addEventListener('touchend', function(event) { // Only proceed if it’s a single-finger touch event if (event.changedTouches.length > 1) return; 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 - lastTapUpTime; // Disable manual scrolling mode isFastScrolling = false; clearTimeout(doubleTapDownTimeout); // Scroll up if tapped in top half of the screen, scroll down if tapped in bottom half const scrollDown = event.changedTouches[0].screenY > (window.screen.height / 2); if (tapInterval < doubleTapTimeout && tapInterval > 0) { // Double-tap detected within timeout // Prevent default action (including link navigation) cancelEvent(event); // Execute custom double-tap action: scroll down scrollPage(scrollDown); // Reset stored event and timing lastTapUpTime = 0; firstTapEvent = null; } else { // First tap // If kinetic scrolling in proccess, scroll again if(isFlingScrolling){ cancelEvent(event); scrollPage(scrollDown); return; } // Store the event and set timeout for single-tap action firstTapEvent = event; lastTapUpTime = currentTime; setTimeout(() => { // If no second tap, execute single-tap action if (preventFirstTap && firstTapEvent && !isFlingScrolling && !isFastScrolling) { if(isFlingScrolling) return; // Prevent default action event.preventDefault(); event.stopPropagation(); // Create and dispatch a manual click event const singleClickEvent = new MouseEvent('click', { bubbles: true, cancelable: true, button: 0, detail: 1, view: window, }); firstTapEvent.target.dispatchEvent(singleClickEvent); } firstTapEvent = null; }, doubleTapTimeout); } }, { passive: false }, true); // Prevent regular scrolling when manual scroll is in progress if (fastScrollingEnabled){ document.addEventListener('wheel', function(e) { if(isFastScrolling) cancelEvent(event); }, { passive: false }); document.addEventListener('scroll', function(e) { if(isFastScrolling) cancelEvent(event); }, { passive: false }); } // Scroll page up or down function scrollPage(scrollDown){ flingScroll( document.scrollingElement, (scrollDown ? 1 : -1) * scrollVelocity / window.visualViewport.scale, scrollDecay, scrollInterval ) } // Prevent default event action function cancelEvent(event){ event.preventDefault(); event.stopPropagation(); // event.stopImmediatePropagation(); } // For fling scroll (kinetic scrolling) function flingScroll(element, velocity = 13.5, decay = 0.98, interval = 8) { let currentVelocity = velocity; // initial velocity of the fling scroll function scrollStep() { isFlingScrolling = true; if(preventFlingScrolling && !isFastScrolling) { isFlingScrolling = false; return; } // Scroll the element by the current velocity element.scrollBy({ left: 0, top: currentVelocity, behavior: "instant" }); // Reduce the velocity to simulate natural slowing down currentVelocity *= decay; // Stop scrolling if the velocity is very low if (Math.abs(currentVelocity) > 1) { setTimeout(scrollStep, interval); } else { isFlingScrolling = false; } } // Start the fling scroll scrollStep(); } })();