Show code
<!DOCTYPE html>
<html><head><title></title><style>body{margin:0;overflow:hidden}canvas{display:block}</style></head><body onload="init()"><canvas id="c"></canvas><script>
const C = document.getElementById('c');
const ctx = C.getContext('2d');
let W, H;
let treeSegments = [];
let animationState = 'growing'; // growing, stable, resting
let segmentCounter = 0;
let lastTime = 0;
function resize() {
W = window.innerWidth;
H = window.innerHeight;
C.width = W;
C.height = H;
}
window.addEventListener('resize', resize);
resize();
// Branch structure: [start_x, start_y, initial_length, current_progress, max_depth]
function generateTree(x, y, length, depth) {
if (depth <= 0 || length < 5) return [];
let segments = [{ x1: x, y1: y, x2: x + length * Math.cos(0), y2: y - length * Math.sin(0), maxLen: length, progress: 0, depth: depth }];
if (depth > 1) {
let angle = Math.PI / 6; // Base angle
for (let i = 0; i < 2; i++) {
let newLength = length * (0.6 + Math.random() * 0.3);
let newAngle = angle + (i === 0 ? -Math.PI/4 : Math.PI/4) + (Math.random() - 0.5) * Math.PI / 8;
// Calculate end points for the child branch
let nextX = x + length * Math.cos(newAngle);
let nextY = y - length * Math.sin(newAngle);
segments.push({ x1: nextX, y1: nextY, maxLen: newLength, progress: 0, depth: depth - 1, parentIndex: segments.length - 1 });
}
}
return segments;
}
function init() {
treeSegments = [];
segmentCounter = 0;
animationState = 'growing';
lastTime = performance.now();
resize();
// Start the first tree from the bottom center, pointing up
treeSegments = generateTree(W * 0.5, H - 50, H * 0.3, 10);
requestAnimationFrame(animate);
}
function updateProgress(deltaTime) {
let finishedCount = 0;
for (const seg of treeSegments) {
if (seg.progress < 1) {
// Growth speed based on depth/size for visibility
let growthRate = Math.min(0.02, 0.05 / (seg.depth + 1));
seg.progress += growthRate * deltaTime;
if (seg.progress > 1) seg.progress = 1;
}
}
return finishedCount; // Placeholder return
}
function drawTree(time) {
ctx.clearRect(0, 0, W, H);
let timeFactor = Math.sin(time / 1000 + 0) * 5;
for (let i = 0; i < treeSegments.length; i++) {
const seg = treeSegments[i];
if (!seg || seg.progress === 0 && seg.depth <= 0) continue;
// Calculate current end points based on progress
let p = seg.progress;
let lengthFactor = p;
// Determine the starting point (x1, y1) relative to parent/canvas
// For simplicity and stability, we recalculate segment coordinates using vectors:
if (seg.depth === treeSegments[i-1]?.depth + 1 && i > 0) {
// Find the true connection point from the previous segment's end
let prev = treeSegments[i-1];
let parentX = prev.x2;
let parentY = prev.y2;
// Calculate angle based on initial structure (this is complex to store precisely,
// so we approximate using relative start/end points and scaling by progress)
let dx_full = seg.x2 - seg.x1;
let dy_full = seg.y2 - seg.y1;
// Recalculate the effective initial coordinates for this segment's growth path:
seg.start_x = seg.x1;
seg.start_y = seg.y1;
seg.end_x = seg.x2;
seg.end_y = seg.y2;
} else {
// Initial segment or root node (using stored x1, y1 as start)
seg.start_x = seg.x1;
seg.start_y = seg.y1;
seg.end_x = seg.x2;
seg.end_y = seg.y2;
}
// --- Wind Sway Calculation (applied to the whole segment) ---
let swayX = Math.sin(time / 500 + i * 0.1) * timeFactor * (i % 3 === 0 ? 0.8 : 1);
let swayY = Math.cos(time / 600 + i * 0.2) * timeFactor * (i % 4 === 0 ? 0.7 : 1);
// --- Interpolation for current position ---
let x_curr = seg.start_x + (seg.end_x - seg.start_x) * lengthFactor;
let y_curr = seg.start_y + (seg.end_y - seg.start_y) * lengthFactor;
// Apply sway to the drawn path
let x1 = seg.start_x + swayX;
let y1 = seg.start_y + swayY;
let x2 = x_curr + swayX;
let y2 = y_curr + swayY;
// Draw Branch
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.strokeStyle = `rgba(45, 60, 30, ${0.5 + seg.depth * 0.1})`;
ctx.lineWidth = Math.max(1, 3 - seg.depth * 0.3);
ctx.lineCap = 'round';
ctx.stroke();
// Draw Leaves/Tips if complete
if (seg.progress >= 0.99 && seg.depth > 0) {
let leafX = x2 + swayX;
let leafY = y2 + swayY;
ctx.fillStyle = '#38761d';
ctx.beginPath();
ctx.arc(leafX, leafY, Math.max(0.5, 1 - seg.depth * 0.2), 0, Math.PI * 2);
ctx.fill();
}
}
// Display counter
ctx.fillStyle = '#eee';
ctx.font = '14px Arial';
ctx.fillText(`Branches: ${segmentCounter}`, 10, 25);
return true; // Always continue for now
}
function animate(time) {
const deltaTime = time - lastTime;
lastTime = time;
if (animationState === 'growing') {
updateProgress(deltaTime);
drawTree(time);
// Simple check: Assume growth is done if progress stabilizing after a few seconds
setTimeout(() => {
animationState = 'stable';
}, 6000); // Growth phase lasts 6 seconds
} else if (animationState === 'stable') {
drawTree(time);
// Hold for 2 seconds, then restart
if (!window.restartTimer) window.restartTimer = setTimeout(() => {
init();
}, 2000);
}
requestAnimationFrame(animate);
}
</script></body></html>