← Animation accessor on animation and transition events
v151 · css · attribute reference
AnimationEvent.animation
A read-only, nullable attribute holding the CSSAnimation that fired this animationstart, animationiteration, animationend, or animationcancel event — null on events a script constructed without one. This page also covers the matching AnimationEventInit.animation dictionary member.
Syntax
// CSS Animations Level 2 (Working Draft) — partial interface, normative IDL
partial interface AnimationEvent {
readonly attribute CSSAnimation? animation;
};
partial dictionary AnimationEventInit {
CSSAnimation? animation = null;
};
// Read it on any CSS animation event:
const anim = event.animation; // CSSAnimation | null
The base AnimationEvent interface (animationName, elapsedTime, pseudoElement, constructor) is defined in CSS Animations Level 1; Level 2 adds animation via the partial interface above. Implementation divergence: Chromium's bindings type the attribute (and the dictionary member) as the Web Animations base interface, not the CSS subclass — [RuntimeEnabled=AnimationEventAnimation] readonly attribute Animation? animation; in animation_event.idl and [RuntimeEnabled=AnimationEventAnimation] Animation? animation = null; in animation_event_init.idl. For UA-dispatched events the runtime value is still a CSSAnimation; the wider static type only matters for the constructor (see the dictionary member).
Inputs
The attribute is read-only — it takes no inputs. The UA sets it when it dispatches the event (in Chromium, the event is constructed with the firing Animation* passed straight through, in css_animations.cc). The dictionary member is the constructor input: new AnimationEvent("animationstart", { animation }). Per WebIDL interface-type conversion, the value must be an object implementing the interface (CSSAnimation per spec, Animation in Chromium) or null; omitted or explicitly undefined, it takes the declared default null.
Outputs
A CSSAnimation object, or null. The spec's attribute description is exact: “The CSS Animation that fired the event.” For every UA-dispatched CSS animation event — animationstart, animationiteration, animationend, animationcancel — the value is the firing animation, never null (Chromium's dispatch path always passes the animation; the level-2 dispatch algorithm fires these events only for animations with an owning element). For script-constructed events the value is whatever the init dictionary carried, defaulting to null. Identity: the object is the same instance you would find in element.getAnimations() — WPT animationevent-types.html asserts event.animation strictly equals the matching getAnimations() entry — so event.animation === previouslySeenAnimation comparisons work.
Errors
Reading the attribute never throws: it always has a value (CSSAnimation or null) for the event's lifetime. The traps are semantic, not exceptional: null does not mean “no animation was involved” — it marks a script-constructed event (check event.isTrusted when the distinction matters); and on engines without the attribute the property is simply absent — feature-detect with "animation" in event (or AnimationEvent.prototype) before branching, treating absence as “unknown”. Constructor-side failure modes are documented under the dictionary member's errors.
Context
Receiver: any AnimationEvent — UA-dispatched to the animation's owning element (the events bubble; the dispatch algorithm targets the owning element), or script-constructed.
Exposure: [Exposed=Window] — the AnimationEvent interface (and therefore this attribute) exists in window contexts only, not workers; CSS animations are a document concept. No secure-context, permission, or user-gesture requirement beyond the interface itself.
Availability: Chrome 151 per the milestone listing (Enabled by default); runtime feature AnimationEventAnimation, status stable at trunk (no flag). No owning element → no CSS animation events are dispatched at all; the animation playback events from Web Animations still fire at the CSSAnimation object itself — those are AnimationPlaybackEvents, a different interface without an animation attribute.
Lifecycle
The reference is fixed when the event is created and never changes for the event's lifetime. Across one animation's life the events all carry the same object: animationstart → (animationiteration per iteration) → exactly one of animationend or animationcancel, per the level-2 phase-change dispatch table (seeking or reversing via the Web Animations API can legitimately produce further start/end pairs — the table accounts for every phase transition). Two state subtleties worth knowing: cancelling from inside a handler (e.g. event.animation.cancel() on animationstart) produces an animationcancel whose animation is that same object, now with playState === "idle"; and after a cancel the animation leaves getAnimations() results, yet the event keeps referencing it (Blink holds the animation as a traced member — animation_ in animation_event.cc). Same-object semantics: neither spec annotates the attribute [SameObject] or [NewObject]; Blink stores the pointer and returns the identical object on every access, and WPT's identity assertion against getAnimations() pins that observable behavior.
Examples
// Pause and re-time the exact animation that fired the event.
element.addEventListener("animationstart", (event) => {
if (event.animation === null) return; // script-constructed event
event.animation.playbackRate = 0.5;
event.animation.finished.then(() => element.remove());
});
// Cancel it — the matching animationcancel carries the same object.
element.addEventListener("animationstart", (event) => {
event.animation.cancel();
});
element.addEventListener("animationcancel", (event) => {
console.log(event.animation.playState); // "idle"
});
// Constructing one explicitly (defaults to null if omitted):
const anim = element.getAnimations()[0];
const synthetic = new AnimationEvent("animationstart", {
animationName: anim.animationName,
elapsedTime: 0,
animation: anim,
});
console.log(synthetic.animation === anim); // true
// Feature-detect; the getAnimations() fallback is ambiguous when the same
// keyframes run twice on one element (csswg-drafts #9010).
const getFiring = (event) => ("animation" in event)
? event.animation
: event.target.getAnimations().find(a => a.animationName === event.animationName);
Source: csswg-drafts issue #9010; ChromeStatus API feature record (activation_risks polyfill); CSS Animations Level 2 — AnimationEvent
AnimationEventInit.animation
The dictionary member mirrors the attribute for constructor use: CSSAnimation? animation = null in AnimationEventInit (Chromium: Animation? animation = null). The exact WebIDL semantics, because constructor behavior is where the traps live:
- Interface-type conversion: a provided value must be an object implementing the interface — per WebIDL interface conversion, anything else (a plain object, a DOMString, a number) throws a
TypeError.nullis accepted (the type is nullable). - Spec vs Chromium typing: the spec member is
CSSAnimation?, so a plain Web-Animationsnew Animation()is out of contract per spec; Chromium's member isAnimation?, so Chromium accepts anyAnimationsubclass. Code relying on the wider acceptance is Chromium-specific. - Default and
undefined: omitted entirely — or passed explicitly asundefined— the member takes its declared defaultnull(WebIDL applies the default whenever the value isundefined); existingnew AnimationEvent(...)call sites keep their meaning. - Unknown members: per WebIDL dictionary semantics, members the dictionary doesn't declare are ignored — a typo'd key silently does nothing.
AnimationEventInit.animation — outputs
The constructed event's animation attribute reads back the very object passed in (Blink copies the initializer's pointer into the event member — animation_(initializer->animation()) in animation_event.cc), or null when omitted. No wrapping, cloning, or re-resolution happens.
AnimationEventInit.animation — errors
Two throwing paths. First, type conversion: a provided value that is not null/undefined and not an Animation object (Chromium) / CSSAnimation object (spec contract) throws a TypeError out of the constructor. Second, retrieval: reading the member off the input object is a property access — a throwing getter or a Proxy with a throwing get trap propagates that exception before any conversion runs. With ordinary values the remaining failure modes are silent semantic ones: a misspelled member name is ignored, and a valid value merely echoes back — none of it proves anything about a UA-dispatched event.
AnimationEventInit.animation — security and privacy
An authored event can carry any Animation object — including one attached to a different element or document subtree — so event.animation on an untrusted event is not evidence of what fired anything; the authored-vs-native distinction is event.isTrusted (false for every script-constructed event). Handlers that blindly drive event.animation (pause/cancel/finish) should be aware a same-origin script can feed them arbitrary animations through dispatchEvent — the same caveat that already applies to all scripted events. The member adds no storage, permission, or cross-origin channel.
Compatibility
| Engine / runtime | Support | Notes |
|---|---|---|
| Chrome | 151 | Matches the milestone listing (Enabled by default); Chrome for Android, WebView (Android + iOS), Opera, Samsung Internet are BCD mirrors |
| Edge | mirror | BCD mirrors Chrome's data (151) |
| Firefox | 152 | BCD version_added: "152" (Firefox for Android mirrors); ChromeStatus records “Shipped/Shipping” |
| Safari | 27 | BCD version_added: "27" (Safari on iOS mirrors); ChromeStatus records “Shipped/Shipping” |
| Deno / Node.js | Unknown | No BCD entries as of 2026-07-26 (window-exposed DOM event API) |
The BCD entry links its canonical references directly (mdn_url to this attribute's MDN page, spec_url to the css-animations-2 definition); the sibling TransitionEvent.animation entry in api/TransitionEvent.json carries identical rows, and the transition-side WPT evidence is events-008.html. WPT: css/css-animations/animationevent-types.html asserts the attribute exists on animationstart/animationend/animationiteration and strictly equals the corresponding getAnimations() entry; the interface harness expectations (animationevent-interface) were updated in the implementing CL 7914303.Not a separately tracked web-feature on webstatus.dev (animationevent query returns zero entries, checked 2026-07-26).
Security and privacy
The attribute exposes no information that was not already reachable: the same CSSAnimation objects are enumerable through element.getAnimations() and document.getAnimations(), so this is an ergonomics change, not a new disclosure surface. CSS Animations Level 2 reports no privacy concerns and no security concerns; the ChromeStatus entry's security and privacy reviews are both “Not applicable”. The one behavioral caveat is authored events: a script-constructed event can carry an arbitrary Animation (see the dictionary member), so treat event.animation on untrusted events as data, not provenance — event.isTrusted separates the two.