Amazon Page Smoother

Safe performance optimization for Amazon: background throttling, lazy loading, and layout stabilization.

当前为 2025-11-23 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Amazon Page Smoother
// @namespace    http://tampermonkey.net/
// @version      0.0.8
// @description  Safe performance optimization for Amazon: background throttling, lazy loading, and layout stabilization.
// @license      Unlicense
// @author       VeleSila
// @match        https://www.amazon.com/*
// @match        https://www.amazon.co.jp/*
// @match        https://www.amazon.co.uk/*
// @match        https://www.amazon.es/*
// @match        https://www.amazon.fr/*
// @match        https://www.amazon.de/*
// @match        https://www.amazon.it/*
// @match        https://www.amazon.ca/*
// @match        https://www.amazon.com.au/*
// @exclude      */cart/*
// @exclude      */buy/*
// @exclude      */checkout/*
// @exclude      */gp/buy/*
// @grant        GM_addStyle
// @run-at       document-start
// ==/UserScript==

(function() {
    'use strict';

    // --- Configuration ---
    /**
     * @typedef {Object} Config
     * @property {number} BACKGROUND_TICK_RATE_MS - Minimum interval delay when tab is hidden (ms)
     * @property {boolean} DISABLE_ANIMATION_IN_BACKGROUND - Pause animations when tab is hidden
     * @property {boolean} ENABLE_CSS_CONTAINMENT - Use CSS containment for rendering optimization
     * @property {number} DEBOUNCE_DELAY_MS - Delay for debouncing mutation observer (ms)
     * @property {number} CRITICAL_IMAGE_THRESHOLD - Number of top images to prioritize
     */
    const CONFIG = {
        BACKGROUND_TICK_RATE_MS: 2000,
        DISABLE_ANIMATION_IN_BACKGROUND: true,
        ENABLE_CSS_CONTAINMENT: true,
        DEBOUNCE_DELAY_MS: 200, // Reduced from 200 to align with original fallback
        CRITICAL_IMAGE_THRESHOLD: 4 // Heuristic for "above-the-fold" images
    };

    // --- Feature Detection ---
    /**
     * @typedef {Object} SupportedFeatures
     * @property {boolean} fetchPriority - Supports HTMLImageElement.fetchPriority
     * @property {boolean} requestIdleCallback - Supports window.requestIdleCallback
     * @property {boolean} closest - Supports Element.closest()
     */
    const SUPPORTS = {
        fetchPriority: 'fetchPriority' in HTMLImageElement.prototype,
        requestIdleCallback: typeof window.requestIdleCallback === 'function',
        closest: typeof Element.prototype.closest === 'function'
    };

    /**
     * 1. Critical Safety Check: Exit on sensitive pages (checkout, sign-in, etc.)
     * Uses pathname instead of full URL to avoid false positives from query params.
     */
    const sensitivePaths = /(checkout|signin|payment|addressselect|huc)/i;
    if (sensitivePaths.test(window.location.pathname)) {
        console.log('[Amazon Smoother] Sensitive page detected. Script disabled.');
        return;
    }

    /**
     * 2. CSS Layout Optimizer: Inject styles for rendering performance
     * Uses CSS containment to limit browser reflows/repaints.
     */
    function injectOptimizedStyles() {
        if (!CONFIG.ENABLE_CSS_CONTAINMENT) return;

        // Minified CSS with targeted selectors to avoid over-applying containment
        const css = `
            /* Optimize search results (specific container) */
            .s-main-slot .s-result-item {
                content-visibility: auto;
                contain-intrinsic-size: 1px 300px; /* Prevents scroll jump */
            }
            /* Optimize carousels and large sections (avoid over-broad .a-section) */
            .a-carousel-viewport, .s-main-slot .a-section {
                content-visibility: auto;
            }
            /* Defer footer rendering until scrolled */
            #navFooter, .nav-footer-line {
                content-visibility: auto;
                contain-intrinsic-size: 1px 500px;
            }
            /* Reduce animation CPU usage */
            .a-spinner-wrapper, .loading-spinner {
                will-change: transform, opacity; /* Hint for browser optimizations */
            }
            /* Hardware-accelerate product images */
            img.s-image {
                transform: translateZ(0); /* Triggers GPU acceleration */
            }
        `;

        // Fallback if GM_addStyle is unavailable (unlikely with @grant)
        if (typeof GM_addStyle === 'function') {
            GM_addStyle(css);
        } else {
            const style = document.createElement('style');
            style.textContent = css;
            document.head.appendChild(style);
        }
    }

    /**
     * 3. Background Throttler: Slow timers/animations when tab is hidden
     * Preserves original behavior when tab is active.
     */
    function initializeThrottler() {
        // Avoid throttling in iframes to prevent breaking embedded content
        if (window.self !== window.top) return;

        const originalSetInterval = window.setInterval;
        const originalRequestAnimationFrame = window.requestAnimationFrame;

        // Throttle short intervals in background
        window.setInterval = (callback, delay, ...args) => {
            if (document.hidden && delay < CONFIG.BACKGROUND_TICK_RATE_MS) {
                return originalSetInterval(callback, CONFIG.BACKGROUND_TICK_RATE_MS, ...args);
            }
            return originalSetInterval(callback, delay, ...args);
        };

        // Throttle animations in background (1 FPS)
        window.requestAnimationFrame = (callback) => {
            if (document.hidden && CONFIG.DISABLE_ANIMATION_IN_BACKGROUND) {
                return setTimeout(() => originalRequestAnimationFrame(callback), 1000);
            }
            return originalRequestAnimationFrame(callback);
        };

        // Restore original methods when tab becomes visible (reduces side effects)
        document.addEventListener('visibilitychange', () => {
            if (!document.hidden) {
                window.setInterval = originalSetInterval;
                window.requestAnimationFrame = originalRequestAnimationFrame;
            }
        }, { once: false });
    }

    /**
     * 4. Resource Optimizer: Prioritize critical images/iframes
     * @param {Node} rootNode - Root node to scan for resources (default: document)
     */
    function optimizeResources(rootNode = document) {
        if (!rootNode.querySelectorAll) return; // Guard against invalid nodes

        // Optimize images
        const images = rootNode.querySelectorAll('img:not([data-optimized])');
        images.forEach(img => {
            // Lazy load non-critical images
            if (!img.hasAttribute('loading')) {
                img.loading = 'lazy';
            }

            // Async decoding to avoid main-thread blocking
            if (!img.hasAttribute('decoding')) {
                img.decoding = 'async';
            }

            // Prioritize critical images (above-the-fold product images)
            if (SUPPORTS.fetchPriority && img.classList.contains('s-image') && SUPPORTS.closest) {
                const parentItem = img.closest('.s-result-item');
                if (parentItem) {
                    // Cache result items to avoid re-querying
                    const resultItems = Array.from(document.querySelectorAll('.s-result-item'));
                    const isCritical = resultItems.indexOf(parentItem) < CONFIG.CRITICAL_IMAGE_THRESHOLD;
                    img.fetchPriority = isCritical ? 'high' : 'auto';
                }
            }

            // Deprioritize footer images
            if (SUPPORTS.fetchPriority && SUPPORTS.closest) {
                if (img.closest('#navFooter') || img.closest('.nav-footer-line')) {
                    img.fetchPriority = 'low';
                }
            }

            img.dataset.optimized = 'true'; // Mark as processed
        });

        // Optimize iframes (ads, tracking) with lazy loading
        const iframes = rootNode.querySelectorAll('iframe:not([loading])');
        iframes.forEach(iframe => {
            iframe.loading = 'lazy';
        });
    }

    /**
     * 5. Initialization & Dynamic Content Observer
     * Debounces optimization calls to avoid excessive re-runs.
     */
    function main() {
        console.log('[Amazon Smoother] Initializing optimizations...');

        try {
            injectOptimizedStyles();
            initializeThrottler();
            optimizeResources();

            // Debounce timer for mutation observer
            let debounceTimer;

            // Observe dynamic content (AJAX-loaded results)
            const observer = new MutationObserver(() => {
                clearTimeout(debounceTimer);
                debounceTimer = setTimeout(() => {
                    if (SUPPORTS.requestIdleCallback) {
                        requestIdleCallback(() => optimizeResources(document.body), { timeout: 1000 });
                    } else {
                        optimizeResources(document.body);
                    }
                }, CONFIG.DEBOUNCE_DELAY_MS);
            });

            // Restrict observer to main content area to reduce overhead
            const targetNode = document.querySelector('.s-main-slot') || document.body;
            observer.observe(targetNode, {
                childList: true,
                subtree: true,
                attributes: false, // Only track new nodes, not attribute changes
                characterData: false
            });

        } catch (error) {
            console.error('[Amazon Smoother] Initialization failed:', error);
        }
    }

    // Initialize safely based on document state
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', main, { once: true });
    } else {
        main();
    }

})();