Disable animations

Speed up browsing by reducing animation and transition delays

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

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

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==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);
})();