Animations API
Entrance animations
play when an element enters the viewport. That's the right behaviour almost always — but not
when an element only becomes visible because something else revealed it. A slide that
scrolls into view, an accordion panel that opens, a popup that appears: the elements inside
were already "in the viewport" as far as the browser was concerned, so their animations
already fired, invisibly.
The Animations API is a set of DOM events for replaying and resetting animations, so
JavaScript can say "these elements are visible now".
The events
All three are ordinary DOM events dispatched at an element. They bubble, so the handler
picks up the element you dispatched at plus every animated element inside it.
| Event | What it does |
|---|---|
breakdance_play_animations |
Replays entrance animations |
breakdance_reset_animations |
Returns them to their initial hidden state |
breakdance_refresh_animations |
Cleans up animation state |
The names keep the breakdance_ prefix on Oxygen — these are event names in shared
JavaScript, not PHP hooks, and there is no oxygen_ variant.
Replaying
Dispatch breakdance_play_animations at the container whose contents should animate:
const panel = document.querySelector('.my-panel');
const event = new Event('breakdance_play_animations', { bubbles: true });
panel.dispatchEvent(event);
The handler collects the target and every descendant carrying data-entrance, and replays
each one. Dispatch at document to replay everything on the page:
document.dispatchEvent(new Event('breakdance_play_animations', { bubbles: true }));
You can pass a delay with a CustomEvent:
const event = new CustomEvent('breakdance_play_animations', {
bubbles: true,
detail: { delay: 0.2 },
});
panel.dispatchEvent(event);
Resetting
breakdance_reset_animations puts elements back into the hidden state they start in, without
playing anything:
const panel = document.querySelector('.my-panel');
panel.dispatchEvent(new Event('breakdance_reset_animations', { bubbles: true }));
The reason to reach for this rather than replaying is when the element is about to become
visible but isn't yet. Reset it while it's hidden and the normal viewport trigger takes over
when it appears — you get a genuine entrance rather than a replay.
The usual pairing is: reset when hiding, and let the scroll trigger handle showing.
A worked example
Content injected after page load has never had its entrance animations initialised in a
useful way. Reset first, then play:
async function loadMoreCards(container) {
const html = await fetch('/wp-json/myplugin/v1/cards').then(r => r.text());
container.insertAdjacentHTML('beforeend', html);
container.dispatchEvent(new Event('breakdance_reset_animations', { bubbles: true }));
container.dispatchEvent(new Event('breakdance_play_animations', { bubbles: true }));
}
Troubleshooting
The elements have no entrance animation
The handler only touches elements carrying a data-entrance attribute, which comes from
having an entrance animation configured in the builder. An element with none is skipped
silently.
The event didn't bubble
{ bubbles: true } is not optional here — the listener is attached globally, and an event
that doesn't bubble never reaches it.
You dispatched at the wrong node
The handler works on the target and its descendants. Dispatching at a sibling of the
animated content does nothing; dispatch at a common ancestor.
Nothing animates in the builder canvas
Entrance animations don't run in the builder canvas at all. Testing this on the front end is
the only meaningful test.