2016-02-10 41 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>