您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Universal smooth scrolling for mouse wheel only. Touchpad uses native scrolling.
当前为
// ==UserScript== // @name Smooth Scroll // @description Universal smooth scrolling for mouse wheel only. Touchpad uses native scrolling. // @author DXRK1E // @icon https://i.imgur.com/IAwk6NN.png // @include * // @exclude https://www.youtube.com/* // @exclude https://mail.google.com/* // @version 2.3 // @namespace sttb-dxrk1e // @license MIT // ==/UserScript== (function() { 'use strict'; class SmoothScroll { constructor() { this.config = { smoothness: 0.8, // Increased for more stability acceleration: 0.25, // Reduced to prevent jumps minDelta: 0.5, // Increased minimum threshold maxRefreshRate: 144, // Reduced max refresh rate minRefreshRate: 30, defaultRefreshRate: 60, debug: false }; this.state = { isLoaded: false, lastFrameTime: 0, lastWheelTime: 0, lastDelta: 0, scrollHistory: [], activeScrollElements: new WeakMap() }; this.handleWheel = this.handleWheel.bind(this); this.handleClick = this.handleClick.bind(this); this.animateScroll = this.animateScroll.bind(this); this.detectScrollDevice = this.detectScrollDevice.bind(this); } init() { if (window.top !== window.self || this.state.isLoaded) { return; } if (!window.requestAnimationFrame) { window.requestAnimationFrame = window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame || ((cb) => setTimeout(cb, 1000 / 60)); } document.addEventListener('wheel', this.handleWheel, { passive: false, capture: true }); document.addEventListener('mousedown', this.handleClick, true); document.addEventListener('touchstart', this.handleClick, true); document.addEventListener('visibilitychange', () => { if (document.hidden) { this.clearAllScrolls(); } }); this.state.isLoaded = true; this.log('Smooth Scroll Activated (Mouse Only)'); } detectScrollDevice(event) { const now = performance.now(); const timeDelta = now - this.state.lastWheelTime; // Update scroll history for better detection this.state.scrollHistory.push({ delta: event.deltaY, time: now, mode: event.deltaMode }); // Keep only last 5 events if (this.state.scrollHistory.length > 5) { this.state.scrollHistory.shift(); } // Analyze scroll pattern const isConsistent = this.analyzeScrollPattern(); // More accurate touchpad detection const isTouchpad = ( (Math.abs(event.deltaY) < 5 && event.deltaMode === 0) || // Very small precise deltas (timeDelta < 32 && this.state.scrollHistory.length > 2) || // Rapid small movements !isConsistent || // Inconsistent scroll pattern (event.deltaMode === 0 && !Number.isInteger(event.deltaY)) // Fractional pixels ); this.state.lastWheelTime = now; this.state.lastDelta = event.deltaY; return isTouchpad; } analyzeScrollPattern() { if (this.state.scrollHistory.length < 3) return true; const deltas = this.state.scrollHistory.map(entry => entry.delta); const avgDelta = deltas.reduce((a, b) => a + Math.abs(b), 0) / deltas.length; // Check if deltas are relatively consistent (characteristic of mouse wheels) return deltas.every(delta => Math.abs(Math.abs(delta) - avgDelta) < avgDelta * 0.5 ); } log(...args) { if (this.config.debug) { console.log('[Smooth Scroll]', ...args); } } getCurrentRefreshRate(timestamp) { const frameTime = timestamp - (this.state.lastFrameTime || timestamp); this.state.lastFrameTime = timestamp; const fps = 1000 / Math.max(frameTime, 1); return Math.min( Math.max(fps, this.config.minRefreshRate), this.config.maxRefreshRate ); } getScrollableParents(element, direction) { const scrollables = []; while (element && element !== document.body) { if (this.isScrollable(element, direction)) { scrollables.push(element); } element = element.parentElement; } if (this.isScrollable(document.body, direction)) { scrollables.push(document.body); } return scrollables; } isScrollable(element, direction) { if (!element || element === window || element === document) { return false; } const style = window.getComputedStyle(element); const overflowY = style['overflow-y']; if (overflowY === 'hidden' || overflowY === 'visible') { return false; } const scrollTop = element.scrollTop; const scrollHeight = element.scrollHeight; const clientHeight = element.clientHeight; return direction < 0 ? scrollTop > 0 : Math.ceil(scrollTop + clientHeight) < scrollHeight; } handleWheel(event) { if (event.defaultPrevented || window.getSelection().toString()) { return; } // If using touchpad, let native scrolling handle it if (this.detectScrollDevice(event)) { return; } const scrollables = this.getScrollableParents(event.target, Math.sign(event.deltaY)); if (!scrollables.length) { return; } const target = scrollables[0]; let delta = event.deltaY; // Normalize delta based on mode if (event.deltaMode === 1) { // LINE mode const lineHeight = parseInt(getComputedStyle(target).lineHeight) || 20; delta *= lineHeight; } else if (event.deltaMode === 2) { // PAGE mode delta *= target.clientHeight; } // Apply a more consistent delta transformation delta = Math.sign(delta) * Math.sqrt(Math.abs(delta)) * 10; this.scroll(target, delta); event.preventDefault(); } handleClick(event) { const elements = this.getScrollableParents(event.target, 0); elements.forEach(element => this.stopScroll(element)); } scroll(element, delta) { if (!this.state.activeScrollElements.has(element)) { this.state.activeScrollElements.set(element, { pixels: 0, subpixels: 0, direction: Math.sign(delta) }); } const scrollData = this.state.activeScrollElements.get(element); // Only accumulate scroll if in same direction or very small remaining scroll if (Math.sign(delta) === scrollData.direction || Math.abs(scrollData.pixels) < 1) { const acceleration = Math.min( 1 + (Math.abs(scrollData.pixels) * this.config.acceleration), 2 ); scrollData.pixels += delta * acceleration; scrollData.direction = Math.sign(delta); } else { // If direction changed, reset acceleration scrollData.pixels = delta; scrollData.direction = Math.sign(delta); } if (!scrollData.animating) { scrollData.animating = true; this.animateScroll(element); } } stopScroll(element) { if (this.state.activeScrollElements.has(element)) { const scrollData = this.state.activeScrollElements.get(element); scrollData.pixels = 0; scrollData.subpixels = 0; scrollData.animating = false; } } clearAllScrolls() { this.state.activeScrollElements = new WeakMap(); } animateScroll(element) { if (!this.state.activeScrollElements.has(element)) { return; } const scrollData = this.state.activeScrollElements.get(element); if (Math.abs(scrollData.pixels) < this.config.minDelta) { scrollData.animating = false; return; } requestAnimationFrame((timestamp) => { const refreshRate = this.getCurrentRefreshRate(timestamp); const smoothnessFactor = Math.pow(refreshRate, -1 / (refreshRate * this.config.smoothness)); // More stable scroll amount calculation const scrollAmount = scrollData.pixels * (1 - smoothnessFactor); const integerPart = Math.trunc(scrollAmount); // Accumulate subpixels more accurately scrollData.subpixels += (scrollAmount - integerPart); let additionalPixels = Math.trunc(scrollData.subpixels); scrollData.subpixels -= additionalPixels; const totalScroll = integerPart + additionalPixels; // Only update if we have a meaningful scroll amount if (Math.abs(totalScroll) >= 1) { scrollData.pixels -= totalScroll; try { element.scrollTop += totalScroll; } catch (error) { this.log('Scroll error:', error); this.stopScroll(element); return; } } if (scrollData.animating) { this.animateScroll(element); } }); } } // Initialize const smoothScroll = new SmoothScroll(); smoothScroll.init(); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址