use-svg-maker or Creating SVGs with React: A Tutorial
SVGs are XML descriptions of vector graphics. React is a highly efficient, well-supported way to create XML-ish things. Indeed, while dynamic, it is often used for purely static content too. So, what if instead of clicking your mouse a lot in Figma, you used React code to create SVGs? That is what use-svg-maker is for. It takes care of the tedious parts (of which there actually aren't that many) to allow you to rapidly create and save SVGs. We will create something like this:
Let's create a basic setup in CodeSandbox:
We've added the library, Tailwind for CSS, and a helper for parameterizing the SVGs. Now, we'd better draw something. If you open the sandbox above, you can code along easily with this tutorial. Open App.tsx
and look for
<svg className=" bg-white" {...props} ref={ref}></svg>
This is just an SVG element. We also have access to some meta
data from the hook. And we've very easily created a button to save the SVG. But we'd better draw something. Let's start with a circle in the middle.
<svg className=" bg-white" {...props} ref={ref}>
<circle cx={meta.center.x} cy={meta.center.y} r={100} />
</svg>
This is a circle element in SVG. It has all sorts of concise props for declaring shapes. By default, the fill
is black.
Okay, that is boring and harder than Figma. Let's draw 50 circles with fancy opacity variation. That would not be easy in Figma. And let's also add a background via a rect
.
<svg className=" bg-white" {...props} ref={ref}>
<rect
x={0}
y={0}
width={meta.right}
height={meta.bottom}
fill={hsla(210, 90, 20)}
/>
{[...Array(50).keys()].map((i) => (
<circle
key={i}
r={meta.bottom / 10}
cx={meta.center.x + meta.right * 0.25 * Math.cos((Math.PI * i) / 25)}
cy={meta.center.y + meta.right * 0.25 * Math.sin((Math.PI * i) / 25)}
fill={hsla(130 + i, 90, 70, 0.4)}
/>
))}
</svg>
Let's make it interactive. We will use leva
, it has a wonderfully quick and concise (and typesafe) API for letting the user pick values: useControls
. In just a few lines of code we can set up a way to explore parameters:
const { mainHue, background } = useControls({
mainHue: {
value: 130,
min: 0,
max: 360,
},
background: "#3366aa",
})
Let's go a bit further. Playing around with the leva
controls, we might decide that 330
is a good base hue and that #141f2e
works for the background.
Let's do something more interesting based on that. use-svg-maker
comes with helpers for noise and pseudorandomness. Let's add some noise to the circles' positions. Add a new controlled parameter:
const { mainHue, background, randomness } = useControls({
mainHue: {
value: 330,
min: 0,
max: 360,
},
background: "#141f2e",
randomness: {
value: 0.3,
min: 0,
max: 1,
},
})
And wire up like:
{
[...Array(50).keys()].map((i) => (
<circle
key={i}
r={meta.bottom / 10}
cx={
meta.center.x +
meta.right * 0.25 * Math.cos((Math.PI * i) / 25) +
meta.right * randomness * perlin2(i, 0.23424)
}
cy={meta.center.y + meta.right * 0.25 * Math.sin((Math.PI * i) / 25)}
fill={hsla(mainHue + i, 90, 70, 0.4)}
/>
))
}
Abstraction
Much of the code we looked at was pretty repetitive and verbose. But remember, we are using React. When we have figured out something we want to work with, it is as easy as it would be for a web app to create a component or hook with the logic. For example, here is a small but non-trivial component from our work:
function Circle({
i,
meta,
randomness,
mainHue,
}: {
i: number
randomness: number
mainHue: number
meta: ReturnType<typeof useSVG>["meta"]
}) {
return (
<circle
key={i}
r={meta.bottom / 10}
cx={
meta.center.x +
meta.right * 0.25 * Math.cos((Math.PI * i) / 25) +
meta.right * randomness * perlin2(i, 0.23424)
}
cy={meta.center.y + meta.right * 0.25 * Math.sin((Math.PI * i) / 25)}
fill={hsla(mainHue + i, 90, 70, 0.4)}
/>
)
}
With React components, we can encapsulate and reuse code. With just a tiny amount more work, we can create this configurable scene with a reusable component. We pull in use-svg-maker
's built-in pseudorandom number generator for another effect. This also includes a parameterised blur filter, one of SVG's more advanced effects.