Disable animations

Speed up browsing by reducing animation and transition delays

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(Tôi đã có Trình quản lý tập lệnh người dùng, hãy cài đặt nó!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name        Disable animations
// @description Speed up browsing by reducing animation and transition delays
// @version     2026.04.12
// @author      Claude Opus 4.6
// @grant       none
// @inject-into auto
// @run-at      document-start
// @match       *://*/*
// @namespace https://greasyfork.org/users/1519047
// ==/UserScript==

(function() {
    'use strict';

    var TRANSITION_CAP_S = 0.01;
    var ANIMATION_CAP_S = 0.001;

    // --- Patch Web Animations API (Element.animate) ---
    var origAnimate = Element.prototype.animate;
    if (origAnimate) {
        Element.prototype.animate = function(keyframes, options) {
            if (typeof options === 'number') {
                options = 1;
            } else if (options && typeof options === 'object') {
                if (options.duration > 1) options.duration = 1;
                if (options.delay) options.delay = 0;
                if (options.iterations === Infinity) options.iterations = 1;
            }
            return origAnimate.call(this, keyframes, options);
        };
    }

    // --- Duration clamping utility ---
    var clampDurations = function(raw, cap) {
        var parts = raw.split(',');
        var changed = false;
        var clamped = parts.map(function(part) {
            var val = parseFloat(part);
            if (val <= 0 || isNaN(val)) return part.trim();
            changed = true;
            return Math.min(val, cap) + 's';
        });
        return changed ? clamped.join(', ') : null;
    };

    // --- Check if any value in a comma-separated list is "infinite" or a large number ---
    // Threshold of 2 catches "infinite" and high iteration counts while allowing
    // deliberate 1- or 2-iteration animations (e.g. attention pulses) to complete normally.
    var hasInfiniteIteration = function(raw) {
        var parts = raw.split(',');
        for (var i = 0; i < parts.length; i++) {
            var val = parts[i].trim();
            if (val === 'infinite') return true;
            var num = parseFloat(val);
            if (!isNaN(num) && num > 2) return true;
        }
        return false;
    };

    // --- RAF batching for processElement to avoid layout thrashing ---
    var pendingSet = new Set();
    var pendingList = [];
    var rafScheduled = false;
    var processing = false;

    var scheduleProcess = function(el) {
        if (pendingSet.has(el)) return;
        pendingSet.add(el);
        pendingList.push(el);
        if (!rafScheduled) {
            rafScheduled = true;
            requestAnimationFrame(function() {
                processing = true;
                for (var i = 0; i < pendingList.length; i++) {
                    processElement(pendingList[i]);
                }
                processing = false;
                pendingSet.clear();
                pendingList.length = 0;
                rafScheduled = false;
            });
        }
    };

    var scheduleTree = function(root) {
        if (root.nodeType !== 1) return;
        scheduleProcess(root);
        if (root.querySelectorAll) {
            var nodes = root.querySelectorAll('*');
            for (var i = 0; i < nodes.length; i++) {
                scheduleProcess(nodes[i]);
            }
        }
    };

    // --- Process a single element ---
    var processElement = function(el) {
        if (el.nodeType !== 1) return;

        var computed = getComputedStyle(el);

        // Clamp transition durations
        var tDur = computed.transitionDuration;
        if (tDur && tDur !== '0s') {
            var tClamped = clampDurations(tDur, TRANSITION_CAP_S);
            if (tClamped) {
                el.style.setProperty('transition-duration', tClamped, 'important');
                el.style.setProperty('transition-delay', '0s', 'important');
            }
        }

        // Clamp animation durations and stop infinite loops
        var aDur = computed.animationDuration;
        if (aDur && aDur !== '0s') {
            var aClamped = clampDurations(aDur, ANIMATION_CAP_S);
            if (aClamped) {
                el.style.setProperty('animation-duration', aClamped, 'important');
                el.style.setProperty('animation-delay', '0s', 'important');

                // Only force iteration-count and fill-mode on infinite/looping animations
                var iterCount = computed.animationIterationCount;
                if (iterCount && hasInfiniteIteration(iterCount)) {
                    el.style.setProperty('animation-iteration-count', '1', 'important');
                    el.style.setProperty('animation-fill-mode', 'both', 'important');
                }
            }
        }
    };

    // --- Initial synchronous pass + observer ---
    var processTree = function(root) {
        processElement(root);
        if (root.querySelectorAll) {
            var nodes = root.querySelectorAll('*');
            for (var i = 0; i < nodes.length; i++) {
                processElement(nodes[i]);
            }
        }
    };

    var startObserver = function() {
        // Synchronous first pass so existing elements are clamped immediately
        processTree(document.documentElement);

        new MutationObserver(function(mutations) {
            for (var i = 0; i < mutations.length; i++) {
                var mutation = mutations[i];
                if (mutation.type === 'childList') {
                    var added = mutation.addedNodes;
                    for (var j = 0; j < added.length; j++) {
                        if (added[j].nodeType === 1) scheduleTree(added[j]);
                    }
                } else if (mutation.type === 'attributes' && !processing) {
                    // Re-process elements whose class or style changed,
                    // since new animations/transitions may have been applied.
                    // Guard with `processing` to avoid infinite loops from
                    // our own style.setProperty calls triggering style mutations.
                    scheduleProcess(mutation.target);
                }
            }
        }).observe(document.documentElement, {
            childList: true,
            subtree: true,
            attributes: true,
            attributeFilter: ['class', 'style']
        });
    };

    // --- jQuery patching ---
    var patchjQuery = function(jq) {
        if (!jq || !jq.fx || jq.fx._animDisablerPatched) return;
        jq.fx._animDisablerPatched = true;

        jq.fx.speeds = { slow: 1, fast: 1, _default: 1 };

        if (jq.fn && jq.fn.animate && !jq.fn.animate._patched) {
            var origAnim = jq.fn.animate;
            jq.fn.animate = function(prop, speed) {
                var args = Array.prototype.slice.call(arguments);
                if (typeof speed === 'object' && speed !== null) {
                    args[1].duration = 1;
                } else {
                    args[1] = 1;
                }
                return origAnim.apply(this, args);
            };
            jq.fn.animate._patched = true;
        }

        ['fadeTo', 'fadeIn', 'fadeOut', 'fadeToggle',
         'slideUp', 'slideDown', 'slideToggle'].forEach(function(method) {
            if (jq.fn[method] && !jq.fn[method]._patched) {
                var orig = jq.fn[method];
                jq.fn[method] = function() {
                    var args = Array.prototype.slice.call(arguments);
                    args[0] = 1;
                    return orig.apply(this, args);
                };
                jq.fn[method]._patched = true;
            }
        });
    };

    // --- Patch GSAP if present ---
    var patchGSAP = function() {
        try {
            if (window.gsap) {
                window.gsap.globalTimeline.timeScale(1000);
            } else if (window.TweenMax) {
                window.TweenMax.globalTimeScale(1000);
            }
            if (window.wrappedJSObject) {
                if (window.wrappedJSObject.gsap) {
                    window.wrappedJSObject.gsap.globalTimeline.timeScale(1000);
                } else if (window.wrappedJSObject.TweenMax) {
                    window.wrappedJSObject.TweenMax.globalTimeScale(1000);
                }
            }
        } catch (e) {
            console.debug('Animation disabler (GSAP):', e);
        }
    };

    var disableJSAnimations = function() {
        try {
            [window.jQuery, window.$,
             window.wrappedJSObject?.jQuery,
             window.wrappedJSObject?.$].forEach(patchjQuery);

            if (window.Velocity) window.Velocity.mock = true;
            if (window.wrappedJSObject?.Velocity) window.wrappedJSObject.Velocity.mock = true;

            patchGSAP();

            if (!window.jQuery && !window.$) {
                var check = setInterval(function() {
                    if (window.jQuery || window.$) {
                        patchjQuery(window.jQuery);
                        patchjQuery(window.$);
                        clearInterval(check);
                    }
                }, 100);
                setTimeout(function() { clearInterval(check); }, 10000);
            }
        } catch (e) {
            console.debug('Animation disabler:', e);
        }
    };

    // --- Initialization ---
    var init = function() {
        startObserver();
        disableJSAnimations();
    };

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }

    // Re-check JS animation libraries after full page load,
    // as some are loaded asynchronously via script tags.
    window.addEventListener('load', disableJSAnimations);
})();