Recursive subdivision

재귀적 분할

Repeatedly splitting a rectangle in two to fill the frame — the mechanism behind Mondrian-like or Swiss-grid-style compositions.

Also known as: Space partitioning artMondrian generator
···
js
const c = document.createElement('canvas');
document.body.appendChild(c);
c.style.width = '100%'; c.style.height = '100%';
const ctx = c.getContext('2d');
const cs = getComputedStyle(document.documentElement);
const palette = [cs.getPropertyValue('--accent').trim(), cs.getPropertyValue('--accent-2').trim(), cs.getPropertyValue('--accent-3').trim()].map(x => x || '#5b5bf7');
let w, h, leaves;
function resize() {
  const dpr = Math.min(devicePixelRatio || 1, 2);
  w = innerWidth; h = innerHeight;
  c.width = w * dpr; c.height = h * dpr;
  ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
  leaves = [{ x: 0, y: 0, w, h, col: Math.random() < 0.3 ? palette[Math.floor(Math.random() * 3)] : null }];
  for (let i = 0; i < 16; i++) split(); // 초기 상태부터 꽤 나뉘어 있도록
}
function split() {
  if (leaves.length > 46) return;
  const candidates = leaves.filter((r) => r.w > 40 && r.h > 40);
  if (!candidates.length) return;
  const r = candidates[Math.floor(Math.random() * candidates.length)];
  const idx = leaves.indexOf(r);
  const vertical = r.w > r.h;
  const ratio = 0.32 + Math.random() * 0.36;
  const col = () => (Math.random() < 0.22 ? palette[Math.floor(Math.random() * 3)] : null);
  let a, b;
  if (vertical) {
    const cut = r.w * ratio;
    a = { x: r.x, y: r.y, w: cut, h: r.h, col: col() };
    b = { x: r.x + cut, y: r.y, w: r.w - cut, h: r.h, col: col() };
  } else {
    const cut = r.h * ratio;
    a = { x: r.x, y: r.y, w: r.w, h: cut, col: col() };
    b = { x: r.x, y: r.y + cut, w: r.w, h: r.h - cut, col: col() };
  }
  leaves.splice(idx, 1, a, b);
}
addEventListener('resize', resize);
resize();

function draw() {
  ctx.fillStyle = '#0d0d12'; ctx.fillRect(0, 0, w, h);
  for (const r of leaves) {
    if (r.col) { ctx.fillStyle = r.col; ctx.fillRect(r.x, r.y, r.w, r.h); }
    ctx.strokeStyle = 'rgba(241,240,236,0.7)'; ctx.lineWidth = 2; ctx.strokeRect(r.x, r.y, r.w, r.h);
  }
}
draw();
let last = 0;
(function loop(t) {
  if (t - last > 140) { split(); draw(); last = t; }
  if (leaves.length >= 46 && t - last > 3200) { resize(); draw(); last = t; }
  requestAnimationFrame(loop);
})(0);

Start with one rectangle covering the whole canvas. At each step, pick a remaining rectangle and cut it in two — horizontally or vertically, at a random ratio. Repeat as many times as you like and the varying-sized rectangles settle into a grid; because the axis and ratio are random each time, the layout reads far more rhythmic than a regular grid. The same idea, under the name binary space partitioning (BSP), also drives game rendering and collision systems.

Fill only some cells with an accent colour and leave the rest as background, and the result reads like a Piet Mondrian composition or the asymmetric grids of Swiss-style posters. This demo picks one cell at a time on an interval and splits it further, so the layout starts sparse and gets progressively denser.

Use it for poster and thumbnail background composition, generating the skeleton of a responsive layout, or auto-placing placeholder blocks in a UI mockup.

When to use

Use it for poster/thumbnail composition or quickly generating several layout skeletons. Keep the fraction of coloured cells low to preserve the Mondrian feel.