cak3_lover
cak3_lover

Reputation: 1938

How to center svg text according to div height & width?

I have a svg path with a textPath connecting 2 divs from center like this:

item1.style.top="20px";
item1.style.left="20px";

item2.style.top="40px";
item2.style.left="160px";

var x1=parseFloat(item1.style.left)+ item1.offsetWidth / 2;
var y1=parseFloat(item1.style.top) + item1.offsetHeight / 2;

var x2=parseFloat(item2.style.left) + item2.offsetWidth / 2; 
var y2=parseFloat(item2.style.top)  + item2.offsetHeight / 2;

path1.setAttribute("d",`M ${x1} ${y1} L ${x2} ${y2}`)
*{
  margin:0;
}
div{
  height:2rem;
  width:2rem;
  position:absolute;
  background-color:black;
  box-sizing:border-box;
}
<div id="item1"></div>
<div id="item2" style="width:10rem; height:3rem"></div>

<svg id="svg1" style="overflow:visible">

  <path id="path1" fill="none" stroke="red" stroke-width="3" />
  <text font-size="24" dy="-10" text-anchor="middle">
    <textPath href="#path1"  fill="green" startOffset="50%">T</textPath>
  </text>
</svg>

But as you can see the Text "T" isn't technically centered because of the height & width

so is there a way to shift the text (without changing the path d) into visual center?
like this:

enter image description here

Note
The height, width & position of the divs will change so a more flexible & versatile approach would be better

Upvotes: 1

Views: 66

Answers (2)

cak3_lover
cak3_lover

Reputation: 1938

The solution provided by @herrstrietzel works but I found another solution

I used this solution and calculated the distance required to center and then used dx attribute to simply shift the text by that much

Upvotes: 0

herrstrietzel
herrstrietzel

Reputation: 17265

You could find the intersection points between the rectangles' borders and your textPath.
The final path start and end coordinates would be the calculated intersection points.

intersections

item1.style.top = "20px";
item1.style.left = "20px";
item2.style.top = "40px";
item2.style.left = "160px";

let svg = document.querySelector('svg');
let cx1 = parseFloat(item1.style.left) + item1.offsetWidth / 2;
let cy1 = parseFloat(item1.style.top) + item1.offsetHeight / 2;
let cx2 = parseFloat(item2.style.left) + item2.offsetWidth / 2;
let cy2 = parseFloat(item2.style.top) + item2.offsetHeight / 2;

// text path coordinates
let lTextP = [cx1, cy1, cx2, cy2];
renderLine(svg, lTextP)


// rect1: left, right, top, bottom
let x1 = parseFloat(item1.style.left);
let rx1 = x1 + item1.offsetWidth;
let y1 = parseFloat(item1.style.top);
let by1 = y1 + item1.offsetHeight;

// rect2: left, right, top, bottom
let x2 = parseFloat(item2.style.left);
let rx2 = x1 + item2.offsetWidth;
let y2 = parseFloat(item2.style.top);
let by2 = y2 + item2.offsetHeight;


// 1st rect: right border
let l1 = [rx1, y1, rx1, by1];
renderLine(svg, l1)

// 2nd rect: left border
let l2 = [x2, y2, x2, by2];
renderLine(svg, l2)


// find intersections between textpath and rect borders
let intersection1 = getVectorIntersection(l1, lTextP, true);
renderPoint(svg, intersection1, 'orange', '1%');
let intersection2 = getVectorIntersection(l2, lTextP, true);
renderPoint(svg, intersection2, 'orange', '1%');

// shorten text path according to intersections
[cx1, cy1] = intersection1;
[cx2, cy2] = intersection2;
path1.setAttribute("d", `M ${cx1} ${cy1} L ${cx2} ${cy2}`);


/**
 * helper: get intersection coordinates
 * based on
 * source: https://dirask.com/posts/JavaScript-calculate-intersection-point-of-two-lines-for-given-4-points-VjvnAj
 */
function getVectorIntersection(l1, l2, exact = false, decimals = 3) {
  let intersection = [];
  let dx1 = l1[0] - l1[2];
  let dy1 = l1[1] - l1[3];
  let dx2 = l2[0] - l2[2];
  let dy2 = l2[1] - l2[3];

  // down part of intersection point formula
  let d = dx1 * dy2 - dy1 * dx2;

  if (d === 0) {
    console.log('parallel')
    return false;
  } else {
    // upper part of intersection point formula
    let u1 = l1[0] * l1[3] - l1[1] * l1[2];
    let u4 = l2[0] * l2[3] - l2[1] * l2[2];

    // only exact intersections
    let isIntersecting = u4 > d;
    if (exact && !isIntersecting) {
      return false;
    }
    // intersection point formula
    let px = +((u1 * dx2 - dx1 * u4) / d).toFixed(decimals);
    let py = +((u1 * dy2 - dy1 * u4) / d).toFixed(decimals);
    intersection = [px, py];
  }
  return intersection;
}

// debug helper: render coordinates as markers
function renderPoint(svg, coords, fill = "red", r = "0.5%") {
  if (coords.length) {
    let marker =
      '<circle cx="' +
      coords[0] +
      '" cy="' +
      coords[1] +
      '"  r="' +
      r +
      '" fill="' +
      fill +
      '" ><title>' +
      coords.join(", ") +
      "</title></circle>";
    svg.insertAdjacentHTML("beforeend", marker);
  }
}

// debug helper: render lines
function renderLine(svg, coords, color = "purple", strokeWidth = 1) {
  let [x1n, y1n, x2n, y2n] = coords;
  let newLine =
    '<line x1="' +
    x1n +
    '"  y1="' +
    y1n +
    '" x2="' +
    x2n +
    '" y2="' +
    y2n +
    '"  stroke-width="' + strokeWidth + '" stroke="' + color + '" />';
  svg.insertAdjacentHTML("beforeend", newLine);
}
*{
  margin:0;
}
.item{
  height:2rem;
  width:2rem;
  position:absolute;
  z-index:-1;
  background-color:black;
  box-sizing:border-box;
}
<div class="item" id="item1"></div>
<div class="item" id="item2" style="width:10rem; height:3rem"></div>

<svg id="svg1" style="overflow:visible">
        <text font-size="24" dy="-10" text-anchor="middle">
            <textPath href="#path1" fill="green" startOffset="50%">T</textPath>
        </text>
        <path id="path1" fill="none" stroke="red" stroke-width="3" stroke-linecap="square" />
</svg>

An easier alternative might be to draw the text path between the vertical centers like so:

var x1=parseFloat(item1.style.left)+ item1.offsetWidth ;
var y1=parseFloat(item1.style.top) + item1.offsetHeight / 2;
var x2=parseFloat(item2.style.left); 
var y2=parseFloat(item2.style.top)  + item2.offsetHeight / 2;

item1.style.top="20px";
item1.style.left="20px";

item2.style.top="40px";
item2.style.left="160px";

var x1=parseFloat(item1.style.left)+ item1.offsetWidth ;
var y1=parseFloat(item1.style.top) + item1.offsetHeight / 2;
var x2=parseFloat(item2.style.left); 
var y2=parseFloat(item2.style.top)  + item2.offsetHeight / 2;

path1.setAttribute("d",`M ${x1} ${y1} L ${x2} ${y2}`)
*{
  margin:0;
}
.item{
  height:2rem;
  width:2rem;
  position:absolute;
  z-index:-1;
  background-color:black;
  box-sizing:border-box;
}
<div class="item" id="item1"></div>
<div class="item" id="item2" style="width:10rem; height:3rem"></div>

<svg id="svg1" style="overflow:visible">

  <text font-size="24" dy="-10" text-anchor="middle">
    <textPath href="#path1"  fill="green" startOffset="50%">T</textPath>
  </text>
  <path id="path1" fill="none" stroke="red" stroke-width="3" stroke-linecap="square" />

</svg>

Upvotes: 1

Related Questions