SPB Git

spb/svgarden Public

SVGarden — searchable bank of 74 self-contained SVG+CSS animation snippets (svgarden.dev)

HTML 79.2% Astro 10.3% JavaScript 6% CSS 3.7% Shell 0.8%

feat(snippets): add text-fx category (6)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Simon-Pierre Boucher committed 13 h ago (Aug 10, 2026) parent 7f10e8d

Showing 6 changed files with +433 and −0

added snippets/text-fx/text-knockout-video-feel.html +76 −0
@@ -0,0 +1,76 @@
1 +<!--
2 + ============================================================
3 + SVGarden — https://www.svgarden.dev
4 + Author : Simon-Pierre Boucher
5 + Contact: contact@spboucher.ai
6 + File : snippets/text-fx/text-knockout-video-feel.html
7 + Desc : Animated gradient sheet visible only through knockout letterforms
8 + ============================================================
9 +-->
10 +<!--svgarden
11 +title: Knockout gradient text
12 +slug: text-knockout-video-feel
13 +category: text-fx
14 +tags: [text, mask, knockout, gradient]
15 +difficulty: advanced
16 +techniques: [svg-mask, mask-knockout, spreadMethod-repeat, translate-property]
17 +how_it_works: >
18 + An SVG <mask> is a grayscale stencil: white areas of the mask let the masked
19 + element show through, black areas erase it. Here the mask is a black rect with
20 + white bold <text> on top, so the animated gradient sheet underneath is visible
21 + only inside the letterforms — the classic "video-in-text" knockout without any
22 + background-clip hacks. The sheet itself is a triple-width rect whose gradient
23 + uses spreadMethod="repeat" with a period of exactly 320 units, so sliding it
24 + left by one period with the modern translate property loops seamlessly forever.
25 +customizable:
26 + - { var: "--sg-color", label: "Base color", type: color, default: "#7F77DD" }
27 + - { var: "--sg-size", label: "Width", type: range, min: 160, max: 480, default: 320, unit: px }
28 + - { var: "--sg-duration", label: "Speed", type: range, min: 2, max: 12, step: 0.5, default: 5, unit: s }
29 +created: 2026-08-10
30 +-->
31 +<div class="sg-text-knockout-video-feel" style="--sg-color:#7F77DD; --sg-size:320px; --sg-duration:5s;">
32 + <svg viewBox="0 0 320 90" role="img" aria-label="SHINE">
33 + <title>SHINE</title>
34 + <defs>
35 + <linearGradient id="sg-text-knockout-video-feel-grad" x1="0" y1="0" x2="0.3333" y2="0" spreadMethod="repeat">
36 + <stop offset="0" class="sg-text-knockout-video-feel-stop-a" />
37 + <stop offset="0.33" stop-color="#3fb6f0" />
38 + <stop offset="0.66" stop-color="#e05fa0" />
39 + <stop offset="1" class="sg-text-knockout-video-feel-stop-a" />
40 + </linearGradient>
41 + <mask id="sg-text-knockout-video-feel-mask">
42 + <rect x="0" y="0" width="320" height="90" fill="black" />
43 + <text class="sg-text-knockout-video-feel-txt" x="160" y="68" fill="white">SHINE</text>
44 + </mask>
45 + </defs>
46 + <g mask="url(#sg-text-knockout-video-feel-mask)">
47 + <rect class="sg-text-knockout-video-feel-sheet" x="0" y="0" width="960" height="90"
48 + fill="url(#sg-text-knockout-video-feel-grad)" />
49 + </g>
50 + </svg>
51 +</div>
52 +<style>
53 + .sg-text-knockout-video-feel {
54 + width: var(--sg-size);
55 + }
56 + .sg-text-knockout-video-feel svg {
57 + display: block;
58 + width: 100%;
59 + }
60 + .sg-text-knockout-video-feel-txt {
61 + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
62 + font-size: 62px;
63 + font-weight: 800;
64 + letter-spacing: 2px;
65 + text-anchor: middle;
66 + }
67 + .sg-text-knockout-video-feel-stop-a {
68 + stop-color: var(--sg-color);
69 + }
70 + .sg-text-knockout-video-feel-sheet {
71 + animation: sg-text-knockout-video-feel-slide var(--sg-duration) linear infinite;
72 + }
73 + @keyframes sg-text-knockout-video-feel-slide {
74 + to { translate: -320px 0; }
75 + }
76 +</style>
added snippets/text-fx/text-liquid-fill.html +88 −0
@@ -0,0 +1,88 @@
1 +<!--
2 + ============================================================
3 + SVGarden — https://www.svgarden.dev
4 + Author : Simon-Pierre Boucher
5 + Contact: contact@spboucher.ai
6 + File : snippets/text-fx/text-liquid-fill.html
7 + Desc : Letterforms fill with rising liquid topped by a moving wave
8 + ============================================================
9 +-->
10 +<!--svgarden
11 +title: Liquid-fill text
12 +slug: text-liquid-fill
13 +category: text-fx
14 +tags: [text, clip-path, liquid, wave]
15 +difficulty: advanced
16 +techniques: [clipPath-from-text, periodic-wave-path, nested-transform-layers, translate-property]
17 +how_it_works: >
18 + A <clipPath> built from bold <text> confines everything drawn inside it to the
19 + letterforms, so the "liquid" only exists where the glyphs are. The liquid is a
20 + single path whose top edge is a periodic quadratic wave (q + t commands repeat
21 + every 130 units) extending far past the visible area and closing into a deep
22 + solid below. Two animations are deliberately split across nested groups so
23 + their transforms don't fight: the outer group rises with translateY to raise
24 + the water level, while the inner path slides horizontally by exactly one wave
25 + period, which makes the surface roll seamlessly. A faint stroked copy of the
26 + text on top shows the unfilled portion on any background.
27 +customizable:
28 + - { var: "--sg-color", label: "Liquid color", type: color, default: "#3F8FDF" }
29 + - { var: "--sg-size", label: "Width", type: range, min: 140, max: 420, default: 260, unit: px }
30 + - { var: "--sg-duration", label: "Fill time", type: range, min: 2, max: 10, step: 0.5, default: 5, unit: s }
31 +created: 2026-08-10
32 +-->
33 +<div class="sg-text-liquid-fill" style="--sg-color:#3F8FDF; --sg-size:260px; --sg-duration:5s;">
34 + <svg viewBox="0 0 260 90" role="img" aria-label="FLOW">
35 + <title>FLOW</title>
36 + <defs>
37 + <clipPath id="sg-text-liquid-fill-clip">
38 + <text class="sg-text-liquid-fill-txt" x="130" y="72">FLOW</text>
39 + </clipPath>
40 + </defs>
41 + <text class="sg-text-liquid-fill-txt sg-text-liquid-fill-ghost" x="130" y="72">FLOW</text>
42 + <g clip-path="url(#sg-text-liquid-fill-clip)">
43 + <g class="sg-text-liquid-fill-rise">
44 + <path class="sg-text-liquid-fill-wave"
45 + d="M-260 8 q32 -14 65 0 t65 0 t65 0 t65 0 t65 0 t65 0 t65 0 t65 0 V120 H-260 Z" />
46 + </g>
47 + </g>
48 + </svg>
49 +</div>
50 +<style>
51 + .sg-text-liquid-fill {
52 + width: var(--sg-size);
53 + }
54 + .sg-text-liquid-fill svg {
55 + display: block;
56 + width: 100%;
57 + }
58 + .sg-text-liquid-fill-txt {
59 + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
60 + font-size: 64px;
61 + font-weight: 800;
62 + letter-spacing: 2px;
63 + text-anchor: middle;
64 + }
65 + .sg-text-liquid-fill-ghost {
66 + fill: none;
67 + stroke: var(--sg-color);
68 + stroke-width: 1.5;
69 + opacity: 0.35;
70 + }
71 + .sg-text-liquid-fill-rise {
72 + animation: sg-text-liquid-fill-up var(--sg-duration) ease-in-out infinite;
73 + }
74 + .sg-text-liquid-fill-wave {
75 + fill: var(--sg-color);
76 + animation: sg-text-liquid-fill-roll calc(var(--sg-duration) / 3) linear infinite;
77 + }
78 + @keyframes sg-text-liquid-fill-up {
79 + 0% { transform: translateY(62px); opacity: 1; }
80 + 60% { transform: translateY(6px); }
81 + 82% { transform: translateY(6px); opacity: 1; }
82 + 94% { transform: translateY(6px); opacity: 0; }
83 + 100% { transform: translateY(62px); opacity: 0; }
84 + }
85 + @keyframes sg-text-liquid-fill-roll {
86 + to { translate: 130px 0; }
87 + }
88 +</style>
added snippets/text-fx/text-marquee-path.html +66 −0
@@ -0,0 +1,66 @@
1 +<!--
2 + ============================================================
3 + SVGarden — https://www.svgarden.dev
4 + Author : Simon-Pierre Boucher
5 + Contact: contact@spboucher.ai
6 + File : snippets/text-fx/text-marquee-path.html
7 + Desc : Infinite marquee flowing along a curved textPath
8 + ============================================================
9 +-->
10 +<!--svgarden
11 +title: Marquee on a path
12 +slug: text-marquee-path
13 +category: text-fx
14 +tags: [text, marquee, textPath, smil]
15 +difficulty: intermediate
16 +techniques: [textPath, SMIL-startOffset, monospace-periodicity, seamless-loop-math]
17 +how_it_works: >
18 + The marquee is one long string — a short phrase repeated eight times — glued
19 + to a curve with <textPath>, and a SMIL <animate> slides startOffset backward
20 + forever. The seamless-loop trick is arithmetic: in a monospace font every
21 + repetition has identical width, so shifting by exactly one repetition
22 + (here ~25% of the path) lands on a frame indistinguishable from the start.
23 + Glyphs that fall before the path's start or past its end are simply not
24 + rendered, which is what makes letters appear at one end of the curve and
25 + vanish at the other with no clipping mask needed. Tune font-size and the
26 + to="-25%" together if you change the phrase.
27 +customizable:
28 + - { var: "--sg-color", label: "Color", type: color, default: "#7F77DD" }
29 + - { var: "--sg-size", label: "Width", type: range, min: 200, max: 520, default: 380, unit: px }
30 +created: 2026-08-10
31 +-->
32 +<div class="sg-text-marquee-path" style="--sg-color:#7F77DD; --sg-size:380px;">
33 + <svg viewBox="0 0 400 100" aria-hidden="true" focusable="false">
34 + <defs>
35 + <path id="sg-text-marquee-path-rail" d="M6 62 C 105 18, 295 98, 394 50" />
36 + </defs>
37 + <use href="#sg-text-marquee-path-rail" class="sg-text-marquee-path-guide" />
38 + <text class="sg-text-marquee-path-txt">
39 + <textPath href="#sg-text-marquee-path-rail" startOffset="0%">
40 + svgarden ✦ svgarden ✦ svgarden ✦ svgarden ✦ svgarden ✦ svgarden ✦ svgarden ✦ svgarden ✦
41 + <animate attributeName="startOffset" from="0%" to="-25%" dur="5s" repeatCount="indefinite" />
42 + </textPath>
43 + </text>
44 + </svg>
45 +</div>
46 +<style>
47 + .sg-text-marquee-path {
48 + width: var(--sg-size);
49 + }
50 + .sg-text-marquee-path svg {
51 + display: block;
52 + width: 100%;
53 + }
54 + .sg-text-marquee-path-guide {
55 + fill: none;
56 + stroke: rgba(128, 128, 128, 0.3);
57 + stroke-width: 1;
58 + stroke-dasharray: 3 5;
59 + }
60 + .sg-text-marquee-path-txt {
61 + font-family: ui-monospace, 'SF Mono', SFMono-Regular, Menlo, Consolas, monospace;
62 + font-size: 15px;
63 + font-weight: 600;
64 + fill: var(--sg-color);
65 + }
66 +</style>
added snippets/text-fx/text-scramble-decode.html +67 −0
@@ -0,0 +1,67 @@
1 +<!--
2 + ============================================================
3 + SVGarden — https://www.svgarden.dev
4 + Author : Simon-Pierre Boucher
5 + Contact: contact@spboucher.ai
6 + File : snippets/text-fx/text-scramble-decode.html
7 + Desc : Letters cycle through random glyphs, then lock into place
8 + ============================================================
9 +-->
10 +<!--svgarden
11 +title: Scramble decode
12 +slug: text-scramble-decode
13 +category: text-fx
14 +tags: [text, scramble, decode, js]
15 +difficulty: advanced
16 +techniques: [minimal-js, setInterval-render-loop, prefers-reduced-motion, monospace-stability]
17 +how_it_works: >
18 + A single interval re-renders the string every 40 ms: characters left of a
19 + "locked" cursor show the real message, everything to the right is drawn fresh
20 + from a pool of glitch glyphs, and the cursor advances a fraction of a letter
21 + per frame so the decode sweeps left to right. The font must be monospace —
22 + with proportional glyphs the line would jitter horizontally on every frame.
23 + The script checks prefers-reduced-motion and, when set, skips the scramble
24 + entirely so motion-sensitive visitors just see the final text; the markup
25 + also contains the real sentence, so nothing breaks with JavaScript disabled.
26 +customizable:
27 + - { var: "--sg-color", label: "Color", type: color, default: "#7F77DD" }
28 + - { var: "--sg-size", label: "Font size", type: range, min: 12, max: 40, default: 22, unit: px }
29 +created: 2026-08-10
30 +-->
31 +<div class="sg-text-scramble-decode" style="--sg-color:#7F77DD; --sg-size:22px;">
32 + <span class="sg-text-scramble-decode-txt">ACCESS GRANTED_</span>
33 +</div>
34 +<script>
35 + document.querySelectorAll('.sg-text-scramble-decode:not([data-sg-init])').forEach(function (root) {
36 + root.setAttribute('data-sg-init', '');
37 + var el = root.querySelector('.sg-text-scramble-decode-txt');
38 + var goal = el.textContent;
39 + var pool = '!<>-_\\/[]{}=+*^?#@$%&';
40 + if (matchMedia('(prefers-reduced-motion: reduce)').matches) return;
41 + (function play() {
42 + var locked = 0;
43 + var timer = setInterval(function () {
44 + var out = goal.slice(0, Math.floor(locked));
45 + for (var i = out.length; i < goal.length; i++) {
46 + out += goal[i] === ' ' ? ' ' : pool[(Math.random() * pool.length) | 0];
47 + }
48 + el.textContent = out;
49 + locked += 0.4;
50 + if (locked >= goal.length + 1) {
51 + clearInterval(timer);
52 + el.textContent = goal;
53 + setTimeout(play, 2400);
54 + }
55 + }, 40);
56 + })();
57 + });
58 +</script>
59 +<style>
60 + .sg-text-scramble-decode {
61 + font-family: ui-monospace, 'SF Mono', SFMono-Regular, Menlo, Consolas, monospace;
62 + font-size: var(--sg-size);
63 + font-weight: 600;
64 + color: var(--sg-color);
65 + letter-spacing: 0.06em;
66 + }
67 +</style>
added snippets/text-fx/text-shadow-3d-tilt.html +68 −0
@@ -0,0 +1,68 @@
1 +<!--
2 + ============================================================
3 + SVGarden — https://www.svgarden.dev
4 + Author : Simon-Pierre Boucher
5 + Contact: contact@spboucher.ai
6 + File : snippets/text-fx/text-shadow-3d-tilt.html
7 + Desc : Extruded pseudo-3D text that tilts in perspective on hover
8 + ============================================================
9 +-->
10 +<!--svgarden
11 +title: 3D extruded tilt
12 +slug: text-shadow-3d-tilt
13 +category: text-fx
14 +tags: [text, 3d, perspective, hover]
15 +difficulty: intermediate
16 +techniques: [perspective, rotateX-rotateY, --sg-i-index-stagger, grid-area-stacking, brightness-derived-shading]
17 +how_it_works: >
18 + The extrusion is six copies of the same word stacked in one grid cell — every
19 + layer reads its depth index from a --sg-i variable and computes its own offset
20 + with translate: calc(var(--sg-i) * var(--sg-depth)), so one rule places the
21 + whole stack and the depth becomes a single tunable knob. Shading needs no
22 + second color: filter: brightness() derived from the same index darkens deeper
23 + layers progressively, which is what sells the solid-block illusion. Hovering
24 + (or keyboard-focusing) tips the stack with rotateX/rotateY inside a
25 + perspective-carrying wrapper, and because the offsets survive the rotation the
26 + extrusion appears genuinely volumetric.
27 +customizable:
28 + - { var: "--sg-color", label: "Color", type: color, default: "#7F77DD" }
29 + - { var: "--sg-size", label: "Font size", type: range, min: 28, max: 90, default: 54, unit: px }
30 + - { var: "--sg-depth", label: "Depth", type: range, min: 0.5, max: 3, step: 0.1, default: 1.4, unit: px }
31 +created: 2026-08-10
32 +-->
33 +<div class="sg-text-shadow-3d-tilt" tabindex="0" aria-label="DEPTH" style="--sg-color:#7F77DD; --sg-size:54px; --sg-depth:1.4px;">
34 + <span class="sg-text-shadow-3d-tilt-stack">
35 + <span class="sg-text-shadow-3d-tilt-layer" style="--sg-i:5" aria-hidden="true">DEPTH</span>
36 + <span class="sg-text-shadow-3d-tilt-layer" style="--sg-i:4" aria-hidden="true">DEPTH</span>
37 + <span class="sg-text-shadow-3d-tilt-layer" style="--sg-i:3" aria-hidden="true">DEPTH</span>
38 + <span class="sg-text-shadow-3d-tilt-layer" style="--sg-i:2" aria-hidden="true">DEPTH</span>
39 + <span class="sg-text-shadow-3d-tilt-layer" style="--sg-i:1" aria-hidden="true">DEPTH</span>
40 + <span class="sg-text-shadow-3d-tilt-layer" style="--sg-i:0" aria-hidden="true">DEPTH</span>
41 + </span>
42 +</div>
43 +<style>
44 + .sg-text-shadow-3d-tilt {
45 + display: inline-block;
46 + perspective: 550px;
47 + cursor: default;
48 + }
49 + .sg-text-shadow-3d-tilt-stack {
50 + display: grid;
51 + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
52 + font-size: var(--sg-size);
53 + font-weight: 800;
54 + letter-spacing: 0.03em;
55 + line-height: 1.1;
56 + transition: transform 0.45s ease;
57 + }
58 + .sg-text-shadow-3d-tilt-layer {
59 + grid-area: 1 / 1;
60 + color: var(--sg-color);
61 + translate: calc(var(--sg-i) * var(--sg-depth)) calc(var(--sg-i) * var(--sg-depth));
62 + filter: brightness(calc(1 - var(--sg-i) * 0.13));
63 + }
64 + .sg-text-shadow-3d-tilt:hover .sg-text-shadow-3d-tilt-stack,
65 + .sg-text-shadow-3d-tilt:focus-visible .sg-text-shadow-3d-tilt-stack {
66 + transform: rotateX(18deg) rotateY(-14deg);
67 + }
68 +</style>
added snippets/text-fx/text-variable-weight-wave.html +68 −0
@@ -0,0 +1,68 @@
1 +<!--
2 + ============================================================
3 + SVGarden — https://www.svgarden.dev
4 + Author : Simon-Pierre Boucher
5 + Contact: contact@spboucher.ai
6 + File : snippets/text-fx/text-variable-weight-wave.html
7 + Desc : A wave of font-weight rolling across letters via @property
8 + ============================================================
9 +-->
10 +<!--svgarden
11 +title: Variable-weight wave
12 +slug: text-variable-weight-wave
13 +category: text-fx
14 +tags: [text, variable-font, property, stagger]
15 +difficulty: advanced
16 +techniques: ["@property", font-variation-settings, --sg-i-index-stagger, "@supports"]
17 +how_it_works: >
18 + Custom properties normally interpolate as raw strings, so animating one just
19 + snaps between keyframes. Registering it with @property and syntax:'<number>'
20 + tells the engine it is a real number, making it tween smoothly — and here that
21 + animated number is piped straight into font-variation-settings "wght", so the
22 + glyph skeleton itself thickens and relaxes. Each letter carries its position
23 + in a --sg-i variable, and a negative animation-delay computed from it phases
24 + the shared keyframe into a traveling wave. Inside @supports the effect is
25 + gated: browsers without @property (or without a variable system font) simply
26 + show the text at a steady weight.
27 +support: >
28 + Needs @property and font-variation-settings (Chrome 85+, Safari 16.4+,
29 + Firefox 128+) plus a variable system font such as SF Pro or Segoe UI
30 + Variable; elsewhere the text renders at a static weight.
31 +customizable:
32 + - { var: "--sg-color", label: "Color", type: color, default: "#7F77DD" }
33 + - { var: "--sg-size", label: "Font size", type: range, min: 20, max: 72, default: 42, unit: px }
34 + - { var: "--sg-duration", label: "Speed", type: range, min: 0.8, max: 5, step: 0.1, default: 2, unit: s }
35 +created: 2026-08-10
36 +-->
37 +<div class="sg-text-variable-weight-wave" aria-label="flexible" style="--sg-color:#7F77DD; --sg-size:42px; --sg-duration:2s;">
38 + <span class="sg-text-variable-weight-wave-ch" style="--sg-i:0" aria-hidden="true">f</span><span class="sg-text-variable-weight-wave-ch" style="--sg-i:1" aria-hidden="true">l</span><span class="sg-text-variable-weight-wave-ch" style="--sg-i:2" aria-hidden="true">e</span><span class="sg-text-variable-weight-wave-ch" style="--sg-i:3" aria-hidden="true">x</span><span class="sg-text-variable-weight-wave-ch" style="--sg-i:4" aria-hidden="true">i</span><span class="sg-text-variable-weight-wave-ch" style="--sg-i:5" aria-hidden="true">b</span><span class="sg-text-variable-weight-wave-ch" style="--sg-i:6" aria-hidden="true">l</span><span class="sg-text-variable-weight-wave-ch" style="--sg-i:7" aria-hidden="true">e</span>
39 +</div>
40 +<style>
41 + @property --sg-tvww-wgt {
42 + syntax: '<number>';
43 + inherits: false;
44 + initial-value: 400;
45 + }
46 + .sg-text-variable-weight-wave {
47 + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI Variable Text', 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
48 + font-size: var(--sg-size);
49 + color: var(--sg-color);
50 + letter-spacing: 0.02em;
51 + display: inline-flex;
52 + }
53 + .sg-text-variable-weight-wave-ch {
54 + font-weight: 550;
55 + }
56 + @supports (font-variation-settings: "wght" 500) {
57 + .sg-text-variable-weight-wave-ch {
58 + animation: sg-text-variable-weight-wave-roll var(--sg-duration) ease-in-out infinite;
59 + animation-delay: calc(var(--sg-i) * var(--sg-duration) / -8);
60 + font-variation-settings: "wght" var(--sg-tvww-wgt);
61 + font-weight: normal;
62 + }
63 + }
64 + @keyframes sg-text-variable-weight-wave-roll {
65 + 0%, 100% { --sg-tvww-wgt: 260; }
66 + 50% { --sg-tvww-wgt: 840; }
67 + }
68 +</style>
69