如何从调整大小/缩放的 SVG 路径定义新的“d”属性?

问题描述 投票:0回答:1

我的问题

目前,我已将 pathIcon 沿着 SVG 路径的“d”路径定位。但是,如果我更改 SVG 的大小,“d”属性也必须更改。
如何定义一个新的“d”,它类似于具有新大小的旧 SVG 路径?我不知道如何在 JavaScript 中定义新的“d”。

document.addEventListener("DOMContentLoaded", function() {
  let svg        = document.querySelector(".svg-path");
  let mPath      = document.getElementById("Path_440");
  let strokePath = document.getElementById("theFill");
  let pathIcon   = document.getElementById("pathIcon");

  const svgRect = svg.getBBox();
  const width   = svgRect.width;
  const height  = svgRect.height;
  svg.setAttribute("viewBox", `0 0 ${width} ${height}`)

  function defineNewOffsetPath()
    {
    /**my issues : defineNewOffsetPath 
      mPath.setAttribute("d", newPath) 
      how to define a path look like old path with new size
    **/
    pathIcon.style.offsetPath = `path('${mPath.getAttribute("d")}')`;
    }

   defineNewOffsetPath();
})
.svg-path {
  overflow: visible;
  width: 100%;
  height: auto;
}

#pathIcon {
  position: absolute;
  inset: 0;
  width: 100px;
  height: 200px;
  background-size: 25px;
  offset-rotate: 0rad;
  transition: 0.2s;
  offset-distance: 0%;
}

#Path_440 {
  stroke-width: 2;
  stroke: #001d36;
}
<div style="height: 175px"></div>
<div id="scrollDiv" style="position: relative">
  <svg class="svg-path" viewBox="0 0 0 0" fill="none">
    <defs>
      <path id="Path_440"
        d="M1293 2S1277 76.47 1197 93.5C1105.55 112.97 888.33 91.07 772.5 100.5 545.5 100.5 302.61 125.94 279 295.5 268 374.5 265.11 419.83 268 503S269.9 645.65 305 741C346.77 854.46 770 838.5 1094.5 832 1366 842.5 1676.02 792 1766 1011 1803.18 1101.5 1766 1457.5 1766 1493.5 1766 1561 1696 1613.5 1618 1627.5 1465 1627.5 1188.11 1632.5 1003.5 1632.5 732.5 1632.5 369.53 1605.69 312 1717.5 271.61 1796 276 1920 276 1982 277.12 2074.28 272.55 2144.17 312 2258 339.86 2338.39 721.15 2324.5 981 2324.5 1297 2324.5 1677.34 2307.5 1739.5 2403.5 1793.57 2487 1772.73 2616.18 1772.73 2765 1772.73 2893 1770.73 2997.5 1652 3032 1612.67 3043.43 1237 3032 893 3032 405.5 3032 2 3030 2 3030"
        stroke="#020878" stroke-width="2" stroke-miterlimit="10" stroke-linecap="round" stroke-dasharray="20 20" />
    </defs>
    <use href="#Path_440" stroke-width="10" stroke-dasharray="20 10"></use>
    <use id="theFill" href="#Path_440" stroke-dasharray="1991.82, 9259.88" stroke-width="10" stroke="#4cacff"></use>
  </svg>
  <svg id="pathIcon" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
    <rect width="100" height="200" fill="url(#pattern0)" />
    <defs>
      <pattern id="pattern0" patternContentUnits="objectBoundingBox" width="1" height="1">
        <use xlink:href="#image0_873_8619" transform="scale(0.00353357 0.00176678)" />
      </pattern>
      <image id="image0_873_8619" width="283" height="566" xlink:href="data:image/png;base64,iVBORw0KGgoAAAA" />
    </defs>
  </svg>
</div>

javascript html css svg
1个回答
0
投票

如果您只需要按比例缩放路径数据,那么这非常简单:

  • 解析你的字符串化路径数据
  • 按比例因子缩放所有与点相关的命令值(您需要排除
    a
    圆弧命令值,例如 x 轴旋转、largeArc 和扫描标志)
  • 将路径数据字符串化为
    d
    属性

let svg = document.querySelector(".svg-path");
let mPath = document.getElementById("Path_440");
let strokePath = document.getElementById("theFill");
let pathIcon = document.getElementById("pathIcon");

document.addEventListener("DOMContentLoaded", function () {
  // auto adjust viewBox
  let svgRect = svg.getBBox();
  let width = svgRect.width;
  let height = svgRect.height;
  svg.setAttribute("viewBox", `0 0 ${width} ${height}`);

  // update offset path
  defineNewOffsetPath();
});


function defineNewOffsetPath() {
  
  // retrieve the current scale from SVG transformation matrix
  let matrix = svg.getCTM();
  let scale = matrix.a;
  
  // parse path data
  let d = mPath.getAttribute("d");
  let pathData = parsePathData(d);

  //scale pathdata
  pathData = scalePathData(pathData, scale);

  // apply scaled pathdata as stringified d attribute value
  d = pathDataToD(pathData);
  pathIcon.style.offsetPath = `path('${d}')`;
}

// recalculate offset path on resize
window.addEventListener("resize", (e) => {
  defineNewOffsetPath();
});

// just for illustration
resizeObserver()
function resizeObserver() {
  defineNewOffsetPath();
}
new ResizeObserver(resizeObserver).observe(scrollDiv)




/**
* sclae path data proportional
*/
function scalePathData(pathData, scale = 1) {
  let pathDataScaled = [];
  pathData.forEach((com, i) => {
    let { type, values } = com;
    let comT = {
      type: type,
      values: []
    };

    switch (type.toLowerCase()) {
      // lineto shorthands
      case "h":
        comT.values = [values[0] * scale]; // horizontal - x-only
        break;
      case "v":
        comT.values = [values[0] * scale]; // vertical - x-only
        break;

      // arcto
      case "a":
        comT.values = [
          values[0] * scale, // rx: scale
          values[1] * scale, // ry: scale
          values[2], // x-axis-rotation: keep it 
          values[3], // largeArc: dito
          values[4], // sweep: dito
          values[5] * scale, // final x: scale
          values[6] * scale // final y: scale
        ];
        break;

      /**
      * Other point based commands: L, C, S, Q, T
      * scale all values
      */
      default:
        if (values.length) {
          comT.values = values.map((val, i) => {
            return val * scale;
          });
        }
    }
    pathDataScaled.push(comT);
  });
  return pathDataScaled;
}

/**
* parse stringified path data used in d attribute
* to an array of computable command data
*/
function parsePathData(d) {
  d = d
    // remove new lines, tabs an comma with whitespace
    .replace(/[\n\r\t|,]/g, " ")
    // pre trim left and right whitespace
    .trim()
    // add space before minus sign
    .replace(/(\d)-/g, "$1 -")
    // decompose multiple adjacent decimal delimiters like 0.5.5.5 => 0.5 0.5 0.5
    .replace(/(\.)(?=(\d+\.\d+)+)(\d+)/g, "$1$3 ");

  let pathData = [];
  let cmdRegEx = /([mlcqazvhst])([^mlcqazvhst]*)/gi;
  let commands = d.match(cmdRegEx);

  // valid command value lengths
  let comLengths = {
    m: 2,
    a: 7,
    c: 6,
    h: 1,
    l: 2,
    q: 4,
    s: 4,
    t: 2,
    v: 1,
    z: 0
  };
  commands.forEach((com) => {
    let type = com.substring(0, 1);
    let typeRel = type.toLowerCase();
    let isRel = type === typeRel;
    let chunkSize = comLengths[typeRel];

    // split values to array
    let values = com.substring(1, com.length).trim().split(" ").filter(Boolean);

    /**
     * A - Arc commands
     * large arc and sweep flags
     * are boolean and can be concatenated like
     * 11 or 01
     * or be concatenated with the final on path points like
     * 1110 10 => 1 1 10 10
     */
    if (typeRel === "a" && values.length != comLengths.a) {
      let n = 0,
        arcValues = [];
      for (let i = 0; i < values.length; i++) {
        let value = values[i];

        // reset counter
        if (n >= chunkSize) {
          n = 0;
        }
        // if 3. or 4. parameter longer than 1
        if ((n === 3 || n === 4) && value.length > 1) {
          let largeArc = n === 3 ? value.substring(0, 1) : "";
          let sweep = n === 3 ? value.substring(1, 2) : value.substring(0, 1);
          let finalX = n === 3 ? value.substring(2) : value.substring(1);
          let comN = [largeArc, sweep, finalX].filter(Boolean);
          arcValues.push(comN);
          n += comN.length;
        } else {
          // regular
          arcValues.push(value);
          n++;
        }
      }
      values = arcValues.flat().filter(Boolean);
    }

    // string  to number
    values = values.map(Number);

    // if string contains repeated shorthand commands - split them
    let hasMultiple = values.length > chunkSize;
    let chunk = hasMultiple ? values.slice(0, chunkSize) : values;
    let comChunks = [
      {
        type: type,
        values: chunk
      }
    ];

    // has implicit or repeated commands – split into chunks
    if (hasMultiple) {
      let typeImplicit = typeRel === "m" ? (isRel ? "l" : "L") : type;
      for (let i = chunkSize; i < values.length; i += chunkSize) {
        let chunk = values.slice(i, i + chunkSize);
        comChunks.push({
          type: typeImplicit,
          values: chunk
        });
      }
    }
    comChunks.forEach((com) => {
      pathData.push(com);
    });
  });

  /**
   * first M is always absolute/uppercase -
   * unless it adds relative linetos
   * (facilitates d concatenating)
   */
  pathData[0].type = "M";
  return pathData;
}

/**
 * serialize pathData array to
 * d attribute string
 */
function pathDataToD(pathData, decimals = 3) {
  let d = ``;
  pathData.forEach((com) => {
    d += `${com.type}${com.values
      .map((val) => {
        return +val.toFixed(decimals);
      })
      .join(" ")}`;
  });
  return d;
}
html{
  margin:0;
  padding:0;
}


.svg-path {
  overflow: visible;
  width: 100%;
}

#pathIcon {
  position: absolute;
  inset: 0;
  width: 5vw;
  height: 5vw;
  offset-rotate: 0deg;
  offset-distance: 10%;
}


#scrollDiv{
resize:both;
overflow:auto;
border: 1px solid #ccc;
margin:10px;
}
<div id="scrollDiv" style="position: relative">
  <svg class="svg-path" viewBox="0 0 0 0" fill="none">
    <defs>
      <path id="Path_440"
        d="M1293 2 s-16 74.47-96 91.5c-91.45 19.47-308.67-2.43-424.5 7-227 0-469.89 25.44-493.5 195-11 79-13.89 124.33-11 207.5s1.9 142.65 37 238c41.77 113.46 465 97.5 789.5 91 271.5 10.5 581.52-40 671.5 179 37.18 90.5 0 446.5 0 482.5 0 67.5-70 120-148 134-153 0-429.89 5-614.5 5-271 0-633.97-26.81-691.5 85-40.39 78.5-36 202.5-36 264.5 1.12 92.28-3.45 162.17 36 276 27.86 80.39 409.15 66.5 669 66.5 316 0 696.34-17 758.5 79 54.07 83.5 33.23 212.68 33.23 361.5 0 128-2 232.5-120.73 267-39.33 11.43-415 0-759 0-487.5 0-891-2-891-2"
         />
    </defs>
    <use class="stroke" href="#Path_440" stroke="#ccc" stroke-width="10" stroke-dasharray="20 10"></use>
    <use class="stroke" id="theFill" href="#Path_440" stroke-dasharray="925.988 9259.88" stroke-width="10" stroke="#4cacff"></use>
  </svg>
  <svg id="pathIcon" fill="none">
    <rect width="100" height="100" fill="red" fill-opacity="0.5"/>
  </svg>
</div>

© www.soinside.com 2019 - 2024. All rights reserved.