from fastcore.meta import delegates from fastcore.utils import snake2camel from fasthtml.common import * from fasthtml.svg import *
audrey.feldroy.com
The experimental notebooks of Audrey M. Roy Greenfeld. This website and all its notebooks are open-source at github.com/audreyfeldroy/audrey.feldroy.com
# SVG Animations in FastHTML
by Audrey M. Roy Greenfeld | Tue, Jan 21, 2025
Exploring how to make basic SVG animations work with FastHTML.
FastHTML SVG Docs Examples
The FastHTML SVG API Docs introduce you to fasthtml.svg
with this nice circle specified as an SVG string:
svg = '<svg width="50" height="50"><circle cx="20" cy="20" r="15" fill="red"></circle></svg>' show(NotStr(svg))
Often you'll just want to paste these strings into your FastHTML apps, and that's fine. However, when you want to construct SVG elements programmatically via Python, you can!
def demo(el, h=50, w=50): return show(Svg(h=h,w=w)(el))
demo(Rect(30, 30, fill='blue', rx=8, ry=8))
MDN Example
svg = """<svg viewBox="0 0 10 10" xmlns="http://www.w3.org/2000/svg"> <rect width="10" height="10"> <animate attributeName="rx" values="0;5;0" dur="10s" repeatCount="indefinite" /> </rect> </svg> """
show(NotStr(svg))
Why is that rectangle so big here? Let's try
svg2 = """<svg width="100" height="100"> <rect width="10" height="10"> <animate attributeName="rx" values="0;5;0" dur="10s" repeatCount="indefinite" /> </rect> </svg> """ show(NotStr(svg2))
MDN Example in FastHTML
Currently Rect()
doesn't accept an animate
child. Seeing if I can make that work:
def Animate(attributeName, values, dur, repeatCount): return Safe(f"<animate {attributeName=} {values=} {dur=} {repeatCount=} />")
Animate(attributeName="rx", values="0;5;0", dur="10s", repeatCount="indefinite")
@delegates(ft_svg) def AnimatedRect(animate, width, height, x=0, y=0, fill=None, stroke=None, stroke_width=None, rx=None, ry=None, **kwargs): "An animated standard SVG `rect` element" return ft_svg('rect', animate, width=width, height=height, x=x, y=y, fill=fill, stroke=stroke, stroke_width=stroke_width, rx=rx, ry=ry, **kwargs)
show(Svg(AnimatedRect( Animate(attributeName="rx", values="0;5;0", dur="10s", repeatCount="indefinite"), width=10, height=10), h=10, w=10))
show(Svg(AnimatedRect( Animate(attributeName="rx", values="0;50;0", dur="10s", repeatCount="indefinite"), width=100, height=100), h=100, w=100))
demo(AnimatedRect( Animate(attributeName="rx", values="0;50;0", dur="1s", repeatCount="indefinite"), width=100, height=100), h=100, w=100)
More Complex SVG Animation
svg4 = """<svg width="100" height="100" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"> <!-- Gradient definitions --> <defs> <linearGradient id="gradient1" x1="0%" y1="0%" x2="100%" y2="100%"> <stop offset="0%" stop-color="#60a5fa"> <animate attributeName="stop-color" values="#60a5fa;#8b5cf6;#ec4899;#60a5fa" dur="8s" repeatCount="indefinite" /> </stop> <stop offset="100%" stop-color="#8b5cf6"> <animate attributeName="stop-color" values="#8b5cf6;#ec4899;#60a5fa;#8b5cf6" dur="8s" repeatCount="indefinite" /> </stop> </linearGradient> </defs> <!-- Background star burst --> <g> <circle cx="50" cy="50" r="45" fill="url(#gradient1)"> <animate attributeName="opacity" values="0.3;0.5;0.3" dur="3s" repeatCount="indefinite" /> </circle> </g> <!-- Spinning triangles --> <g transform="translate(50 50)"> <path d="M0,-30 L26,15 L-26,15 Z" fill="#fcd34d" opacity="0.8"> <animateTransform attributeName="transform" type="rotate" from="0" to="360" dur="8s" repeatCount="indefinite" /> <animate attributeName="d" values="M0,-30 L26,15 L-26,15 Z;M0,-20 L35,25 L-35,25 Z;M0,-30 L26,15 L-26,15 Z" dur="4s" repeatCount="indefinite" /> </path> </g> <!-- Bouncing squares --> <rect x="40" y="40" width="20" height="20" fill="#34d399" opacity="0.8"> <animate attributeName="x" values="40;30;50;40" dur="3s" repeatCount="indefinite" /> <animate attributeName="y" values="40;50;30;40" dur="3s" repeatCount="indefinite" /> <animate attributeName="rx" values="0;10;0" dur="3s" repeatCount="indefinite" /> </rect> <!-- Orbiting particles --> <g> <circle cx="50" cy="20" r="4" fill="#f472b6"> <animateMotion path="M 0,0 a 30,30 0 1,1 0,0" dur="3s" repeatCount="indefinite" /> <animate attributeName="r" values="4;6;4" dur="1.5s" repeatCount="indefinite" /> </circle> <circle cx="80" cy="50" r="4" fill="#60a5fa"> <animateMotion path="M 0,0 a 30,30 0 1,0 0,0" dur="4s" repeatCount="indefinite" /> <animate attributeName="r" values="4;6;4" dur="2s" repeatCount="indefinite" /> </circle> </g> <!-- Dancing dots --> <g> <circle cx="50" cy="50" r="3" fill="#fcd34d"> <animateMotion path="M 0,0 q 15,15 0,30 q -15,15 0,0" dur="2.5s" repeatCount="indefinite" /> </circle> <circle cx="50" cy="50" r="3" fill="#f472b6"> <animateMotion path="M 0,0 q -15,-15 -30,0 q -15,15 0,0" dur="2.5s" repeatCount="indefinite" /> </circle> </g> <!-- Pulsing rings --> <circle cx="50" cy="50" r="20" fill="none" stroke="#93c5fd" stroke-width="1"> <animate attributeName="r" values="20;30;20" dur="4s" repeatCount="indefinite" /> <animate attributeName="stroke-opacity" values="1;0;1" dur="4s" repeatCount="indefinite" /> </circle> <circle cx="50" cy="50" r="25" fill="none" stroke="#93c5fd" stroke-width="1"> <animate attributeName="r" values="25;35;25" dur="4s" repeatCount="indefinite" /> <animate attributeName="stroke-opacity" values="0;1;0" dur="4s" repeatCount="indefinite" /> </circle> </svg>""" show(NotStr(svg4))
I kind of like this one for representing "a sound is currently playing" for experimental audio apps.
© 2024-2025 Audrey M. Roy Greenfeld