DEV Community

Cover image for Making a text art and SVG circle picker design tool in 34 lines of pure HTML web component code + 1 extra file (no node or JS!)
michaelb
michaelb

Posted on • Updated on

Making a text art and SVG circle picker design tool in 34 lines of pure HTML web component code + 1 extra file (no node or JS!)

Hey all, I'm back to writing my Modulo.js tutorial series, and am always looking for more content ideas. If you want to see a certain type of tutorial in the future, be sure let me know in the comments -- and follow, if you haven't already!

In my previous tutorials, I showed how to use the single-file, 2000-line, small-but-mighty Modulo.js framework to make a gradient picker component in 13 lines, quickly embed APIs, or even how to build an animated text-shadow "wave" effect text picker. This time, we'll be making something like the last one, except instead of using pure HTML and CSS, we'll be employing the full power of SVG. This lets us really get wild with our text art designer tool! Ready for some creative and exciting shapes in SVG Text Art?

The final result

Screenshot showing design tool with several inputs (offset, border, radius, fill color, and border color) and the words Modulo.js Text FX in a circle around with lavendar and mint colors

Try it out now, in less than 30 seconds: 🚀🚀🚀 Want to skip ahead? Scroll to the end and copy the ~40 lines of HTML code into any local HTML file, and then open it in *Firefox. Modulo has no dependencies and even runs embedded in local HTML files, so it's really that easy!


Template and SVG

Including SVG in Modulo is as simple as adding it to a Template:

<Template>
  <svg viewBox="0 0 600 600" style="width: 600px; float: right">
    <circle id="circle" cx="60" cy="100" r="40"
      stroke="{{ state.stroke }}" fill="{{ state.color }}"
      stroke-width="10" />
    <text width="2000">
        <textPath
            xlink:href="#circle"
            startOffset="10"
        >Modulo.js: Text FX!</textPath>
    </text>
  </svg>
</Template>
<State
    color="#B90183"
    stroke="#A2E4B8"
></State>
Enter fullscreen mode Exit fullscreen mode

In this snippet, we build a 600x600 SVG image (<svg viewBox="0 0 600 600" ...>), put a 4-radius circle in it at 60x100, and use state.stroke and state.color state variables to assign it's color scheme. We then add a text element that "follows" the shape of the circle referring to it by it's ID: <textPath xlink:href="#circle">.

Binding color inputs

We have our colors in our state, but the user has no way of adjusting them. Let's add to our Template two bound color inputs:

<label><input [state.bind] name="color" type="color"> Fill Color</label><br/>
<label><input [state.bind] name="stroke" type="color"> Border Color</label><br/>
Enter fullscreen mode Exit fullscreen mode

The [state.bind] name="..." binds each of these to those state variables, so the component will rerender with the new state when a new color is picked. If you are new to Modulo.js, and state and binding is confusing you, consider warming up with this tutorial on creating a font design tool, or the Modulo JS - Ramping Up Part 2: State. If SVG <textPath> elements are confusing you, check out MDN's documentation.

Finishing up the other inputs: Offset, Border, Radius, and Text

Start with three new state variables, like the initial color and stroke ones, except using the data-prop := syntax for the numbers:

<State
    text="Modulo.js: Text FX!"
    radius:=40
    width:=20
    offset:=20
    color="#B90183"
    stroke="#A2E4B8"
></State>
Enter fullscreen mode Exit fullscreen mode

And, just like before, add in inputs:

<input [state.bind] name="text" />
<input [state.bind]="input" name="offset" type="range" min="0" max="300" />
<input [state.bind]="input" name="width" type="range" min="0" max="40" />
<input [state.bind]="input" name="radius" type="range" min="10" max="80" />
Enter fullscreen mode Exit fullscreen mode

The first input controls the text (default type), and the other three input tags with [state.bind]="input" control numbers using type="range" (the ="input" tweak explained in this tutorial).

Adding in the new state variables back to the SVG

Let's go back to the SVG to assign the new state variables to cx=, cy=, r=, and stroke-width= attributes on the <circle>, and the startOffset and content of the <textPath> itself:

<circle id="circle"
    cx="{{ state.radius|add:20 }}" cy="100"
    r="{{ state.radius }}"
    stroke="{{ state.stroke }}" fill="{{ state.color }}"
    stroke-width="{{ state.width }}" />
<text width="2000">
    <textPath xlink:href="#circle" startOffset="{{ state.offset }}">
       {{ state.text }}
    </textPath>
</text>
Enter fullscreen mode Exit fullscreen mode

Note the |add:20 for the cx=: This |add filter gives an offset of 20 to the circle so it doesn't overflow on the left.

Firefox Footnotes

This tutorial requires the SVG feature of circular text, which Chrome hasn't yet implemented. My next tutorial will talk about SVG paths, which is supported by Chrome. The images generated can still be used cross-platform, however.

<x-TextArtCircleTool> - Embeddable results

Combine everything, and we get the following results. Hope you enjoy this tutorial -- follow for more like this!

<!DOCTYPE html>
<template Modulo>
  <Component name="TextArtCircleTool">
    <Template>
      <svg viewBox="0 0 600 600" style="width: 600px; float: right">
          <circle id="circle"
            cx="{{ state.radius|add:20 }}" cy="100"
            r="{{ state.radius }}"
            stroke="{{ state.stroke }}" fill="{{ state.color }}"
            stroke-width="{{ state.width }}" />
          <text width="2000">
              <textPath
                  xlink:href="#circle"
                  startOffset="{{ state.offset }}"
              >{{ state.text }}</textPath>
          </text>
      </svg>
      <label><input [state.bind] name="text" /></label><br/>
      <label><input [state.bind]="input" name="offset"
        type="range" min="0" max="300" /> Offset</label><br/>   
      <label><input [state.bind]="input" name="width"
        type="range" min="0" max="40" /> Border</label><br/>
      <label><input [state.bind]="input" name="radius"
        type="range" min="10" max="80" /> Radius</label><br/>
      <label><input [state.bind] name="color" type="color"> Fill Color</label><br/>
      <label><input [state.bind] name="stroke" type="color"> Border Color</label><br/>
    </Template>
    <State
        text="Modulo.js: Text FX!"
        radius:=40
        width:=20
        offset:=20
        color="#B90183"
        stroke="#A2E4B8"
    ></State>
  </Component>
</template>
<script src="https://unpkg.com/mdu.js"></script>
<x-TextArtCircleTool></x-TextArtCircleTool>
Enter fullscreen mode Exit fullscreen mode

Top comments (0)