Skip to content
Founding Lifetime: every Pro skillfirst 100 members · $169 onceUpgrade
Lookbk
Sign In
Effect guide

Isometric city animation for a website

A small city seen from above at an angle, with no vanishing point: blocks of towers, streets with traffic, windows switching on at dusk. How an isometric city is drawn, a canvas starter you can paste and run, and the skill that builds a finished city hero.

Free skills to start. Pro is $79 a year for every skill, or $9.99 a month for 10. See pricing

What is a isometric city?

An isometric city shows buildings from above and to one side, with every parallel line kept parallel. Nothing shrinks with distance, so the whole city stays readable at once, like a map that has grown up into 3D. It is the view of SimCity 2000 and a long line of city builders since, and it reads as planned, tidy and a little toy-like.

On a website it suits proptech, real estate, urban planning, mobility, logistics, smart-city and mapping products: anything about places, infrastructure or many things happening at once. Motion brings it to life: towers rising, windows lighting floor by floor, cars running the streets.

How the effect works

Isometric projection turns a grid position into a screen position with two sums. Screen x is (x − y) × half the tile width; screen y is (x + y) × half the tile height, minus the height above the ground. With tiles twice as wide as they are tall you get the 2:1 angle that almost all isometric game and pixel art uses, because its edges step cleanly across pixels.

Each building is a box: draw its two front faces and its roof as three filled shapes, the lit face lighter than the shaded one. A 2D canvas has no depth buffer, so draw from back to front: sort buildings and cars by x + y and paint them in that order, and nearer things cover farther ones. That is the painter’s algorithm.

  1. 01

    Lay out the plan

    A grid of lots with a street every few rows and columns. Heights rise toward the middle for a downtown, and a few lots stay parks.

  2. 02

    Project to the screen

    screenX = (x − y) × tileW / 2 and screenY = (x + y) × tileH / 2 − z, with tileH half of tileW.

  3. 03

    Draw back to front

    Sort everything by x + y and draw each box as two side faces and a roof, shaded by which way they face.

  4. 04

    Animate the details

    Windows that switch on and off on their own slow clocks, and cars that move along the street lines, headlights coming toward you and tail lights going away.

HTML

An isometric city on a 2D canvas

A repeatable city plan of extruded blocks around a taller downtown, drawn back to front, with windows switching on and off and cars running the streets. Change N for a bigger city and CARS for more traffic. Reduced motion gets one still frame.

<canvas id="city" style="position:fixed;inset:0;width:100%;height:100%;background:#0e1228"></canvas>
<script>
const canvas = document.getElementById('city')
const g = canvas.getContext('2d')
const N = 13, CARS = 26 // N cells a side; a street every 4th row and column
const still = matchMedia('(prefers-reduced-motion: reduce)').matches
const rand = (i) => { const x = Math.sin(i * 127.1 + 311.7) * 43758.5453; return x - Math.floor(x) } // repeatable random
const isStreet = (i) => i % 4 === 0

// The plan: every lot off the streets gets a height, tallest near the middle; a few are parks
const lots = []
for (let i = 0; i < N; i++) for (let j = 0; j < N; j++) {
  if (isStreet(i) || isStreet(j)) continue
  const d = Math.hypot(i - N / 2, j - N / 2) / (N / 2)
  const r = rand(i * 31 + j)
  lots.push({ i, j, park: r < 0.1, floors: Math.round(1 + Math.max(0, 1 - d) * 7 * (0.4 + r) + rand(i + j * 17) * 2), warm: r > 0.5 })
}
// Cars drive along the streets; dir is +1 or -1 along the street, lane keeps them apart
const streets = []
for (let k = 0; k < N; k += 4) streets.push(k)
const cars = Array.from({ length: CARS }, (_, n) => ({
  alongX: n % 2 === 0, line: streets[n % streets.length], dir: rand(n) < 0.5 ? 1 : -1,
  start: rand(n + 99) * N, speed: 0.6 + rand(n + 7) * 0.6,
}))

let w, h, dpr, tw, th, fh, ox, oy // tile width and height, floor height, origin
function size() {
  dpr = Math.min(devicePixelRatio, 2)
  w = canvas.width = innerWidth * dpr
  h = canvas.height = innerHeight * dpr
  tw = Math.min(w * 0.85, h * 1.25) / N; th = tw / 2; fh = th * 0.5
  ox = w / 2; oy = h / 2 - (N * th) / 2 + fh * 3 // nudged down to leave room for the towers
}
size()
// Isometric projection: grid x and y run down-right and down-left, z goes straight up
const iso = (x, y, z = 0) => [ox + (x - y) * tw / 2, oy + (x + y) * th / 2 - z]

function poly(color, ...pts) {
  g.fillStyle = color; g.beginPath(); g.moveTo(...pts[0])
  for (const p of pts.slice(1)) g.lineTo(...p)
  g.closePath(); g.fill()
}

function building(b, t) {
  const m = 0.12, i = b.i + m, j = b.j + m, s = 1 - 2 * m, z = b.floors * fh // inset from the lot edge
  if (b.park) { poly('#1f3a3a', iso(i, j), iso(i + s, j), iso(i + s, j + s), iso(i, j + s)); return }
  poly('#2b3260', iso(i, j + s), iso(i + s, j + s), iso(i + s, j + s, z), iso(i, j + s, z)) // left face, in shade
  poly('#4a5389', iso(i + s, j), iso(i + s, j + s), iso(i + s, j + s, z), iso(i + s, j, z)) // right face, lit
  poly('#6b75b0', iso(i, j, z), iso(i + s, j, z), iso(i + s, j + s, z), iso(i, j + s, z)) // roof
  // Windows: two per face per floor, each switching on and off on its own slow clock
  for (let f = 0; f < b.floors; f++) for (let c = 0; c < 2; c++) for (let side = 0; side < 2; side++) {
    const seed = b.i * 977 + b.j * 131 + f * 7 + c * 3 + side
    const on = rand(seed + Math.floor(t / (4 + rand(seed) * 6) + rand(seed + 1))) > 0.55
    if (!on) continue
    g.fillStyle = b.warm ? '#ffd38a' : '#bfe3ff'
    const u = (0.2 + c * 0.4) * s, z0 = f * fh + fh * 0.3, z1 = z0 + fh * 0.45
    const at = side ? (a, zz) => iso(i + a, j + s, zz) : (a, zz) => iso(i + s, j + a, zz)
    poly(g.fillStyle, at(u, z0), at(u + 0.2 * s, z0), at(u + 0.2 * s, z1), at(u, z1))
  }
}

function car(c, t) {
  const along = (((c.start + c.dir * c.speed * t) % N) + N) % N
  const lane = c.line + 0.5 + c.dir * 0.18
  const [x, y] = c.alongX ? [along, lane] : [lane, along]
  const [sx, sy] = iso(x, y, 1.5 * dpr)
  g.fillStyle = c.dir > 0 ? '#fff4d8' : '#ff5d5d' // coming toward you: headlights; going away: tail lights
  g.shadowColor = g.fillStyle; g.shadowBlur = 8 * dpr
  g.beginPath(); g.arc(sx, sy, 2 * dpr, 0, Math.PI * 2); g.fill()
  g.shadowBlur = 0
}

function frame(now) {
  const t = still ? 6 : now / 1000
  g.fillStyle = '#0e1228'; g.fillRect(0, 0, w, h)
  poly('#141933', iso(0, 0), iso(N, 0), iso(N, N), iso(0, N)) // streets
  for (const b of lots) poly('#1d2342', iso(b.i, b.j), iso(b.i + 1, b.j), iso(b.i + 1, b.j + 1), iso(b.i, b.j + 1))
  // Painter's order: draw from the back (small x + y) to the front, cars and buildings together
  const items = lots.map((b) => ({ k: b.i + b.j + 1, draw: () => building(b, t) }))
  for (const c of cars) {
    const along = (((c.start + c.dir * c.speed * t) % N) + N) % N
    items.push({ k: along + c.line + 0.5 + 0.01, draw: () => car(c, t) })
  }
  items.sort((a, b) => a.k - b.k).forEach((it) => it.draw())
  if (!still) requestAnimationFrame(frame)
}
addEventListener('resize', () => { size(); if (still) frame(0) })
requestAnimationFrame(frame)
</script>

A starting point written for this page, free to use in your own projects. The skills below build the finished version with checks, fallbacks and a recorded video.

What to get right on a real page

  1. 01

    Keep one angle

    Mixing true 30° isometric with 2:1 tiles, or adding any perspective, makes a city look warped. Pick one projection and use it for every building, car and shadow.

  2. 02

    Sort with care

    Ordering by x + y works for boxes that fit inside one tile. Buildings that span several tiles, or cars crossing between tiles, can draw in the wrong order: split big ones into tile-sized pieces, or move to WebGL and its depth buffer.

  3. 03

    Keep the copy readable

    A busy city full of lights fights a headline set over it. Put the city beside the copy, or wash it toward the background behind the text.

  4. 04

    Hold a still frame for reduced motion

    Respect prefers-reduced-motion: show one finished frame instead of the loop. A visitor who turned motion off still gets the look.

Skills that build a isometric city

A finished isometric city hero with your colors, fonts, logo and words, and a seamless 1080p loop for launch posts.

  1. Isometric City Hero: an example of what it makes
    web
    Isometric City HeroClaude CodeCodexCursor+3 more agents

    Motion Heroes · Pro

    Isometric City Hero

    Animated WebGL hero · page + embed + 1080p video

    A small city on a survey grid, planned from a seed, beside your headline. The blocks rise out of the plan from the middle outward, the sun sets and long shadows sweep across the streets, windows light up floor by floor (warm homes, cool offices, shop fronts), street lamps come on and cars run every street with headlights and tail lights. On one parcel a proposed tower in your accent color fills in floor by floor each loop, with a ring pulsing round its lot. Drawn in raw WebGL1, with 27 documented settings.

    Worth knowing: The plan is generated from a seed, so it will not map a real city or address, and the buildings are simplified blocks.

    Needs: Node.js 18+, Chrome (or Chromium/Edge), and ffmpeg for the video

    See what Isometric City Hero makes

Install

Build a isometric city with your agent

Each skill is a SKILL.md folder, so it installs the same way in Claude Code, Codex, Cursor and other agents. The example below uses Isometric City Hero; each skill page has its own prompt.

1. Paste the install prompt

Your agent runs the installer. The first time, it shows a sign-in link so you can approve it in the browser.

Install the "Isometric City Hero" skill from Lookbk (https://lookbk.dev/skills/isometric-city-hero).
1. Run: curl -fsSL https://lookbk.dev/cli.js | node - add isometric-city-hero
2. If it prints a sign-in link, show it to me. When I say I approved it, run the same command again.
3. When the install finishes, read the SKILL.md it installed and tell me what you need from me to make the first result.

Or run it in a terminal

curl -fsSL https://lookbk.dev/cli.js | node - add isometric-city-hero

2. Start a new session

Agents load skills when a session starts. The installer writes the folder where your agent looks:

Claude Code
~/.claude/skills/isometric-city-hero
Codex
~/.agents/skills/isometric-city-hero
Cursor
~/.agents/skills/isometric-city-hero

3. Ask for the result

Describe it in plain words, for example:

Build an Isometric City hero for Parcelwise, a zoning and site-modelling platform: midnight navy, mint accent, the proposed tower on lot 14-B, headline 'See the block before it's built.', and record a 12-second loop.

Another agent? Install a skill in any agent

FAQ

Questions

Still stuck? The guides cover installing, agents and licensing in more detail.

Read the guides
What is the difference between isometric and perspective?

Perspective makes far things smaller, and parallel lines meet at a vanishing point. Isometric keeps every size and every parallel line the same across the scene, so a building at the back is drawn exactly like one at the front.

Is isometric art really 30 degrees?

True isometric uses 30° lines. Most game and pixel art uses 2:1 tiles, about 26.6°, which step cleanly across pixels and are still called isometric. The starter uses 2:1.

Canvas, SVG or WebGL for an isometric city?

SVG or CSS transforms are fine for a few dozen still buildings. A 2D canvas handles hundreds of boxes with moving lights. WebGL adds a depth buffer and per-pixel lighting, which suits shadows and thousands of windows; Isometric City Hero is drawn in raw WebGL1.

Can the city show a real place?

Not in Isometric City Hero: it plans its city from a seed, and the same seed always gives the same city, so you try seeds until the skyline suits you. To draw a real place, read building footprints and heights from map data and project them the same way.

Can I get the city as a video?

Yes. Isometric City Hero records a frame-perfect 1080p MP4 that loops seamlessly, and a poster, with portrait sizes too.