Disable animations

Speed up browsing by reducing animation and transition delays

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==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);
})();