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
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.