A small recursive SVG tree: real vector lines and circles, not canvas pixels, so Save SVG below gives you an actual file you can open and edit.
How This Tree Actually Grows
There's no pre-drawn tree image anywhere in this page. Every branch is calculated and drawn fresh, in your browser, by a function that calls itself, which is why no two trees this tool generates ever come out quite the same.
Recursion, Not a Template
One function draws a single branch, then calls itself twice to draw two shorter branches growing out of its tip, and those two call themselves again, and so on. Each generation gets shorter and thinner than the one before it, which is what naturally brings the recursion to a stop instead of needing a hard cutoff to prevent it from running forever.
Why It's a Real SVG File
Every branch and leaf is an actual `<line>` or `<circle>` element in the page, not pixels painted onto a canvas. That distinction is exactly why the Save SVG button below produces a genuine, editable vector file rather than a screenshot, open it in any vector editor afterward and every branch is still its own selectable object.
The Randomness Is Controlled
Hit Regenerate and you'll get a different tree, but not a chaotic one. The angle, length, and depth of each branch are randomized only within a narrow range, enough for real variety without ever producing something that reads as broken or unbalanced. Structure first, randomness second, the same way an actual tree's growth is guided by rules even though no two ever grow identically.
Copy the Code
Plain HTML, CSS, and JavaScript, no dependencies, no build step. Kept deliberately small: a shallow recursion depth and a compact generator, so there isn't much to paste. Comments walk through what each block is doing.
<!-- Fractal Tree -- copy this whole block into any HTML page. -->
<!-- No build step, no dependencies -- plain HTML, CSS, and JS. -->
<!-- Real SVG <line>/<circle> elements, not canvas pixels, so "Save SVG"
below produces a genuine vector file you can open and edit. -->
<div id="fractal-tree">
<svg id="ftSvg" viewBox="0 0 360 260" preserveAspectRatio="xMidYMax meet"></svg>
<div class="ft-controls">
<button id="ftRegen" type="button">Regenerate</button>
<button id="ftSave" type="button">Save SVG</button>
</div>
</div>
<style>
/* Minimal styling -- restyle freely to match your own site. */
#fractal-tree { font-family: sans-serif; max-width: 400px; }
#ftSvg { display: block; width: 100%; height: 260px; background: #0a0d12; border-radius: 8px; }
.ft-controls { display: flex; gap: 8px; margin-top: 10px; }
.ft-controls button { padding: 8px 14px; cursor: pointer; }
/* Gentle continuous sway on the whole tree, purely CSS so it costs
nothing to run alongside the one-off draw-in transitions below. */
@keyframes ftSway {
0%, 100% { transform: rotate(-1deg); }
50% { transform: rotate(1deg); }
}
.ft-sway { animation: ftSway 6s ease-in-out infinite; }
</style>
<script>
(function () {
var SVG_NS = 'http://www.w3.org/2000/svg';
var svg = document.getElementById('ftSvg');
var regenBtn = document.getElementById('ftRegen');
var saveBtn = document.getElementById('ftSave');
var W = 360, H = 260;
function rand(min, max) { return min + Math.random() * (max - min); }
// Bark-to-leaf gradient: branches shift from brown to green with depth,
// leaves get a random natural green.
function branchColor(depth, maxDepth) {
var t = depth / maxDepth;
return 'hsl(' + (25 + t * 65) + ', 45%, ' + (26 + t * 16) + '%)';
}
function leafColor() {
return 'hsl(' + rand(85, 140) + ', 60%, ' + rand(45, 60) + '%)';
}
// Draws a line already hidden (dasharray/dashoffset both set to its own
// length), then flips dashoffset to 0 a frame later so it animates in
// as a "grow" stroke instead of just popping into view.
function addLine(group, x1, y1, x2, y2, color, width, depth) {
var el = document.createElementNS(SVG_NS, 'line');
el.setAttribute('x1', x1); el.setAttribute('y1', y1);
el.setAttribute('x2', x2); el.setAttribute('y2', y2);
el.setAttribute('stroke', color);
el.setAttribute('stroke-width', width);
el.setAttribute('stroke-linecap', 'round');
var len = Math.hypot(x2 - x1, y2 - y1);
el.style.strokeDasharray = String(len);
el.style.strokeDashoffset = String(len);
el.style.transition = 'stroke-dashoffset 0.3s linear';
el.style.transitionDelay = (depth * 0.08) + 's';
group.appendChild(el);
requestAnimationFrame(function () {
requestAnimationFrame(function () { el.style.strokeDashoffset = '0'; });
});
}
function addLeaf(group, x, y, depth) {
var el = document.createElementNS(SVG_NS, 'circle');
el.setAttribute('cx', x); el.setAttribute('cy', y);
el.setAttribute('r', rand(2.5, 4));
el.setAttribute('fill', leafColor());
el.style.opacity = '0';
el.style.transition = 'opacity 0.35s ease';
el.style.transitionDelay = (depth * 0.08 + 0.1) + 's';
group.appendChild(el);
requestAnimationFrame(function () {
requestAnimationFrame(function () { el.style.opacity = '1'; });
});
}
// Recursive branch: each call draws one segment, then (unless it's hit
// max depth or gotten too short) spawns two shorter child branches at
// slightly randomized angles. The length shrinking on every level is
// what naturally stops the recursion, a generous depth cap is a
// backstop, not the real limiter.
function branch(group, x, y, angle, length, depth, maxDepth, spread, ratio) {
if (length < 4 || depth > maxDepth) { addLeaf(group, x, y, depth); return; }
var x2 = x + Math.cos(angle) * length;
var y2 = y + Math.sin(angle) * length;
addLine(group, x, y, x2, y2, branchColor(depth, maxDepth), Math.max(1, (maxDepth - depth + 1) * 0.7), depth);
if (depth === maxDepth) { addLeaf(group, x2, y2, depth + 1); return; }
[-1, 1].forEach(function (side) {
var offset = side * spread * rand(0.6, 1.1);
branch(group, x2, y2, angle + offset, length * ratio * rand(0.85, 1.05), depth + 1, maxDepth, spread, ratio);
});
}
function generate() {
svg.innerHTML = '';
var group = document.createElementNS(SVG_NS, 'g');
group.setAttribute('class', 'ft-sway');
group.style.transformOrigin = (W / 2) + 'px ' + H + 'px';
svg.appendChild(group);
// Kept shallow on purpose (max depth 5-6) so the tree stays visually
// small and each regenerate finishes almost instantly.
var maxDepth = Math.floor(rand(5, 7));
var spread = rand(0.35, 0.55);
var ratio = rand(0.68, 0.78);
branch(group, W / 2, H, -Math.PI / 2, rand(55, 70), 0, maxDepth, spread, ratio);
}
regenBtn.addEventListener('click', generate);
// Exports the live SVG as a real, standalone .svg file -- not a
// screenshot, an actual vector document you can open and edit.
saveBtn.addEventListener('click', function () {
var clone = svg.cloneNode(true);
clone.setAttribute('xmlns', SVG_NS);
var source = new XMLSerializer().serializeToString(clone);
var blob = new Blob([source], { type: 'image/svg+xml' });
var url = URL.createObjectURL(blob);
var link = document.createElement('a');
link.download = 'fractal-tree-' + Date.now() + '.svg';
link.href = url;
link.click();
setTimeout(function () { URL.revokeObjectURL(url); }, 1000);
});
generate();
})();
</script>