greensock/GSAP · warning
GSDevTools error: invalid animation.
Error message
GSDevTools error: invalid animation.
What it means
GSDevTools validates the animation passed to it (or via animation()) is an actual GSAP Animation instance. The value may be a string ID, in which case _getAnimationById is used to resolve it; if after resolution it is still not an Animation instance, this warning is printed. The library throws it because the scrubber/controls cannot operate on a non-animation object.
Source
Thrown at src/GSDevTools.js:756
matches = (i && animations[i].vars.id === animations[i-1].vars.id) ? matches + 1 : 0;
option.setAttribute("value", (option.innerHTML = animations[i].vars.id + (matches ? " [" + matches + "]" : (animations[i+1] && animations[i+1].vars.id === animations[i].vars.id) ? " [0]" : "")));
}
for (; i < options.length; i++) {
list.removeChild(options[i]);
}
},
animation = function(anim) {
let ts = parseFloat(timeScale.options[timeScale.selectedIndex].value) || 1,
tl, maxDuration;
if (!arguments.length) {
return selectedAnimation;
}
if (_isString(anim)) {
anim = _getAnimationById(anim);
}
//console.log("animation() ", anim.vars.id);
if (!(anim instanceof Animation)) {
console.warn("GSDevTools error: invalid animation.");
}
if (anim.scrollTrigger) {
console.warn("GSDevTools can't work with ScrollTrigger-based animations; either the scrollbar -OR- the GSDevTools scrubber can control the animation.");
}
if (anim === selectedAnimation) {
return;
}
if (selectedAnimation) {
selectedAnimation._inProgress = inProgress;
selectedAnimation._outProgress = outProgress;
}
selectedAnimation = anim;
if (linkedAnimation) {
ts = linkedAnimation.timeScale();
if (linkedAnimation._targets && linkedAnimation._targets[0] === declaredAnimation) {
declaredAnimation.resume();
linkedAnimation.kill();
}View on GitHub (pinned to 13e2b79054)
Solutions
- Pass the actual gsap.timeline()/gsap.to() instance to `animation:` in GSDevTools.create()
- If using a string, set `gsap.timeline({id: "myId"})` and pass exactly that id string
- Create the animation before GSDevTools.create(), or pass a function/later reference supported by your version
- Ensure only one copy of gsap is loaded so instanceof checks pass
Example fix
// before
GSDevTools.create({ animation: "myTween " }); // typo, id not found -> undefined
// after
gsap.timeline({ id: "myTween" }).to(".box", {x: 100});
GSDevTools.create({ animation: "myTween" }); Defensive patterns
Strategy: type-guard
Validate before calling
const anim = typeof id === 'string' ? gsap.getById(id) : id;
if (!anim || typeof anim.play !== 'function' || !(anim instanceof gsap.core.Animation)) {
throw new Error('GSDevTools: animation must be a GSAP Animation instance or registered id');
} Type guard
function isGSAPAnimation(a) {
return a instanceof gsap.core.Animation || (a && typeof a.play === 'function' && typeof a.progress === 'function');
} Prevention
- Create animations before GSDevTools.create()
- Use gsap.getById() to verify string ids resolve
- Avoid loading duplicate gsap copies
- Avoid ScrollTrigger-based animations with GSDevTools scrubber
When it happens
Trigger: Passing GSDevTools.create() an `animation` value that is undefined, a timeline not yet created, a misspelled string id, or a non-GSAP object (e.g. a raw DOM element, a Promise, or an animation from a different/older GSAP core).
Common situations: Typos in the animation `id`; referencing an animation created after GSDevTools.create() runs; passing the result of a function that returns undefined; bundlers loading two copies of gsap so instanceof Animation fails.
Related errors
- Please gsap.registerPlugin(CSSPlugin, CSSRulePlugin)
- Please gsap.registerPlugin(CustomEase, CustomBounce)
- Please gsap.registerPlugin(CustomEase)
- Please gsap.registerPlugin(CustomEase, CustomWiggle)
- Please gsap.registerPlugin(Draggable)
AI-assisted analysis of greensock/GSAP@13e2b79054 (2026-08-29).
Data as JSON: /api/errors/ade701335131b368.
Report an issue: GitHub.