svg(viewbox="0 0 100 100" id="tree")

div
  button(onclick='gen()') generate
View Compiled
body
  background-color: #000
  display: flex
  justify-content: center
  align-items: center

svg
  height: 100vh
  margin-right: 20px
  transform: scaleY(-1)

line
  stroke: #ddd
  stroke-linecap: round

svg > :first-child
  transform: translateX(50%)
View Compiled
const topology = {
  trunk: {
    length: 16,
    width: 3,
    children: [
      {
        spawnRate: 1,
        position: 1,
        angle: 45,
        type: "branch"
      }
    ]
  },
  leaves: ([{ width }]) =>
    range(
      Math.floor(
        (topology.trunk.width - width) /
          topology.trunk.width *
          Math.random() *
          3
      )
    ).map(_ => ({
      color: "#DC925F",
      position: Math.random(),
      size: Math.random() * 0.5 + 0.5
    })),
  branches: {
    branch: ([{ length, width }]) => ({
      width: width * 0.7,
      length: length * 0.87,
      children: [
        {
          spawnRate: 0.5,
          position: 1,
          angle: 20,
          type: "branch"
        },
        {
          spawnRate: 0.5,
          position: 0.5,
          angle: 45,
          type: "branch"
        }
      ]
    })
  }
};

const entries = obj => Object.keys(obj).map(key => [key, obj[key]]);

const clearEl = el => {
  el.innerHTML = "";
};

const mapEntries = (obj, fn) => {
  const res = {};
  entries(obj).forEach(([k, v], i) => {
    [nk, nv] = fn([k, v], i);
    res[nk] = nv;
  });
  return res;
};

const mapObj = (obj, fn) => mapEntries(obj, ([k, v], i) => [k, fn(v, i)]);

const range = n =>
  Array(n)
    .fill(0)
    .map((_, i) => i);

const setAttrs = (el, attrs = {}) => {
  entries(attrs).forEach(([k, v]) => {
    el.setAttribute(k, v);
  });
  return el;
};

const attach = (el, child) => {
  el.appendChild(child);
  return el;
};

const attachAll = (el, children) => {
  children.forEach(child => {
    attach(el, child);
  });
  return el;
};

const ns = "http://www.w3.org/2000/svg";

const createEl = (name, children = [], attrs = {}) =>
  attachAll(setAttrs(document.createElementNS(ns, name), attrs), children);

const X = { x1: 0, x2: 0 };
const LIMIT = 6;

const resolveConfig = (obj, history) =>
  mapObj(obj, v => (typeof v === "function" ? v(history) : v));

const layer = (
  { children, length, width },
  y,
  angle,
  history,
  lim,
  begin,
  overall = 0
) => {
  history.unshift({ y, angle, length, width });
  const animLen = length / (20 * Math.pow(lim, 1.5));
  const nodes = [
    createEl(
      "line",
      [
        createEl("animate", [], {
          attributeName: "y2",
          dur: `${animLen}s`,
          fill: "freeze",
          from: y,
          to: y + length,
          begin
        }),
        createEl("animate", [], {
          attributeName: "stroke-width",
          dur: `${animLen / 9}s`,
          fill: "freeze",
          from: 0,
          to: width,
          begin
        })
      ],
      {
        ...X,
        y1: y,
        y2: y,
        "stroke-width": 0
      }
    )
  ];

  topology.leaves(history).forEach(({ color, position, size }) => {
    const mod = Math.floor(overall / 180) % 2 == 0;
    nodes.push(
      createEl(
        "circle",
        [
          createEl("animate", [], {
            attributeName: "r",
            dur: `${animLen}s`,
            fill: "freeze",
            from: 0,
            to: size,
            begin: begin + animLen * position
          })
        ],
        {
          r: 0,
          cx: (mod ? -1 : 1) * (size + width / 2),
          cy: y + length * position,
          fill: color
        }
      )
    );
  });
  if (lim) {
    children.forEach(({ position, angle: cangle, spawnRate, type }) => {
      if (Math.random() < spawnRate) {
        nodes.push(
          layer(
            topology.branches[type](history),
            y + position * length,
            cangle,
            history,
            lim - 1,
            begin + animLen,
            overall + cangle
          )
        );
      }
      if (Math.random() < spawnRate) {
        nodes.push(
          layer(
            topology.branches[type](history),
            y + position * length,
            -cangle,
            history,
            lim - 1,
            begin + animLen,
            overall - cangle
          )
        );
      }
    });
  }
  history.shift();
  return createEl("g", nodes, {
    transform: `translate(0 ${y}) rotate(${angle}) translate(0 -${y})`
  });
};

const svg = document.getElementById("tree");

const gen = () => {
  clearEl(svg);
  attach(svg, layer(topology.trunk, 0, 0, [], LIMIT, svg.getCurrentTime()));
};

gen();
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.