Skip to content
SiteEmail

This article extends the WAAPI introduction in UI Animations. It documents the rest of the supported methods & properties in Gameface, the parts of the standard API that are not exposed , and the practical patterns that combine these methods into common game UI behaviors.

WAAPI in Gameface is a deliberate subset focused on controlling CSS-defined animations from JavaScript . The imperative constructors that let standard browsers create animations directly in JavaScript - element.animate(), new Animation(), new KeyframeEffect() - are not available. Knowing the boundary up front saves a lot of debugging time when porting browser code.

NameTypePurpose
element.getAnimations()MethodRetrieve all Animation objects active on an element. The only entry point.
play(), pause()MethodStart, resume, or pause playback.
reverse()MethodFlip the playback direction.
finish()MethodJump immediately to the end state.
cancel()MethodStop playback and remove the animation effect.
commitStyles()MethodWrite the current animated values into the element’s inline style.
persist()MethodPrevent automatic removal when a filling animation is replaced. See note.
playFromTo(start, end)Method Gameface extension. Play a specific timeline segment, then auto-pause.
currentTimePropertyGet or set the current timeline position in milliseconds.
playbackRatePropertyGet or set the playback speed and direction.

Anything not in the table above is not exposed. In practice, the standard-API gaps that come up most often when porting browser code have a direct alternative in Gameface:

Every call to getAnimations() walks the element’s resolved animation list and allocates a fresh array. In a HUD that reacts to gameplay events many times per second, calling it inside an event handler or a game-loop callback is unnecessary work.

Avoid looking the animation up every time you need to control it:

anti-pattern.js
function showWarning() {
const warningPulse = warningIcon.getAnimations()[0];
warningPulse.currentTime = 0;
warningPulse.play();
}

Retrieve it once during setup and keep the reference around. All later playback calls go through the stored variable:

stored-reference.js
const warningPulse = warningIcon.getAnimations()[0];
function showWarning() {
warningPulse.currentTime = 0;
warningPulse.play();
}
function hideWarning() {
warningPulse.reverse();
}

CSS-declared animations stay attached to the element for as long as the CSS rule applies, so a stored reference remains valid across any number of play/pause/reverse cycles. There is no need to call persist() here - that method solves a different problem specific to imperative element.animate() calls. See persist() for details.

play() and pause() cover the basic start/stop case (see the UI Animations article). The three methods below extend that with reversing, jumping to the end, and discarding an animation entirely.

reverse() flips the sign of the playback direction and continues playing from the current timeline position. It is functionally equivalent to assigning playbackRate = -1 and then calling play().

The method is the cleanest way to implement open/close panels with a single keyframe definition. Define the “open” animation in CSS, play it forward to open the panel, and call reverse() to close it from wherever the open animation got to.

quest-panel.css
@keyframes quest-panel-open {
from {
opacity: 0;
transform: translateX(-4vh);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.quest-panel {
animation: quest-panel-open 280ms ease-out both paused;
}
quest-panel.js
const panel = document.getElementById("quest-panel");
const openAnim = panel.getAnimations()[0];
function openPanel() {
openAnim.playbackRate = 1;
openAnim.play();
}
function closePanel() {
openAnim.reverse();
}

finish() jumps the animation to its end state and stops playback. Use it for skip-to-end behaviors: a “skip cinematic” button, a HUD that should snap to its final state when the player presses any key, or a delayed-reveal animation that should complete instantly on a state change.

skip-intro.js
const introCard = document.getElementById("intro-card");
const introAnim = introCard.getAnimations()[0];
document.getElementById("skip-btn").addEventListener("click", () => {
introAnim.finish();
});

After finish(), the animation is in its end state but is still attached to the element. The next play() call restarts it from the beginning.

pause() and cancel() look similar at first glance - both stop the animation - but they have very different effects on the element’s visual state.

  • pause() freezes the animation at the current timeline position. The animated values keep being applied, so the element holds its current visual state.
  • cancel() aborts the animation entirely and removes its effect from the element. The element snaps back to its underlying CSS values from the regular stylesheet.
cancel-vs-pause.js
const panel = document.getElementById("status-panel");
const fadeAnim = panel.getAnimations()[0];
function holdCurrentState() {
fadeAnim.pause();
}
function discardAnimation() {
fadeAnim.cancel();
}

By default, an animation’s effect on an element only lasts as long as the animation is alive. Once it is cancelled or its effect is otherwise removed, the element returns to its underlying CSS values. The two methods below let you decide how that visual state is handled when the timeline ends.

commitStyles() writes the animation’s currently computed values into the element’s inline style attribute. Pair it with cancel() when you want to freeze the element at an arbitrary point in the timeline and discard the animation entirely.

For end-state persistence after the animation finishes, you usually do not need commitStyles() at all - the CSS animation-fill-mode: forwards declaration already keeps the final keyframe applied. commitStyles() is the right tool when you need an intermediate state to persist, for example a charging bar that should freeze at whatever fill level it had reached when the player released the trigger.

freeze-mid-animation.js
const fill = document.getElementById("charge-fill");
const chargeAnim = fill.getAnimations()[0];
function startCharging() {
chargeAnim.currentTime = 0;
chargeAnim.play();
}
function releaseTrigger() {
chargeAnim.pause();
chargeAnim.commitStyles();
chargeAnim.cancel();
}

After releaseTrigger() runs, the bar’s current fill width and color are written to the element’s inline style and the animation is gone from getAnimations(). The element will hold that visual indefinitely without an animation actively driving it.

In Gameface, persist() is effectively a no-op exposed only for API completeness.

In standard browsers, this method prevents the automatic removal of overlapping animations created imperatively via element.animate(). Because Gameface relies exclusively on CSS-declared animations and does not support element.animate(), the auto-removal scenario that persist() guards against cannot occur.

You do not need to use persist() in your setup code. If you want to learn more about how this memory management feature works in standard web browsers, check out the MDN documentation.

The playbackRate property gets or sets the speed at which the animation timeline advances. Common values:

  • 1 - normal speed (default).
  • 2 - double speed.
  • 0.5 - half speed.
  • -1 - full speed in reverse.

Negative values play the animation backward from the current timeline position. playbackRate = -1 followed by play() is functionally the same as calling reverse(), but assigning playbackRate directly also lets you control the speed of the reverse playback (for example, -2 for a fast rewind or -0.5 for a slow one).

speed-control.js
const fill = document.getElementById("charge-bar-fill");
const chargeAnim = fill.getAnimations()[0];
function normalCharge() {
chargeAnim.playbackRate = 1;
chargeAnim.play();
}
function slowMotionCharge() {
chargeAnim.playbackRate = 0.25;
chargeAnim.play();
}
function rapidCharge() {
chargeAnim.playbackRate = 4;
chargeAnim.play();
}

This is a clean way to implement bullet-time effects, “fast-forward” through a long animation when the player is impatient, or expose a debug speed toggle without authoring multiple keyframe variants.

The standard way to react when an animation finishes is animation.finished.then(...) (a Promise) or animation.addEventListener('finish', ...). Neither is available in Gameface.

The supported alternative is the CSS animation event , dispatched on the element itself rather than on the Animation object:

completion-listener.js
const card = document.getElementById("reward-card");
const cardAnim = card.getAnimations()[0];
card.addEventListener("animationend", (event) => {
if (event.animationName === "reward-intro") {
unlockNextStep();
}
});
cardAnim.play();

The event.animationName matches the CSS @keyframes name, which is essential when an element has multiple animations and you need to react to a specific one. Two related events are also supported:

  • animationstart - fires when the animation begins playing (after any animation-delay).
  • animationiteration - fires once per loop for animations with animation-iteration-count > 1.

A few combinations of the methods above cover most real game UI scenarios:

A reveal animation that the game can replay any number of times, for example a “new item received” badge that re-appears every time a pickup event fires.

replay-reveal.js
const badge = document.getElementById("pickup-badge");
const revealAnim = badge.getAnimations()[0];
function showPickup() {
revealAnim.currentTime = 0;
revealAnim.play();
}

A panel that opens and closes from a single keyframe definition, regardless of how quickly the player toggles the button.

toggle-panel.js
const panel = document.getElementById("inventory-panel");
const toggleAnim = panel.getAnimations()[0];
let isOpen = false;
function togglePanel() {
if (isOpen) {
toggleAnim.reverse();
} else {
toggleAnim.playbackRate = 1;
toggleAnim.play();
}
isOpen = !isOpen;
}

An ambient animation that runs at normal speed during gameplay and slows down when an ability triggers a bullet-time effect.

bullet-time.js
const overlay = document.getElementById("ambient-glow");
const glowAnim = overlay.getAnimations()[0];
function enterBulletTime() {
glowAnim.playbackRate = 0.2;
}
function exitBulletTime() {
glowAnim.playbackRate = 1;
}