← Animation accessor on animation and transition events
v151 · css · attribute reference
TransitionEvent.animation
A read-only, nullable attribute holding the CSSTransition that fired this transitionrun, transitionstart, transitionend, or transitioncancel event — null on events a script constructed without one. This page also covers the matching TransitionEventInit.animation dictionary member.
Syntax
// CSS Transitions Level 2 (Working Draft) — partial interface, normative IDL
partial interface TransitionEvent {
readonly attribute CSSTransition? animation;
};
partial dictionary TransitionEventInit {
CSSTransition? animation = null;
};
// Read it on any CSS transition event:
const anim = event.animation; // CSSTransition | null
The base TransitionEvent interface (propertyName, elapsedTime, pseudoElement, constructor) is defined in CSS Transitions Level 1; Level 2 adds animation via the partial interface above. The member is named animation (not transition) even though its type is CSSTransition — the naming choice was deliberate in csswg-drafts #9010, keeping both event interfaces symmetrical. Implementation divergence: Chromium's bindings type it as the Web Animations base interface — [RuntimeEnabled=AnimationEventAnimation] readonly attribute Animation? animation; in transition_event.idl and [RuntimeEnabled=AnimationEventAnimation] Animation? animation = null; in transition_event_init.idl. For UA-dispatched events the runtime value is still a CSSTransition (asserted by WPT); 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 transition event delegate constructs the event with the firing Animation*, in css_animations.cc). The dictionary member is the constructor input: new TransitionEvent("transitionend", { animation }). Per WebIDL interface-type conversion, the value must be an object implementing the interface (CSSTransition per spec, Animation in Chromium) or null; omitted or explicitly undefined, it takes the declared default null.
Outputs
A CSSTransition object, or null. The spec's attribute description: “The CSS Transition corresponding to the transition that fired the event.” For every UA-dispatched transition event — transitionrun, transitionstart, transitionend, transitioncancel — the value is the firing transition, never null (Chromium's dispatch path always passes the animation; the level-2 dispatch algorithm fires transition events only for transitions with an owning element). For script-constructed events the value is whatever the init dictionary carried, defaulting to null. Runtime type guarantee: WPT events-008.html asserts event.animation instanceof CSSTransition on transitionrun/transitionstart/transitionend and that its transitionProperty matches the transitioned property — so the value is usable as a full CSSTransition, not just a base Animation.
Errors
Reading the attribute never throws: it always has a value (CSSTransition or null) for the event's lifetime. The traps are semantic, not exceptional: null does not mean “no transition 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 TransitionEvent.prototype) before branching, treating absence as “unknown”. Constructor-side failure modes are documented under the dictionary member's errors.
Context
Receiver: any TransitionEvent — UA-dispatched to the transition's owning element, or script-constructed.
Exposure: [Exposed=Window] — the TransitionEvent interface (and therefore this attribute) exists in window contexts only, not workers; CSS transitions 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 transition events are dispatched at all; the animation playback events from Web Animations still fire at the CSSTransition 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 transition's life the events all carry the same object, following the level-2 phase-change dispatch table: transitionrun first (when the transition is created, possibly still in its delay phase), then — except for paused or infinite-running transitions — the invariants transitionrun → exactly one of transitionend/transitioncancel, and every transitionend preceded by a transitionstart. The typical sequences: regular playback transitionrun, transitionstart, transitionend; interrupted transitionrun, transitionstart, transitioncancel; interrupted during delay transitionrun, transitioncancel. Seeking or reversing via the Web Animations API can produce further start/end pairs — the table accounts for every phase transition. Cancelling (event.animation.cancel(), or a style change that kills the transition) produces a transitioncancel whose animation is that same object, now with playState === "idle"; after a cancel the transition leaves getAnimations() results, yet the event keeps referencing it (Blink holds the animation as a traced member). Same-object semantics: neither spec annotates the attribute [SameObject] or [NewObject]; Blink stores the pointer and returns the identical object on every access (animation_.Get() in transition_event.cc).
Examples
// Reverse the exact transition that fired the event.
element.addEventListener("transitionstart", (event) => {
if (event.animation === null) return; // script-constructed event
console.log("transitioning", event.animation.transitionProperty);
event.animation.finished.then(() => element.classList.add("settled"));
});
// Speed up every transition as it starts running.
element.addEventListener("transitionrun", (event) => {
event.animation.playbackRate = 2;
});
// transitioncancel carries the same (now idle) CSSTransition.
element.addEventListener("transitioncancel", (event) => {
console.log(event.animation.transitionProperty, "cancelled;",
"playState:", event.animation.playState); // "idle"
});
// Constructing one explicitly (defaults to null if omitted):
const transition = element.getAnimations().find(a => "transitionProperty" in a);
const synthetic = new TransitionEvent("transitionend", {
propertyName: "opacity",
elapsedTime: 0.3,
animation: transition,
});
console.log(synthetic.animation === transition); // true
// Feature-detect before relying on the attribute:
const getFiring = (event) => ("animation" in event)
? event.animation
: event.target.getAnimations().find(
a => a.transitionProperty === event.propertyName);
Source: csswg-drafts issue #9010; CSS Transitions Level 2 — TransitionEvent; WPT — events-008.html
TransitionEventInit.animation
The dictionary member mirrors the attribute for constructor use: CSSTransition? animation = null in TransitionEventInit (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
CSSTransition?, so aCSSAnimationor a plainnew 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 TransitionEvent(...)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.
TransitionEventInit.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, mirroring animation_(initializer->animation()) in animation_event.cc), or null when omitted. No wrapping, cloning, or re-resolution happens.
TransitionEventInit.animation — errors
Two throwing paths. First, type conversion: a provided value that is not null/undefined and not an Animation object (Chromium) / CSSTransition 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.
TransitionEventInit.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-transitions-2 definition with its unusually cased anchor); the sibling AnimationEvent.animation entry in api/AnimationEvent.json carries identical rows, and the animation-side WPT evidence is animationevent-types.html. WPT: css/css-transitions/events-008.html asserts transitionrun/transitionstart/transitionend expose the attribute, that its value instanceof CSSTransition, and that transitionProperty is correct; the interface harness expectations (transitionevent-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 CSSTransition objects are enumerable through element.getAnimations() and document.getAnimations(), so this is an ergonomics change, not a new disclosure surface. CSS Transitions Level 2 reports no new privacy considerations and no new security considerations; 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.