How to Animate 3D Characters in the Browser
3D character animation in the browser has reached the point where a single developer can get fully animated characters running in under an hour using free tools. The key is choosing the right format (glTF), the right animation source (Mixamo for rapid results, Blender for custom work), and the right engine for your project.
Step 1: Get a Rigged 3D Character
A rigged character is a 3D mesh with an internal skeleton (armature) that controls how the mesh deforms when bones move. You need this rig before you can apply any animations. There are three ways to get one.
Mixamo auto-rigging is the fastest path. Upload any humanoid 3D model (FBX, OBJ, or ZIP) to Mixamo's web service at mixamo.com. Its auto-rigger analyzes the mesh, places a standard humanoid skeleton inside it, and generates skin weights automatically. The process takes about 30 seconds and works well for characters with standard bipedal proportions. Unusual body shapes (four arms, long tails, non-humanoid creatures) may need manual weight correction in Blender afterward.
Blender rigging gives you complete control. Create an armature, position bones inside the mesh, and weight-paint the mesh vertices to assign each vertex to the bones that should influence it. Blender's automatic weight painting (Ctrl+P -> Armature Deform With Automatic Weights) produces good starting weights for humanoid characters, which you can then refine manually in weight paint mode. The learning curve is significant, but the result is a custom rig that matches your character's exact proportions and movement requirements.
Pre-rigged asset packs from Sketchfab, TurboSquid, itch.io, Kenney, and Quaternius provide rigged characters ready for animation. Many are free. Look for characters exported in glTF or FBX format with a skeleton already applied. Verify the rig has a reasonable bone count (20 to 30 bones for a humanoid is standard) and that the skin weights are clean by testing the model in Blender's pose mode before committing to the asset.
Step 2: Source Animation Clips
Mixamo's animation library contains over 2,000 free animation clips: walk cycles, run cycles, idles, jumps, attacks, dances, emotes, and special actions. Select your rigged character in Mixamo, browse or search the animation library, preview animations on your character in real time, and download each clip as a separate FBX file. Set "Without Skin" for the download to get just the animation data (smaller file), or "With Skin" if you want the character mesh bundled with each clip.
Blender animation lets you create custom clips by posing the armature at keyframes. Set your timeline to 30fps, pose the character at frame 0, insert keyframes for all bones (I key -> Whole Character), move to frame 15, pose the character at the next key pose, insert keyframes, and repeat. Blender interpolates between keyframes automatically. Use the graph editor to adjust the interpolation curves for natural-looking motion. Export each animation as a separate NLA action strip.
Retargeting lets you apply animations from one skeleton to a different character. If your character has a different rig than the Mixamo standard, retargeting maps bone names and rotations between the source and target skeletons. Blender's NLA (Non-Linear Animation) system supports retargeting, and plugins like Rokoko and Auto-Rig Pro simplify the process. For characters rigged with Mixamo's auto-rigger, retargeting is unnecessary because all Mixamo animations use the same skeleton standard.
Step 3: Export to glTF
glTF (GL Transmission Format) is the standard 3D format for the web. It supports mesh geometry, materials with PBR textures, skeletal rigs, skin weights, and animation keyframes in a single file. The binary variant (GLB) bundles everything into one file, which is convenient for web delivery. Both ThreeJS and BabylonJS load glTF/GLB natively.
In Blender, export with File -> Export -> glTF 2.0. Enable "Include Animations" and set the animation mode to "Actions" (exports each NLA action as a separate animation clip) or "Active Actions" (exports only the currently active action). Enable "Include Skinning" for skeletal deformation data. For the mesh, enable "Apply Modifiers" and set the format to GLB for a single file or glTF Separate for human-readable JSON plus separate binary and texture files.
If you downloaded FBX files from Mixamo, import them into Blender first (File -> Import -> FBX), then re-export as glTF. This conversion step is necessary because ThreeJS's FBX loader is less reliable than its glTF loader, and glTF is the format both major web engines recommend. During import, Blender may warn about bone orientation differences; these are cosmetic and do not affect animation playback.
For multiple animation clips, you have two export strategies. Strategy one: embed all animations in a single GLB file. This keeps everything in one download but increases file size. Strategy two: export the character mesh in one GLB and each animation clip in separate GLB files. This lets you load animations on demand (load idle and walk immediately, load attack and special actions later). Strategy two reduces initial load time for web games.
Step 4: Load in ThreeJS or BabylonJS
In ThreeJS, use GLTFLoader to load the model: const loader = new GLTFLoader(); loader.load('character.glb', function(gltf) { scene.add(gltf.scene); }). The loaded gltf object contains gltf.scene (the 3D scene graph), gltf.animations (array of AnimationClip objects), and gltf.parser (for accessing raw data). The character mesh and skeleton are already connected; you just add the scene to your ThreeJS scene and the character renders with its skeleton in the bind pose.
To play animations in ThreeJS, create an AnimationMixer attached to the character model: const mixer = new AnimationMixer(gltf.scene). Then create AnimationAction objects for each clip: const idleAction = mixer.clipAction(gltf.animations[0]); const walkAction = mixer.clipAction(gltf.animations[1]). Call idleAction.play() to start the idle animation. In your render loop, call mixer.update(deltaTime) to advance the animation by the elapsed time.
In BabylonJS, use SceneLoader.ImportMesh or SceneLoader.Append to load glTF files. The loaded meshes automatically include skeleton data, and animation groups are created for each animation in the file. Access them through scene.animationGroups, then play, pause, or blend them. BabylonJS provides a higher-level animation API than ThreeJS with built-in blending and weight control on animation groups.
Loading separate animation files requires merging them into the character's mixer. In ThreeJS, load each animation GLB separately, extract its AnimationClip from gltf.animations[0], and call mixer.clipAction(clip) on the character's mixer. The clip's bone names must match the character's skeleton bone names for the animation to apply correctly. Mixamo animations use consistent bone naming, so clips from different Mixamo downloads work interchangeably on any Mixamo-rigged character.
Step 5: Set Up Animation Mixing
Animation mixing controls transitions between clips. Without mixing, switching from walk to run produces a jarring visual snap. With mixing, the character smoothly crossfades between the two animations over a short duration, producing natural-looking transitions.
In ThreeJS, crossfade between actions with: walkAction.crossFadeTo(runAction, 0.3, true). This blends from walk to run over 0.3 seconds. The third parameter (true) warps the time scales to match, preventing the animations from falling out of sync during the blend. To stop the previous animation after the blend completes, listen for the 'finished' event or set walkAction.clampWhenFinished = true.
A practical animation controller for a game character maintains references to all AnimationAction objects and the current state. When the game logic determines a state change (player starts moving, stops moving, jumps, attacks), the controller crossfades from the current action to the new action and updates the current state reference. The crossfade duration varies by transition: idle to walk uses 0.2 seconds, walk to run uses 0.3 seconds, any state to hurt uses 0.05 seconds for immediate visual feedback.
Action weights control how much each animation contributes. Setting idle.weight = 0.5 and walk.weight = 0.5 produces a 50/50 blend of both animations. This is useful for blend trees where the character's speed determines the walk/run blend ratio. ThreeJS and BabylonJS both support manual weight setting alongside automatic crossfade transitions.
Step 6: Optimize for Web Performance
Polygon count is the first optimization target. A game character for desktop WebGL can have 5,000 to 15,000 triangles. For mobile WebGL, aim for 2,000 to 5,000 triangles. Blender's Decimate modifier reduces polygon count semi-automatically. For crowd scenes with many characters, use level-of-detail (LOD) meshes: full-detail mesh for the player character, half-detail for nearby NPCs, quarter-detail for distant NPCs.
Bone count directly affects skinning performance. Each bone requires a matrix calculation per frame, and each vertex is transformed by its weighted bone influences. A 30-bone skeleton is standard for a main character. For background NPCs, reduce to 15 to 20 bones by merging finger bones, removing facial bones, and simplifying the spine chain. For distant crowd characters, 8 to 10 bones (hips, spine, head, upper arms, upper legs) is sufficient.
Texture compression reduces the download size and GPU memory usage of character textures. Basis Universal (via KTX2 containers) provides GPU-compressed textures that decompress on the GPU rather than in JavaScript, saving both download size and memory. ThreeJS supports KTX2 loading with KTX2Loader. For simpler setups, WebP textures with mipmaps provide good compression with broad browser support.
Animation compression reduces the keyframe data size. Removing redundant keyframes (where the value does not change from the previous keyframe), reducing keyframe precision from 32-bit to 16-bit floats, and using quaternion compression for rotations all reduce the animation data size. Blender's glTF exporter applies some of these optimizations automatically. For additional compression, the Draco extension in glTF provides mesh and animation data compression that the browser decompresses at load time.
The fastest path to animated 3D characters in a web game is Mixamo (auto-rig + animation library) exported to glTF and loaded with ThreeJS GLTFLoader or BabylonJS SceneLoader. Use AnimationMixer with crossFadeTo for smooth transitions between animation states. For web performance, keep characters under 10,000 triangles and 30 bones, and load animation clips on demand rather than bundling all clips in the initial GLB file.