Speed up browsing by reducing animation and transition delays
// ==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);
})();