以数学方式转换SVG路径中的值以填充viewBox

时间:2016-02-10 19:51:26

标签: javascript svg viewbox

所以,让我们说我有一个看起来像这样的SVG:



<svg width="800" height="600" viewBox="0 0 800 600" style="border: 1px solid blue;">
	<path fill="#f00" stroke="none" d="M720 394.5c-27.98 0-51.61-6.96-71.97-18.72-29.64-17.1-52.36-44.37-71.48-75.12-28-45.01-48.31-97.48-71.39-136.52-20.03-33.88-42.14-57.64-73.16-57.64-31.1 0-53.24 23.88-73.31 57.89-23.04 39.05-43.34 91.45-71.31 136.39-19.28 30.98-42.21 58.41-72.2 75.45C195 387.72 171.62 394.5 144 394.5Z"/>
</svg>
&#13;
&#13;
&#13;

如您所见,路径仅占用SVG(和viewBox区域)的一部分。

我想知道如何转换它们填充viewBox的路径中的值(基本上重新缩放和重新定位路径中的值,以便填充整个viewBox)。

[UPDATE]

我正在添加更多细节...

举一个例子 - 假设我开始使用带有这样的viewBox的SVG:0 0 1600 1600

在该SVG中,有一条路径占据了从1200,12001500,1400的区域。 (即,路径为300 x 200)。

我希望能够提取该路径,并将其添加到viewBox为0 0 300 200的新SVG中。

为此,需要相应地修改d属性中的值 - 基本上向上移动1200点。

显然,绝对坐标需要改变,但相对坐标不会改变。 (这应该很简单)。

但我也必须处理曲线及其控制点,这可能会有点棘手。

一个完美的解决方案是能够检查路径,识别可能包含它的最小边界框,然后调整所有点,使它们适合该边界框,锚定在0,0

我不想缩放或延伸路径。

我对这样做的数学过程或功能,或某种在线工具同样感到满意。

我意识到我可以使用SVG转换来实现这一目标,但我希望能够改变实际路径。

(即,我不希望我的网页包含&#34;错误的&#34;数据和转换为&#34;更正&#34;它;我只是希望我的代码包含& #34;纠正&#34;数据。)

有办法做到这一点吗?

2 个答案:

答案 0 :(得分:1)

如果您不需要缩放新路径,那么您需要做的就是应用transform将其移动到正确的位置。如果它从(1200,1200)开始,并且你希望它在(0,0),那么进行变换"translate(-1200, -1200)"

<svg width="800" height="600" viewBox="0 0 800 600" style="border: 1px solid blue;">
    <path fill="#f00" stroke="none" transform="translate(-1200,-1200)"
          d="M720 394.5c-27.98 0-51.61-6.96-71.97-18.72-29.64-17.1-52.36-44.37-71.48-75.12-28-45.01-48.31-97.48-71.39-136.52-20.03-33.88-42.14-57.64-73.16-57.64-31.1 0-53.24 23.88-73.31 57.89-23.04 39.05-43.34 91.45-71.31 136.39-19.28 30.98-42.21 58.41-72.2 75.45C195 387.72 171.62 394.5 144 394.5Z"/>
</svg>

答案 1 :(得分:1)

在您提供更新之前,我已经写了大部分答案。因此,我的回答是对我原本想要的回应:能够直接改变&#34; d&#34; SVG路径的属性,以便路径现在只填充SVG视口。因此,我的答案确实涉及您在原始答案中建议您确实需要的缩放,但您在更新中并不需要。在任何情况下,我希望我的代码能让您了解如何在不使用转换的情况下直接更改d属性。

下面的代码段显示了您以红色提供的原始路径,其中&#34;已转换为&#34;路径显示为蓝色。请注意,在svg代码中,两条路径的开头相同。您可以通过右键单击路径并选择&#34;检查元素&#34;来获取蓝色路径的d属性,至少在Firefox中是这样。

希望代码中的变量名称和注释提供了理解我的方法所需的指导。

(更新:修复了代码段中的代码,以便它现在也可以在Chrome和Safari中使用,而不仅仅是在Firefox中。看来有些ES6语言功能,例如&#34;让&# 34;,&#34; const&#34;,destructuring,Symbols,在Firefox中工作,但至少其中一些人不能在Chrome或Safari中工作。我还没有检查过Internet Explorer或Opera或其他任何内容浏览器)。

&#13;
&#13;
// Retrieve the "d" attribute of the SVG path you wish to transform.
var $svgRoot    = $("svg");
var $path       = $svgRoot.find("path#moved");
var oldPathDStr = $path.attr("d");

// Calculate the transformation required.
var obj = getTranslationAndScaling($svgRoot, $path);
var pathTranslX = obj.pathTranslX;
var pathTranslY = obj.pathTranslY;
var scale       = obj.scale;

// The path could be transformed at this point with a simple
// "transform" attribute as shown here.

// $path.attr("transform", `translate(${pathTranslX}, ${pathTranslY}), scale(${scale})`);

// However, as described in your question you didn't want this.
// Therefore, the code following this line mutates the actual svg path.

// Calculate the path "d" attributes parameters.
var newPathDStr = getTransformedPathDStr(oldPathDStr, pathTranslX, pathTranslY, scale);

// Apply the new "d" attribute to the path, transforming it.
$path.attr("d", newPathDStr);

document.write("<p>Altered 'd' attribute of path:</p><p>" + newPathDStr + "</p>");

// This is the end of the main code. Below are the functions called.



// Calculate the transformation, i.e. the translation and scaling, required
// to get the path to fill the svg area. Note that this assumes uniform
// scaling, a path that has no other transforms applied to it, and no
// differences between the svg viewport and viewBox dimensions.
function getTranslationAndScaling($svgRoot, $path) {
  var svgWdth = $svgRoot.attr("width" );
  var svgHght = $svgRoot.attr("height");

  var origPathBoundingBox = $path[0].getBBox();

  var origPathWdth = origPathBoundingBox.width ;
  var origPathHght = origPathBoundingBox.height;
  var origPathX    = origPathBoundingBox.x     ;
  var origPathY    = origPathBoundingBox.y     ;

  // how much bigger is the svg root element
  // relative to the path in each dimension?
  var scaleBasedOnWdth = svgWdth / origPathWdth;
  var scaleBasedOnHght = svgHght / origPathHght;

  // of the scaling factors determined in each dimension,
  // use the smaller one; otherwise portions of the path
  // will lie outside the viewport (correct term?)
  var scale = Math.min(scaleBasedOnWdth, scaleBasedOnHght);

  // calculate the bounding box parameters
  // after the path has been scaled relative to the origin
  // but before any subsequent translations have been applied

  var scaledPathX    = origPathX    * scale;
  var scaledPathY    = origPathY    * scale;
  var scaledPathWdth = origPathWdth * scale;
  var scaledPathHght = origPathHght * scale;

  // calculate the centre points of the scaled but untranslated path
  // as well as of the svg root element

  var scaledPathCentreX = scaledPathX + (scaledPathWdth / 2);
  var scaledPathCentreY = scaledPathY + (scaledPathHght / 2);
  var    svgRootCentreX = 0           + (svgWdth        / 2);
  var    svgRootCentreY = 0           + (svgHght        / 2);

  // calculate translation required to centre the path
  // on the svg root element

  var pathTranslX = svgRootCentreX - scaledPathCentreX;
  var pathTranslY = svgRootCentreY - scaledPathCentreY;

  return {pathTranslX, pathTranslY, scale};
}
  
function getTransformedPathDStr(oldPathDStr, pathTranslX, pathTranslY, scale) {

  // constants to help keep track of the types of SVG commands in the path
  var BOTH_X_AND_Y   = 1;
  var JUST_X         = 2;
  var JUST_Y         = 3;
  var NONE           = 4;
  var ELLIPTICAL_ARC = 5;
  var ABSOLUTE       = 6;
  var RELATIVE       = 7;

  // two parallel arrays, with each element being one component of the
  // "d" attribute of the SVG path, with one component being either
  // an instruction (e.g. "M" for moveto, etc.) or numerical value
  // for either an x or y coordinate
  var oldPathDArr = getArrayOfPathDComponents(oldPathDStr);
  var newPathDArr = [];

  var commandParams, absOrRel, oldPathDComp, newPathDComp;

  // element index
  var idx = 0;

  while (idx < oldPathDArr.length) {
    var oldPathDComp = oldPathDArr[idx];
    if (/^[A-Za-z]$/.test(oldPathDComp)) { // component is a single letter, i.e. an svg path command
      newPathDArr[idx] = oldPathDArr[idx];
      switch (oldPathDComp.toUpperCase()) {
        case "A": // elliptical arc command...the most complicated one
          commandParams = ELLIPTICAL_ARC;
          break;
        case "H": // horizontal line; requires only an x-coordinate
          commandParams = JUST_X;
          break;
        case "V": // vertical line; requires only a y-coordinate
          commandParams = JUST_Y;
          break;
        case "Z": // close the path
          commandParams = NONE;
          break;
        default: // all other commands; all of them require both x and y coordinates
          commandParams = BOTH_X_AND_Y;
      }
      absOrRel = ((oldPathDComp === oldPathDComp.toUpperCase()) ? ABSOLUTE : RELATIVE);
      // lowercase commands are relative, uppercase are absolute
      idx += 1;
    } else { // if the component is not a letter, then it is a numeric value
      var translX, translY;
      if (absOrRel === ABSOLUTE) { // the translation is required for absolute commands...
        translX = pathTranslX;
        translY = pathTranslY;
      } else if (absOrRel === RELATIVE) { // ...but not relative ones
        translX = 0;
        translY = 0;
      }
      switch (commandParams) {
        // figure out which of the numeric values following an svg command
        // are required, and then transform the numeric value(s) from the
        // original path d-attribute and place it in the same location in the
        // array that will eventually become the d-attribute for the new path
        case BOTH_X_AND_Y:
          newPathDArr[idx    ] = Number(oldPathDArr[idx    ]) * scale + translX;
          newPathDArr[idx + 1] = Number(oldPathDArr[idx + 1]) * scale + translY;
          idx += 2;
          break;
        case JUST_X:
          newPathDArr[idx    ] = Number(oldPathDArr[idx    ]) * scale + translX;
          idx += 1;
          break;
        case JUST_Y:
          newPathDArr[idx    ] = Number(oldPathDArr[idx    ]) * scale + translY;
          idx += 1;
          break;
        case ELLIPTICAL_ARC:
          // the elliptical arc has x and y values in the first and second as well as
          // the 6th and 7th positions following the command; the intervening values
          // are not affected by the transformation and so can simply be copied
          newPathDArr[idx    ] = Number(oldPathDArr[idx    ]) * scale + translX;
          newPathDArr[idx + 1] = Number(oldPathDArr[idx + 1]) * scale + translY;
          newPathDArr[idx + 2] = Number(oldPathDArr[idx + 2])                        ;
          newPathDArr[idx + 3] = Number(oldPathDArr[idx + 3])                        ;
          newPathDArr[idx + 4] = Number(oldPathDArr[idx + 4])                        ;
          newPathDArr[idx + 5] = Number(oldPathDArr[idx + 5]) * scale + translX;
          newPathDArr[idx + 6] = Number(oldPathDArr[idx + 6]) * scale + translY;
          idx += 7;
          break;
        case NONE:
          throw new Error('numeric value should not follow the SVG Z/z command');
          break;
      }
    }
  }
  return newPathDArr.join(" ");
}

function getArrayOfPathDComponents(str) {
  // assuming the string from the d-attribute of the path has all components
  // separated by a single space, then create an array of components by
  // simply splitting the string at those spaces
  str = standardizePathDStrFormat(str);
  return str.split(" ");
}

function standardizePathDStrFormat(str) {
  // The SVG standard is flexible with respect to how path d-strings are
  // formatted but this makes parsing them more difficult. This function ensures
  // that all SVG path d-string components (i.e. both commands and values) are
  // separated by a single space.
  return str
    .replace(/,/g         , " "   )  // replace each comma with a space
    .replace(/-/g         , " -"  )  // precede each minus sign with a space
    .replace(/([A-Za-z])/g, " $1 ")  // sandwich each   letter between 2 spaces
    .replace(/  /g        , " "   )  // collapse repeated spaces to a single space
    .replace(/ ([Ee]) /g  , "$1"  )  // remove flanking spaces around exponent symbols
    .replace(/^ /g        , ""    )  // trim any leading space
    .replace(/ $/g        , ""    ); // trim any tailing space
}
&#13;
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<svg width="800" height="600" viewBox="0 0 800 600" style="border: 1px solid blue;">
	<path id="notmoved" fill="#f00" stroke="none" d="M720 394.5c-27.98 0-51.61-6.96-71.97-18.72-29.64-17.1-52.36-44.37-71.48-75.12-28-45.01-48.31-97.48-71.39-136.52-20.03-33.88-42.14-57.64-73.16-57.64-31.1 0-53.24 23.88-73.31 57.89-23.04 39.05-43.34 91.45-71.31 136.39-19.28 30.98-42.21 58.41-72.2 75.45C195 387.72 171.62 394.5 144 394.5Z" opacity="0.5" />
	<path id="moved" fill="#00f" stroke="none" d="M720 394.5c-27.98 0-51.61-6.96-71.97-18.72-29.64-17.1-52.36-44.37-71.48-75.12-28-45.01-48.31-97.48-71.39-136.52-20.03-33.88-42.14-57.64-73.16-57.64-31.1 0-53.24 23.88-73.31 57.89-23.04 39.05-43.34 91.45-71.31 136.39-19.28 30.98-42.21 58.41-72.2 75.45C195 387.72 171.62 394.5 144 394.5Z" opacity="0.5" />
</svg>
&#13;
&#13;
&#13;