SVG✨ Premium

Fractal Tree

A fractal tree drawn by a recursive function over 10 levels: its branches gently spread and close, from a pale pink trunk to green tips.

CanvasFractalRecursive

Updated

This effect is part of Effect.Labs — 840 vanilla effects, some free, some premium. SVG has 30 effects, including 3 free. Explore the category →

3 usage examples

Tree at the top of a genealogy website home page — Fractal Tree example 1

① Tree at the top of a genealogy website home page

WhenOn Lignavel, the tree fills the right half of the hero, standing on a ground line; on the left, the "Every branch has a story" headline and the search-by-name field.
WhyA tree that forks in two at every generation is the exact metaphor for a family tree, and its slow sway adds life without pulling attention from the form.
SettingsRoot in position:absolute over 47 % of the width, from 40 px below the top to the bottom (--ftr-height:100%): 64 px trunk. Text column limited to 46 % so the widest branches never slide under the form.
Reward card after a 30-day streak in a habit app — Fractal Tree example 2

② Reward card after a 30-day streak in a habit app

WhenIn Pousselin, the right-hand card stays locked while the streak is 29 days; clicking "Check off today" ticks the 30th box and reveals the tree with the congratulations message.
WhyThe reward is a living object rather than a static badge: the tree keeps swaying while the card is open, which makes people come back to look at it.
SettingsThe root ships with the hidden attribute; the scene script removes it on click. The sold code, which waits for a non-zero size, starts at that moment. 220 × 222 px area, 44 px trunk.
Small illustration for a garden journal's empty state — Fractal Tree example 3

③ Small illustration for a garden journal's empty state

WhenIn Semavert, when no planting has been logged for the season, a 132 × 112 px tree sways above the "No plantings this season yet" message and the add button.
WhyAn animated empty state reassures (the app is not broken) and invites action; at this size the tree stays readable because branch thickness follows the level, not the area size.
SettingsRoot in a 132 × 112 px block (--ftr-height:100%), canvas set to aria-hidden="true" since the heading already states the situation. Scene background matched to the root's (#0a0a0f).

How it works

The branch() function draws a segment, then calls itself twice from its tip, with the length cut to 70 % and the angle shifted left then right. It stops at the 10th level or when a branch would be shorter than 2 px: 1,023 segments in all. The trunk starts at the middle of the bottom edge and is 20 % of the area height; the whole tree takes about 64 % of the height and 76 % of the height in width, so a 300 px tall area needs at least 230 px of width.

Only the spread moves: it equals 0.5 + sin(phase) × 0.2 radian, from 0.3 to 0.7, and the phase advances 0.0012 radian per millisecond, a full cycle about every 5.2 s. Elapsed time is capped at 50 ms per frame, so the rhythm is the same at 60 and 120 Hz. Nothing grows and nothing is random: the tree is identical on every load. Hue and thickness depend on the level: 5 px trunk in very light pink (hue 320, 90 % lightness), 0.5 px green tips (hue 140, 45 % lightness).

The canvas is cleared on every frame (transparent, so the background of .fx-0616-root shows through) and fully redrawn. Its size is read from the displayed area and multiplied by devicePixelRatio; a ResizeObserver follows it, an IntersectionObserver stops the loop off screen, and nothing starts while the area is 0 px. Every root on the page runs on its own, and only once (data-ftr-ready), even if the script is pasted twice.

Accessibility

  • Label: role="img" and aria-label on the canvas. When nearby text already says the same thing (empty state, reward card), switch it to aria-hidden="true".
  • Reduced motion: under prefers-reduced-motion: reduce, the tree is drawn once, still, at its average 0.5 radian spread.
  • Pausing: the sway lasts as long as the tree is visible. For WCAG 2.2.2, a button that toggles the root's hidden attribute is enough: the loop stops at zero size and resumes at the same phase.
  • Keyboard: the effect has no interactive element.
  • Contrast: the 45 % lightness green tips stay visible on a dark background; on white, the last 0.5 px branches become very faint.

Browser compatibility

2D canvas and about 3.4 KB of JavaScript, no dependency. ResizeObserver: Chrome 64, Firefox 69, Safari 13.1; IntersectionObserver: Chrome 51, Firefox 55, Safari 12.1 (MDN data). Measured in Chromium 151, Firefox 153 and WebKit 26.5: 2× density rendering, two independent copies, start after a delayed reveal, still tree under reduced motion, no frames computed off screen.

Chrome 64+✓ Full (measured)
Firefox 69+✓ Full (measured)
Safari 13.1+✓ Full (measured in WebKit)
Edge 79+✓ Full (Chromium engine)
Safari iOS 13.4+✓ Full (WebKit, not checked on a device)
Chrome Android 64+✓ Full (Chromium engine)

Without ResizeObserver, the tree only follows window resizes; without IntersectionObserver, it keeps swaying off screen. Without JavaScript, the area keeps the root's background with no tree.

The code

HTML structure to paste into your page (the CSS + JS come with your purchase):

index.html — structure
<div class="fx-0616-root">
  <canvas class="ftr-canvas" role="img" aria-label="Fractal tree whose branches gently spread apart and close in"></canvas>
</div>

One-time payment, no subscription — the code is yours forever

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
Depth (JS — LEVELS) 10 levels (1,023 segments) 7 gives a stylised tree with crisp branches. Branches shorter than 2 px are never drawn: beyond 10, a level is only added in a tall enough area (the 11th from 355 px high, the 12th from 506 px, up to 4,095 segments).
Trunk (JS — TRUNK) 0.2 (20 % of the height) 0.25 makes a taller tree that nearly touches the top edge; 0.15 leaves room above for a heading.
Shrink per level (JS — REDUCTION) 0.7 0.6 gives a compact crown, 0.75 long branches that spill over the sides.
Spread (JS — SPREAD) 0.5 radian 0.3 draws a narrow poplar, 0.8 a wide-open tree.
Sway (JS — SWAY, SPEED) 0.2 radian, 0.0012 radian/ms SWAY at 0.05 for a shiver, SPEED at 0.0005 for a 12 s cycle.
Colours (JS — strokeStyle line in branch()) hue 120 + level × 20, lightness 40 + level × 5 % A fixed hue (30 for instance) with the same lightness formula gives a graded autumn tree.
Background (CSS — background of .fx-0616-root) #0a0a0f The canvas is transparent: this colour is the only one that matters.
Height (CSS — --ftr-height) 300px Any fixed height, or 100% inside a parent of known height. Plan a width of at least 0.76 times the height.

FAQ

Each branch opens by the same angle left and right, with no randomness, because the tree is redrawn on every frame: a random draw would make it jitter. For an irregular yet stable tree, angles would have to be drawn once at load and stored per branch.
Not with the provided settings: LEVELS is fixed. Growth would require raising the depth or trunk length over time inside loop(). That is why the reward example reveals the tree at once instead of pretending it grows.
1,023 segments stroked one by one, a few tenths of a millisecond on a recent computer. Each extra level doubles that count. The loop stops off screen and when the root is hidden.