Pedro Ludovico Bozzini
Pedro Ludovico Bozzini

Reputation: 412

Add text to SVG path dynamically

I have an SVG exported from Adobe Illustrator with several paths like this, which produces a small polygon I intend to use as a text box

<svg viewbox="387 390 74 20">
    <g>
        <path class="st37" d="M452,408h-56c-4.42,0-8-3.58-8-8l0,0c0-4.42,3.58-8,8-8h56c4.42,0,8,3.58,8,8l0,0     C460,404.42,456.42,408,452,408z" />
    </g>
</svg>

I'd like to dynamically add text to it. I've seen many similar questions here, but most of them involed specifying a x and y property for a text element based on the x and y property the path element. My path, however, does not have such properties.

I've tried to use a textPath element with xlink:href pointing to my path. I gets attached to the path, but the text wraps my path element instead of being inside it.

So, is there a way to achieve this? I'm open to different solutions here. My dream would be to use javascript to get the path element and add the text from there. But I could also edit the base SVG file to add a text or any other relevant element and attributes to make this work, as long as I can change the text dynamically from javascript later. And since this SVG is produced by Illustrator, I could also try different export options there in order to get a proper output for my goal.

Upvotes: 7

Views: 8753

Answers (3)

Since you can edit your base SVG align

  • create a proper SVG to work with

    • Your path is a background label starting (red circle) at a large offset x=452 y=408
    • I would recreate it,
      starting at the green circle, (editor: https://yqnn.github.io/svg-path-editor/)
      in a viewBox="0 0 80 20"
    • To get single coordinates for both backgroundlabel and (blue) textPath line
  • after that use JavaScript to add text dynamically.

    • No need for text x,y calculations, pathLength and startoffset do the work

    • Or if you go fancy you can create the blue line dynamically from getBBox()
      That will also work with your current path; just more calculations required

  • It is all about coordinates,
    and positioning the blue line (with stroke="transparent" then):

playground:

<svg viewbox="387 390 74 20">
  <path fill="lightgreen" d="M452,408h-56c-4.42,0-8-3.58-8-8l0,0c0-4.42,3.58-8,8-8h56c4.42,0,8,3.58,8,8l0,0C460,404.42,456.42,408,452,408z" />
 
  <circle cx="452" cy="408" r="2" fill="red"/>
  <circle cx="388" cy="400" r="2" fill="green"/>
  
  <path id="P" pathLength="100" d="M388 400h72" stroke="blue"/>
  <text>
    <textPath href="#P" startoffset="50" text-anchor="middle" dominant-baseline="middle"
              fill="green" font-size="14px">My Text</textPath>
  </text>
</svg>

Upvotes: 3

Pedro Ludovico Bozzini
Pedro Ludovico Bozzini

Reputation: 412

Thanks for the answers! I end up using a tweaked version of Paul LeBeau's function to take into account the structure suggested by Danny '365CSI' Engelman so I don't have to use translate to center the text vertically.

let label = document.querySelector("#mylabel");

addLabelTextPath(label, "Something");

function addLabelTextPath(bgPath, labelText) {

  let bbox = bgPath.getBBox();
  let x = bbox.x + bbox.width / 2;
  let y = bbox.y + bbox.height / 2;

  // Create the path line
  let pathElem = document.createElementNS(bgPath.namespaceURI, "path");
  pathElem.setAttribute("pathLength", 100);
  pathElem.setAttribute("d", `M${bbox.x} ${y}h${bbox.width}`);
  pathElem.id = `baseline-${bgPath.id}`;

  // Create a <text> element
  let textElem = document.createElementNS(bgPath.namespaceURI, "text");

  // Create a <textPath> element
  let textPath = document.createElementNS(bgPath.namespaceURI, "textPath");
  textPath.setAttribute("href", `#${pathElem.id}`);
  //Center text
  textPath.setAttribute("dominant-baseline", "Middle");
  textPath.setAttribute("startOffset", 50);
  textPath.setAttribute("text-anchor", "middle");
  // Give it a class that will determine the text size, colour, etc
  textPath.classList.add("label-text");
  // Set the text
  textPath.textContent = labelText;

  // Add the elements directly after the label background path
  bgPath.after(pathElem);
  pathElem.after(textElem);
  textElem.appendChild(textPath);
}
.st37 {
  fill: lightblue;
}

.label-text {
  font-size: 10px;
  fill: darkblue;
}
<svg viewbox="387 390 74 20">
    <g>
        <path id="mylabel" class="st37" d="M452,408h-56c-4.42,0-8-3.58-8-8l0,0c0-4.42,3.58-8,8-8h56c4.42,0,8,3.58,8,8l0,0     C460,404.42,456.42,408,452,408z" />
    </g>
</svg>

Upvotes: 1

Paul LeBeau
Paul LeBeau

Reputation: 101938

Here's some sample code that takes a label path and adds a <text> element after it with whatever text you choose.

let label1 = document.querySelector("#label1");

addLabelText(label1, "Something");



function addLabelText(bgPath, labelText)
{
   let bbox = bgPath.getBBox();
   let x = bbox.x + bbox.width / 2;
   let y = bbox.y + bbox.height / 2;
   
   // Create a <text> element
   let textElem = document.createElementNS(bgPath.namespaceURI, "text");
   textElem.setAttribute("x", x);
   textElem.setAttribute("y", y);
   // Centre text horizontally at x,y
   textElem.setAttribute("text-anchor", "middle");
   // Give it a class that will determine the text size, colour, etc
   textElem.classList.add("label-text");
   // Set the text
   textElem.textContent = labelText;
   // Add this text element directly after the label background path
   bgPath.after(textElem);
}
.st37 {
  fill: linen;
}

.label-text {
  font-size: 10px;
  fill: rebeccapurple;
  transform: translate(0, 3px); /* adjust vertical position to centre text */
}
<svg viewbox="387 390 74 20">
  <g>
    <path id="label1" class="st37" d="M452,408h-56c-4.42,0-8-3.58-8-8l0,0c0-4.42,3.58-8,8-8h56c4.42,0,8,3.58,8,8l0,0     C460,404.42,456.42,408,452,408z" />
  </g>
</svg>

Upvotes: 4

Related Questions