Text along circle path SVG + JS

Let’s say you have some text in your HTML that you want to wrap around a circle creating some stamp like circular text.

Image 1 – Simple inline text » Text following a circle path

TLDR;

Though this can be easily done in any vector graphics editing software, Sketch, Figma, Adobe XD, Adobe Illustrator or others, usually this converts characters to paths and upon export you cannot change your text (maybe Adobe Illustrator treats this correctly, I have no idea 🤷‍♂️, I’m not a designer), but even if that wasn’t the case you would be stuck with a circle path of fixed dimensions.

Let’s say that at this point you don’t know what will the actual text be and you want to be able to fit any text, from start to end.

To devise this solution we would need a template svg containing a circular path and a bit of JavaScript to do the calculations:

  • You start here:
<div class="circleize">
Through the depths of infinity &infin;&nbsp;
</div>

<style>
.circleize {
  font-size: 34px;
  color: orange;
}

/* adding this style will ensure that the text within SVG keeps the current color */
.circleize svg text {
  fill: currentColor;
}
</style>
  • The length of the text is actually the circle’s circumference, and the text’s line height is the padding from the viewport.

    You would want to add few css rules to your text to get exact measurements: white-space: nowrap – so the text wouldn’t wrap to the new line and display: inline-block – making sure that you will get the width of the text itself and not of the block container which spans across it’s parent’s width.
function calcTextDimensions (element) {
  var inner = element.querySelector('span');
  var text_content = element.innerText;
  
  if (!inner) {
  	var inner = document.createElement('span');
    inner.setAttribute('style', 'display: inline-block; white-space: nowrap;')
   	inner.append(text_content);
    element.innerHTML = '';
    element.appendChild(inner);
  }
  
  return {
  	height: inner.offsetHeight,
    width: inner.offsetWidth,
    text_content: text_content
  }
}
  • When you have circumference you can easily calculate the radius ( r = C / 2π ) of your circle. Using the text height as padding, you can now calculate the exact dimensions of the SVG viewport (side = 2*r + 2*padding)
function calcSVGDimensions(text_dimensions) {
	var C = text_dimensions.width;
  var r = C/(2 * Math.PI);
  var padding = text_dimensions.height;
  var side = 2*r + 2*padding;
  
  return {
  	r: r,
    padding: padding,
    side: side
  }
}
  • Next you will need a SVG template which you will fill with calculated data.

    You can get this template just by exporting a circle from a vector design tool. But regardless here it is:
<svg xmlns="http://www.w3.org/2000/svg" 
     xmlns:xlink="http://www.w3.org/1999/xlink" 
     width="{{side}}" height="{{side}}" 
     viewBox="0 0 {{side}} {{side}}">
     
     <path id="path_text{{id}}" 
           transform="translate({{padding}} {{padding}})" 
           data-name="path_text{{id}}" 
           d="M{{r}},0A{{r}},{{r}},0,1,1,0,{{r}},{{r}},{{r}},0,0,1,{{r}},0Z" 
           fill="none" />
     <text dy="0">
       <textPath xlink:href="#path_text{{id}}">{{text}}</textPath>
      </text>
</svg>
  • This templating example uses a simple standard replace with a dash of RegExp.

    Though this could be done in a more performant way, it will serve for this example. Assigning unique IDs is a good idea, because you never know if you will want to have multiple instances in the same DOM scope.
function buildSVG(text, svg_dimensions, id) {
	if (!id) {
  	id = '';
  }
  
	var template = '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="{{side}}" height="{{side}}" viewBox="0 0 {{side}} {{side}}"><path id="path_text{{id}}" transform="translate({{padding}} {{padding}})" data-name="path_text{{id}}" d="M{{r}},0A{{r}},{{r}},0,1,1,0,{{r}},{{r}},{{r}},0,0,1,{{r}},0Z" fill="none" /><text dy="0"><textPath xlink:href="#path_text{{id}}">{{text}}</textPath></text></svg>';
  
  template = template.replace(/\{\{side\}\}/gm, svg_dimensions.side);
  template = template.replace(/\{\{r\}\}/gm, svg_dimensions.r);
  template = template.replace(/\{\{padding\}\}/gm, svg_dimensions.padding);
  template = template.replace(/\{\{text\}\}/gm, text);
  template = template.replace(/\{\{id\}\}/gm, id);
  
  return template;
}
  • Lastly, one function to connect previously mentioned ones. Pretty self explanatory.
function circleize(selector) {
	var elements = document.querySelectorAll(selector);
  
  for	(var i = 0; i < elements.length; i++) {
  	var element = elements[i];
  	var text_dimensions = calcTextDimensions(element);
    var svg_dimensions = calcSVGDimensions(text_dimensions);
    var template = buildSVG(text_dimensions.text_content, svg_dimensions, i);
    element.innerHTML = template;
  }
}

circleize('.circleize'); //and of course run it whenever is appropriate

Hope you found this useful!
Cheers! 🤗

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: