Skip to content
SiteEmail

Motion brings a UI to life, but using the wrong asset type can put significant pressure on your game’s memory and performance budget. This article covers the constraints of GIF animations, how to implement WebM video playback correctly, and why you should prioritize alternatives like Sprite Sheets and Live Views for most use cases.

While GIFs are common on the traditional web, the format is generally not recommended for game UI. It carries several technical limitations that make it a poor fit for production use in Gameface.

  • Compression and file size. GIF animations rely on an older compression algorithm. For the same animation, a GIF file tends to be 5 to 10 times larger than an equivalent efficiently encoded video. This translates directly into larger asset bundles and longer load times.

  • Visual quality. The GIF standard supports only 256 colors per frame and provides no support for semi-transparency. Every pixel is either fully opaque or fully transparent. Artwork with gradients or soft alpha edges will show visible color banding and rough outlines compared to formats like PNG.

  • Memory consumption. Gameface optimizes GIF playback by preloading all frames to avoid decoding them at runtime. While this prevents per-frame CPU spikes during animation, it means the entire animation is unpacked into memory up front. High-resolution or high-framerate GIFs will have a notable impact on your game’s memory consumption.

If your design requires GIF animations, they remain practical for one specific use case: small animated emojis . GIFs are currently supported as a source for <img> elements.

To keep memory consumption manageable, follow these limits:

  • Keep the resolution at or below 128x128 pixels .
  • Keep the frame count at or below 30 frames per animation.

The only supported way to render GIF animations in Gameface is through standard HTML <img> tags. The following example places a small animated emoji inline within a chat message:

src/views/chat/index.html
<div class="chat-message">
<span>Great shot!</span>
<img src="thumbs_up_emoji.gif" width="32" height="32" alt="Thumbs Up" />
</div>

GIFs are not supported as values for background-image, border-image, or mask-image. Further support and enhancements for these CSS properties are not planned.

The following patterns will not produce an animated result in Gameface:

src/views/chat/index.css
/* Unsupported: GIF as a CSS background will not animate */
.chat-reaction-button {
width: 64px;
height: 64px;
background-image: url("spinning_star.gif");
}
/* Unsupported: GIF as a CSS mask will not animate */
.avatar-mask {
mask-image: url("animated_mask.gif");
}

While GIFs work for tiny emoji-sized animations, the preferred and more effective solution for anything larger is to use the <video> element. Gameface provides full support for the standard HTML <video> element, including playback of both opaque and transparent videos.

Gameface requires videos that meet these specifications:

  • The container format must be WebM .
  • The video track must be encoded with VP8 or VP9 .
  • If audio is needed, it must be a single audio track encoded in Vorbis .

The supported standard attributes are src, autoplay, loop, muted, and preload="auto".

Depending on your UI needs, configure the <video> element to achieve different behaviors. Below are the most common patterns for game UI.

For a dynamic main menu background, combine autoplay, loop, and muted to start the video immediately without interfering with the engine’s menu music:

src/views/main-menu/index.html
<video width="1920" height="1080" src="main_menu_bg.webm" autoplay loop muted></video>

Synchronized UI Sequences (Loot Box, Reveal Animations)

Section titled “Synchronized UI Sequences (Loot Box, Reveal Animations)”

For one-off UI animations like a loot box opening, you want the video ready to play immediately when the user triggers the action. Use preload="auto" to preload all keyframe data in advance so there is no buffering delay on playback:

src/views/rewards/lootbox.html
<video id="lootbox-video" width="800" height="600" src="lootbox_open.webm" preload="auto"></video>

Gameface does not render video frames unless the video is actively playing or has been seeked to a specific time. To display a static preview frame without starting playback, seek the video element to the desired timestamp. The following snippet forces the first frame to render as a thumbnail:

src/views/tutorials/preview.js
const videoElement = document.getElementById("tutorial-video");
videoElement.currentTime = 0;

Regardless of what video editing software you use for authoring, encoding your final assets with ffmpeg is recommended. It consistently produces strong decoding performance with its default VPX encoder settings, and no additional encoder options are typically required.

A basic transcoding command takes an input file and converts it to WebM with VP9 video and Vorbis audio:

Terminal
ffmpeg -i VideoIn.mp4 -c:v libvpx-vp9 -b:v 1M -c:a libvorbis -b:a 128k VideoOut.webm

For higher visual quality, use two-pass encoding. The first pass analyzes the source and generates statistics, while the second pass uses that data to produce an optimally compressed output:

Terminal (Two-Pass Encoding)
ffmpeg -i VideoIn.mp4 -c:v libvpx-vp9 -b:v 1M -an -pass 1 -f webm NUL
ffmpeg -i VideoIn.mp4 -c:v libvpx-vp9 -b:v 1M -c:a libvorbis -b:a 128k -pass 2 VideoOut.webm

Transparent videos are useful for overlaying animated elements (character portraits, ability effects, UI transitions) on top of other content without a solid background. To enable the alpha channel in the VPX encoder, add the -pix_fmt yuva420p flag:

Terminal (Transparent Video)
ffmpeg -i TransparentSource.mov -c:v libvpx-vp9 -b:v 1M -pix_fmt yuva420p -c:a libvorbis -b:a 128k TransparentOut.webm

If your source is a sequence of transparent PNG frames rather than a single video file, pass the frame pattern as input:

Terminal (PNG Sequence to Transparent WebM)
ffmpeg -i sequence-%05d.png -c:v libvpx-vp9 -b:v 1M -pix_fmt yuva420p TransparentOut.webm

Most video compression formats store only incremental changes between frames, with full image data stored at specific intervals called keyframes .

When you seek to an arbitrary point in the video, the decoder must start from the nearest previous keyframe and apply all intermediate frame changes to reconstruct the target frame. If your video has long stretches without keyframes, a single seek operation can require hundreds of frames to be decoded before the target frame is ready to display.

Gameface provides several custom APIs to help manage seek performance.

The cohfastseek HTML attribute (or the HTMLMediaElement.cohFastSeek JS property) forces any seek operation to snap to the nearest keyframe instead of decoding all intermediate frames:

Fast Seek Attribute
<video src="cinematic.webm" cohfastseek></video>

The cohGetKeyframeTimestamps() method returns an array of all keyframe timestamps (in seconds) once the video metadata has been parsed. You can use this to build seek controls that only target valid keyframe positions:

src/views/player/seek.js
const video = document.getElementById("cinematic-video");
const keyframes = video.cohGetKeyframeTimestamps();
// Example result: [0, 1.5, 5, 12.3, ...]

The cohPrebufferKeyframe(timestamp) method pre-buffers the encoded data for a specific keyframe so that a subsequent seek to that timestamp can begin decoding immediately without waiting for data to load from disk or network:

src/views/player/prebuffer.js
const video = document.getElementById("cinematic-video");
video.cohGetKeyframeTimestamps().forEach((t) => video.cohPrebufferKeyframe(t));

Gameface supports the standard media playback events on the <video> element, including durationchange, ended, pause, play, playing, seeked, seeking, timeupdate, volumechange, and error.

In addition, Gameface fires two custom events that are particularly useful when synchronizing video playback with other UI animations:

  • cohplaybackstalled: Fired when the video decoder cannot keep up with the playback rate and is unable to provide new frames quickly enough. If you are running CSS or JavaScript animations that need to stay in sync with the video, pause those animations when this event fires.
  • cohplaybackresumed: Fired when the decoder has caught up and normal playback resumes. Resume any synchronized animations at this point.

The following example keeps a subtitle overlay animation in sync with a cinematic video that may experience momentary decoding delays:

src/views/cinematic/sync.js
const video = document.getElementById("cinematic-video");
const animatedOverlay = document.getElementById("subtitle-overlay");
video.addEventListener("cohplaybackstalled", () => {
animatedOverlay.style.animationPlayState = "paused";
});
video.addEventListener("cohplaybackresumed", () => {
animatedOverlay.style.animationPlayState = "running";
});

Video playback performance depends on three primary factors:

  • Bitrate: Lower bitrate reduces the amount of data the decoder must process per second, improving performance. Variable bitrate (VBR) encoding produces better visual quality but can spike in high-motion scenes. If you use VBR, consider setting an upper limit with ffmpeg’s -maxrate flag.
  • Resolution: Higher resolution increases the per-frame pixel data that must be displayed. Unlike bitrate, resolution is constant throughout playback, making it a fixed and predictable performance cost.
  • Transparency: Adds up to 60% more decoding data and roughly 25% more display data compared to opaque videos.

Because video compression reuses frame data from previous frames and only encodes the differences, the actual decoding cost varies frame-to-frame depending on how much motion and scene change occurs. Sequences with rapid movement or frequent cuts will be more expensive to decode than static or slow-panning footage.

While the <video> element is suitable for pre-rendered sequences and background loops, it is not the best tool for every animated element in your UI. Video decoding consumes CPU cycles, and preloading large GIFs consumes RAM. For many common game UI animation patterns, Gameface supports more efficient approaches .

For standard 2D UI animations (glowing icons, looping loading spinners, simple character idles, particle effects), sprite sheets are the preferred technique. Instead of loading a video file or a GIF, a sprite sheet packs all animation frames into a single image. You then animate the background-position via CSS to step through each frame.

This approach completely bypasses video decoding. The engine only needs to shift a texture offset on an already-loaded image, making it lightweight and allowing instantaneous playback with no buffering delay.

Live Views (For 3D Assets and Heavy Media)

Section titled “Live Views (For 3D Assets and Heavy Media)”

If your design requires complex 3D character portraits, rotating inventory item previews, or streaming heavy video content, embedding pre-rendered video files directly in the DOM is not the recommended approach.

Live Views allow you to stream a live render texture directly from the game engine into your frontend layout. The engine renders the 3D scene or video on its side and pushes the result as a texture that your UI element displays. This integrates real-time 3D content and heavy media into your UI layout without the overhead of packaging and decoding video files in the HTML layer.