2016-02-10 12 views
0

所以,让我们说我有一个看起来像这样的SVG:数学变换中的SVG路径值来填充视框

<svg width="800" height="600" viewBox="0 0 800 600" style="border: 1px solid blue;"> 
 
\t <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>

正如你所看到的,路径仅占一部分SVG(和viewBox区域)。

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

[更新]

我增加了一些更多的细节......

以一个例子 - 让说,我开始与视框像这样的SVG:0 0 1600 1600

在该SVG中,有一条路径占用从1200,12001500,1400的区域。 (即,路径是300×200)。

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

要做到这一点,d属性中的值需要相应修改 - 基本上向上移动了1200个点并向左移动。

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

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

一个完美的解决方案将能够检查路径,确定可能包含它的最小边界框,然后调整所有点以使它们适合该边界框,并将其固定在0,0处。

我不想缩放或拉伸路径。

我很喜欢数学过程或函数来做到这一点,或者某种在线工具。

我意识到我可以使用SVG转换来完成此操作,但我希望能够更改实际路径。

(即,我不希望我的网页,包括“不正确”的数据以及变换为“纠正”它,我只是希望我的代码,包括“正确”的数据。)

有一种方法来做到这一点?

+1

[Inkscape中] (http://inkscape.org)可以[冻结转换](http://www.inkscapeforum.com/viewtopic.php?t=10205) – cxw

+1

其他方法可能会更容易:将视图框大小调整为路径。为此,你可以看看:http://stackoverflow.com/q/16377186/1169798 – Sirko

+0

@Sirko - 这是一个好主意,但是SVG中的其他所有*必须与该适配viewBox相关。我真正希望能够做的是从另一个SVG(带有一个任意的viewBox)开始,并使其适应新的SVG。所以 - 我真的需要能够适应路径,而不是viewBox。 – mattstuehler

回答

1

我写了我的大部分答案你给你的更新之前。因此,我的回答是对我以前想要的东西的回应:能够直接更改SVG路径的“d”属性,使路径现在只填充SVG视口。因此,我的答案确实涉及到您在原始答案中确实需要的缩放比例,但您不需要更新。无论如何,我希望我的代码能够让您了解如何在不使用变换的情况下直接更改d属性。

下面的代码片段显示了您以红色提供的原始路径,其中“已转换”路径显示为蓝色。请注意,svg代码中提供了两条路径始于相同的路径。至少在Firefox中,通过右键单击路径并选择“检查元素”,您可以获得蓝色路径的d属性。

希望代码中的变量名称和注释提供了您了解我的方法所需的准则。

(更新:代码片段中的固定代码,现在它也可以在Chrome和Safari中使用,不仅仅在Firefox中。看起来,某些ES6语言功能,例如“let”,“const”符号,在Firefox但至少他们中的一些作品没有在Chrome或Safari工作。我没有检查Internet Explorer或歌剧或任何其他浏览器。)

// 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 
 
}
<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;"> 
 
\t <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" /> 
 
\t <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>

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>