【问题标题】:How to center svg text according to div height & width?如何根据 div 高度和宽度居中 svg 文本?
【发布时间】:2022-08-19 01:14:45
【问题描述】:

我有一个带有textPath 的svg 路径,从中心连接2 个divs,如下所示:

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>

但正如您所看到的,由于高度和宽度,文本 \"T\" 在技术上并不居中

那么有没有办法将文本(不改变路径d)转移到视觉中心?
像这样:

笔记
div 的高度、宽度和位置会发生变化,因此更灵活和通用的方法会更好

    标签: javascript html css


    【解决方案1】:

    您可以找到矩形边框和textPath 之间的交点。
    最终路径的起点和终点坐标将是计算出的交点。

    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>

    一个更简单的选择可能是在垂直中心之间绘制文本路径,如下所示:

    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>

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-08-20
      • 2011-12-22
      • 1970-01-01
      • 1970-01-01
      • 2014-02-16
      • 1970-01-01
      • 2022-10-18
      相关资源
      最近更新 更多