← All posts

2D Inverse Kinematics in HTML5 Canvas: CCD Walk Cycles

Master 2D inverse kinematics on HTML5 Canvas. Learn how bone chains and Cyclic Coordinate Descent create slick procedural character walk cycles.

Ever watched an indie web game character scramble up uneven terrain, their legs snapping gracefully onto jagged rocks without a single pre-baked sprite animation, and wondered what sorcery was running under the hood?

The answer is procedural animation driven by Inverse Kinematics (IK). Instead of sketching forty individual frames of a running goblin in Aseprite and watching its feet awkwardly clip through tilted platforms, you define a skeletal rig and let maths do the heavy lifting.

Here is how to construct a lightweight, snappy 2D IK engine in pure JavaScript on the HTML5 Canvas using bone chains and a Cyclic Coordinate Descent (CCD) solver.


Direct Answer: What Is 2D Inverse Kinematics?

Inverse Kinematics (IK) is a mathematical process in computer graphics that calculates the joint angles required to position the end of a kinematic chain (the end effector, such as a foot or hand) at an arbitrary target point in 2D or 3D space.

In traditional Forward Kinematics (FK), parent bones dictate child positions: rotate the hip, and the knee moves; rotate the knee, and the ankle follows. IK flips this logic on its head: you place the foot on a ledge, and the algorithm solves backward to figure out where the knee and hip should bend.


[Hip Joint] (Origin)
    \
     \  Bone 1
      \
     [Knee Joint]
      /
     /  Bone 2
    /
 [Foot / End Effector] ---> [Target: Ground Contact Point]

CCD vs FABRIK: Choosing the Right Web Solver

Browse through game development forums, GitHub repositories, or indie devlogs on YouTube, and you will find two primary algorithms fighting for dominance in 2D procedural motion: Cyclic Coordinate Descent (CCD) and Forward And Backward Reaching Inverse Kinematics (FABRIK).

FeatureCyclic Coordinate Descent (CCD)FABRIK
Core CalculationTrigonometric (angles and cross-products)Geometric (line projections and distances)
Joint ConstraintsTrivial to enforce (clamping scalar angles)Moderate (requires vector re-projection)
Performance (2D)Blazing fast for short chains (2–3 bones)Faster for long, noodle-like chains (10+ bones)
Visual FeelMechanical, snappy, arcade-friendlyFluid, rope-like, organic
Best Used ForLimbs, legs, mechanical turrets, spidersTentacles, tails, physics ropes

For a standard browser game character leg (thigh, shin, foot), CCD remains king. It operates directly on joint angles, which makes angle constraints—like stopping your hero's knee from bending backwards like a flamingo—dead simple.


Anatomy of a 2D Bone Chain

A kinematic chain is a linked list of bones where each segment maintains a fixed length and a local rotation angle.

1. Root: The anchor point (e.g., the character’s pelvis).

2. Bones: Fixed lengths connecting joints.

3. End Effector: The tip of the final bone in the chain (e.g., the sole of the boot).

4. Target: The vector where the end effector wants to be (e.g., the floor tile underneath the character).


Coding a Lightweight CCD Solver in Vanilla JS

A CCD solver loops from the end of the chain backward to the root. For each joint, it calculates the angle from the joint to the current end-effector position, calculates the angle from the joint to the target, and rotates the bone by the difference. You repeat this loop over several iterations until the tip touches the target or errors become unnoticeable.


class Bone {
  constructor(length, angle = 0) {
    this.length = length;
    this.angle = angle; // Relative to parent
  }
}

class LimbChain {
  constructor(root, lengths) {
    this.root = root; // { x, y }
    this.bones = lengths.map(len => new Bone(len));
  }

  solveCCD(target, iterations = 5) {
    for (let it = 0; it < iterations; it++) {
      // Loop backwards from last bone to first
      for (let i = this.bones.length - 1; i >= 0; i--) {
        const jointPositions = this.forwardKinematics();
        const joint = jointPositions[i];
        const endEffector = jointPositions[jointPositions.length - 1];

        // Vectors from current joint to end-effector and target
        const vEffector = { x: endEffector.x - joint.x, y: endEffector.y - joint.y };
        const vTarget = { x: target.x - joint.x, y: target.y - joint.y };

        const angleEffector = Math.atan2(vEffector.y, vEffector.x);
        const angleTarget = Math.atan2(vTarget.y, vTarget.x);

        let deltaAngle = angleTarget - angleEffector;

        // Apply rotation
        this.bones[i].angle += deltaAngle;

        // Optional: Apply angle constraints here
        // e.g., if (i === 1) this.bones[i].angle = Math.max(0.1, this.bones[i].angle);
      }
    }
  }

  forwardKinematics() {
    const positions = [{ ...this.root }];
    let currentAngle = 0;

    for (let i = 0; i < this.bones.length; i++) {
      currentAngle += this.bones[i].angle;
      const prev = positions[i];
      positions.push({
        x: prev.x + Math.cos(currentAngle) * this.bones[i].length,
        y: prev.y + Math.sin(currentAngle) * this.bones[i].length
      });
    }
    return positions;
  }
}

Rendering this to an HTML5 Canvas simply requires calling forwardKinematics(), looping through the coordinates, and drawing lines using ctx.lineTo().


Assembling the Procedural Walk Cycle

Getting feet to touch points is only half the battle. If the target stays glued beneath the pelvis, your character will glide across the screen like an ice skater.

A natural walk cycle requires step triggers and sinusoidal trajectories:

1. The Step Trigger

Monitor the distance between the character's hip and foot target. When the character moves forward and the distance exceeds a defined threshold (say, 50 pixels behind the hip), trigger a step.

2. The Foot Arc (Trajectory)

Do not snap the target forward instantly. Instead, interpolate the foot’s position to a new anchor point ahead of the character along a parabolic arc:

  • X position: Lerp towards hip.x + stride_distance.
  • Y position: Offset by a sine wave Math.sin(progress Math.PI) step_height to lift the leg off the ground during the stride.

3. Alternating Gaits

To prevent both legs from jumping forward simultaneously, enforce a simple state check: Leg B cannot begin its step cycle until Leg A has firmly planted its target back on the floor.


Key Takeaways for Web Game Developers

  • Iterate minimally: For 2-bone limbs (thigh and shin), a CCD solver converges in just 2 to 4 iterations. Keeping iterations low preserves your 60 FPS frame budget on mobile browsers.
  • Clamp your angles: Without joint limits, CCD solvers tend to buckle into unnatural shapes or fold bones backwards when reaching for extreme targets.
  • Separate physics from visual bones: Calculate your character's main collision box using standard rigid-body mechanics, then let the IK skeleton dangle beneath it to handle foot placement and surface conformity.

Thanks for reading. Browse more from the Wobblox blog, or jump straight into all 100 free games.