如何在SVG中制作螺旋?

时间:2018-03-04 04:27:28

标签: svg

我想在SVG中制作Archimedean spiral

created a spiral有四个二次贝塞尔点,但我不知道我应该把控制点放在哪里以获得完美的阿基米德螺旋:

<path class="spiral" 
  d="M100 50 
     C 100 116 12.5 99.5 12.5 50 
     C 12.5 0.5 75 9 75 50 
     C 75 83 37.5 74 37.5 50
     C 37.5 38 50 42 50 50"
  stroke="black" stroke-width="1" fill="none">

3 个答案:

答案 0 :(得分:3)

我想在math.stackexchange处扩展Zev Eisenberg提出的问题。从那以后,Zev implented a solution作为C函数。它使用二次贝塞尔曲线而不是立方体,但其优点是可以自由设置路径部分的角度,从而最大限度地减少误差。

这是一个Javascript端口。根据自己的喜好将参数设置为getPath(角度为度)。 thetaStep是每个路径部分涵盖的角度。我认为30°给出了相当不错的结果。

function lineIntersection (m1, b1, m2, b2) {
    if (m1 === m2) {
        throw new Error("parallel slopes");
    }
    const x = (b2 - b1) / (m1 - m2);
    return {x: x, y: m1 * x + b1};
}

function pStr (point) {
  return `${point.x},${point.y} `;
}

function getPath (center, startRadius, spacePerLoop, startTheta, endTheta, thetaStep) {
    // Rename spiral parameters for the formula r = a + bθ
    const a = startRadius;  // start distance from center
    const b = spacePerLoop / Math.PI / 2; // space between each loop

    // convert angles to radians
    let oldTheta = newTheta = startTheta * Math.PI / 180;
    endTheta = endTheta * Math.PI / 180;
    thetaStep = thetaStep * Math.PI / 180;

    // radii
    let oldR,
        newR = a + b * newTheta;

    // start and end points
    const oldPoint = {x: 0, y: 0};
    const newPoint = {
        x: center.x + newR * Math.cos(newTheta), 
        y: center.y + newR * Math.sin(newTheta)
    };

    // slopes of tangents
    let oldslope,
        newSlope = (b * Math.sin(oldTheta) + (a + b * newTheta) * Math.cos(oldTheta)) /
                   (b * Math.cos(oldTheta) - (a + b * newTheta) * Math.sin(oldTheta));

    let path = "M " + pStr(newPoint);
    
    while (oldTheta < endTheta - thetaStep) {
        oldTheta = newTheta;
        newTheta += thetaStep;

        oldR = newR;
        newR = a + b * newTheta;

        oldPoint.x = newPoint.x;
        oldPoint.y = newPoint.y;
        newPoint.x = center.x + newR * Math.cos(newTheta);
        newPoint.y = center.y + newR * Math.sin(newTheta);

        // Slope calculation with the formula:
        // (b * sinΘ + (a + bΘ) * cosΘ) / (b * cosΘ - (a + bΘ) * sinΘ)
        const aPlusBTheta = a + b * newTheta;

        oldSlope = newSlope;
        newSlope = (b * Math.sin(newTheta) + aPlusBTheta * Math.cos(newTheta)) /
                   (b * Math.cos(newTheta) - aPlusBTheta * Math.sin(newTheta));

        const oldIntercept = -(oldSlope * oldR * Math.cos(oldTheta) - oldR * Math.sin(oldTheta));
        const newIntercept = -(newSlope * newR* Math.cos(newTheta) - newR * Math.sin(newTheta));

        const controlPoint = lineIntersection(oldSlope, oldIntercept, newSlope, newIntercept);

        // Offset the control point by the center offset.
        controlPoint.x += center.x;
        controlPoint.y += center.y;

        path += "Q " + pStr(controlPoint) + pStr(newPoint);
    }
    
    return path;
}

const path = getPath({x:400,y:400}, 0, 50, 0, 6*360, 30);

const spiral = document.querySelector('#spiral');
spiral.setAttribute("d", path);
<svg xmlns="http://www.w3.org/2000/svg" width="400" height="400" viewBox="0 0 800 800">
    <path id="spiral" d="" fill="none" stroke="black" stroke-width="3"/>
</svg>

答案 1 :(得分:1)

要获取sprien代码,您可以使用矢量编辑器,例如Inkscape

在矢量编辑器工具栏中,选择螺旋(F9),您可以在其中选择螺旋参数 - 转数,内半径。

保存文件。我们需要字符串<path> ... </ path>复制它。

<svg xmlns="http://www.w3.org/2000/svg" width="50%" heihgt="50%" viewBox="0 150 744 1052">
<path d="m351 487c0 8-11 4-14-1-6-11 4-24 15-27 19-5 37 11 40 30 4 27-18 50-44 53-35 4-64-25-66-59-3-42 32-77 73-79 50-3 90 39 92 88 2 57-46 104-102 105-65 2-117-53-119-117-1-72 60-131 131-132 80-1 144 67 145 146 1 87-74 158-160 158-95 0-171-81-171-175 0-102 88-185 190-184 110 1 198 95 197 204C557 615 456 709 340 708 215 706 115 598 117 475 119 342 233 236 364 238 504 240 616 361 614 500 611 648 484 766 337 763 182 760 58 626 61 472 65 309 206 179 367 183c170 4 306 151 302 320-4 178-158 319-335 315" fill="none" stroke="grey" stroke-width="3"/>
   

螺旋动画示例

对于螺旋图的动画,我们将使用补丁属性stroke-dashoffset - 从行的开头缩进。如果最大缩进等于行(补丁)的长度,则该行不可见。使用stroke-dashoffset = "0"时,线条将完全绘制。

换句话说,要实现线条的绘制动画,您需要将边距从最大值减小到零。

找出该行的长度 - var len = Math.round (path.getTotalLength ()); 对于我们的补丁 - 6265px

<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" width="50%" heihgt="50%" viewBox="0 150 744 1052" id="svg2" version="1">
   
     <path  stroke-dashoffset="6265" stroke-dasharray="6265" d="m351 487c0 8-11 4-14-1-6-11 4-24 15-27 19-5 37 11 40 30 4 27-18 50-44 53-35 4-64-25-66-59-3-42 32-77 73-79 50-3 90 39 92 88 2 57-46 104-102 105-65 2-117-53-119-117-1-72 60-131 131-132 80-1 144 67 145 146 1 87-74 158-160 158-95 0-171-81-171-175 0-102 88-185 190-184 110 1 198 95 197 204C557 615 456 709 340 708 215 706 115 598 117 475 119 342 233 236 364 238 504 240 616 361 614 500 611 648 484 766 337 763 182 760 58 626 61 472 65 309 206 179 367 183c170 4 306 151 302 320-4 178-158 319-335 315" style="fill:none;stroke:#000" stroke-width="2">
	 <animate attributeName="stroke-dashoffset" values="6265;0;6265;0" dur="15s" fill="freeze" /> 
	 </path>
  

使用CSS实现动画

将鼠标悬停在光标上时开始动画

#spiral {
  
  stroke: dodgerblue;
  stroke-width:4;
  fill:#FCFCFC;
  stroke-dasharray: 6265;
  stroke-dashoffset: 6265;
  transition: stroke-dashoffset 10s;
}

#spiral:hover {
  stroke-dashoffset: 0;
} 
svg text {
font-size:36px;
pointer-events:none;
}
<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" width="50%" heihgt="50%" viewBox="0 150 744 1052" id="svg2" version="1"  >
   
     <path id="spiral"  stroke-dashoffset="6265" stroke-dasharray="6265"  d="m351 487c0 8-11 4-14-1-6-11 4-24 15-27 19-5 37 11 40 30 4 27-18 50-44 53-35 4-64-25-66-59-3-42 32-77 73-79 50-3 90 39 92 88 2 57-46 104-102 105-65 2-117-53-119-117-1-72 60-131 131-132 80-1 144 67 145 146 1 87-74 158-160 158-95 0-171-81-171-175 0-102 88-185 190-184 110 1 198 95 197 204C557 615 456 709 340 708 215 706 115 598 117 475 119 342 233 236 364 238 504 240 616 361 614 500 611 648 484 766 337 763 182 760 58 626 61 472 65 309 206 179 367 183c170 4 306 151 302 320-4 178-158 319-335 315" />
	  <text x="10" y="200"   > Mouse over </text>
</svg>

答案 2 :(得分:0)

您需要使用贝塞尔曲线吗?您无法获得具有贝塞尔曲线的完美螺旋线,就像您无法获得精确的圆弧一样。

您可能需要考虑使用由直线段组成的<polyline>

&#13;
&#13;
// pathId the id of the path element to modify.
// centreX, centreY: the position of the centre of the spiral.
// startRadius: radius at start (inside) of spiral.
// endRadius: radius after one complete (360deg) rotation.
// quarterTurns: the number of quarter turns to generate.

function makeSpiral(pathId, centreX, centreY, startRadius, endRadius, quarterTurns)
{
  var pointsPerQuarter = 90;
  var radiusStep = (endRadius - startRadius) / 4 / pointsPerQuarter;
  var points = [];

  for (var i=0; i < quarterTurns * pointsPerQuarter; i++)
  {
    var radius = startRadius + radiusStep * i;
    var angle = i * Math.PI / 2 / pointsPerQuarter;
    points.push(radius * Math.cos(angle));
    points.push(radius * Math.sin(angle));
  }
  document.getElementById(pathId).setAttribute("points", points.join(','));
}


makeSpiral("spiral", 0, 0, 1, 2, 31);
&#13;
<svg width="300" viewBox="-10 -10 20 20">

  <g class="arc" fill="none" stroke="blue" stroke-width="0.05">
    <polyline id="spiral" points=""/>
  </g>

</svg>
&#13;
&#13;
&#13;

使用beziers

就像我说的那样,beziers永远不会是精确的,但是要小心,你可以得到一个非常准确的近似值。

让我们先想象一个四分之一圈。控制点近似于该半径的比率约为0.55。确切的值取决于您是要最小化最大错误,还是平均错误,或通过特定点等等。

You can read about one approach to calculate this here.

该页面上的第一种方法是(4/3)*(sqrt(2) - 1),这是最常用的值。

&#13;
&#13;
<svg width="300" viewBox="-0.5 -0.5 2 2">

  <g class="axes" stroke="black" stroke-width="0.01">
    <line x2="1.1" y2="0"/>
    <line x2="0" y2="1.1"/>
  </g>

  <g class="arc" fill="none" stroke="blue" stroke-width="0.01">
    <path d="M 1,0 C 1,0.552, 0.552,1, 0,1"/>
  </g>

</svg>
&#13;
&#13;
&#13;

为了制作你的螺旋,你可以想象当你每个四分之一圆圈步长时半径增长。

为了使这更容易,我将使用一些JS来计算我们的bezier值。我还将红色参考螺旋包括在内,以了解bezier版本的准确度。

&#13;
&#13;
// pathId the id of the path element to modify.
// centreX, centreY: the position of the centre of the spiral.
// startRadius: radius at start (inside) of spiral.
// endRadius: radius after one complete (360deg) rotation.
// quarterTurns: the number of quarter turns to generate.

function makeSpiral(pathId, centreX, centreY, startRadius, endRadius, quarterTurns)
{
  var radiusStep = (endRadius - startRadius) / 4;
  var FACTOR = 0.5522847498;

  var step = 0;
  var radius = startRadius;
  var nextRadius = radius + radiusStep;
  var d = "M " + (centreX + startRadius) + "," + centreY;

  while (step < quarterTurns)
  {
    switch(step % 4)
    {
      case 0:
        d += "c" + [0, radius * FACTOR, -radius + nextRadius * FACTOR, nextRadius, -radius, nextRadius].join(',');
        break;        

      case 1:
        d += "c" + [-radius * FACTOR, 0, -nextRadius, -radius + nextRadius * FACTOR, -nextRadius, -radius].join(',');
        break;        

      case 2:
        d += "c" + [0, -radius * FACTOR, radius - nextRadius * FACTOR, -nextRadius, radius, -nextRadius].join(',');
        break;        

      case 3:
        d += "c" + [radius * FACTOR, 0, nextRadius, radius - nextRadius * FACTOR, nextRadius, radius].join(',');
        break;
    }
    step++;
    radius = nextRadius;
    nextRadius += radiusStep;
  }

  document.getElementById(pathId).setAttribute("d", d);
}


function makePolylineSpiral(pathId, centreX, centreY, startRadius, endRadius, quarterTurns)
{
  var pointsPerQuarter = 90;
  var radiusStep = (endRadius - startRadius) / 4 / pointsPerQuarter;
  var points = [];

  for (var i=0; i < quarterTurns * pointsPerQuarter; i++)
  {
    var radius = startRadius + radiusStep * i;
    var angle = i * Math.PI / 2 / pointsPerQuarter;
    points.push(radius * Math.cos(angle));
    points.push(radius * Math.sin(angle));
  }
  document.getElementById(pathId).setAttribute("points", points.join(','));
}


makePolylineSpiral("reference-spiral", 0, 0, 1, 2, 4);
makeSpiral("spiral", 0, 0, 1, 2, 4);
&#13;
<svg width="300" viewBox="-2 -2 5 5">

  <g class="arc" fill="none" stroke="blue" stroke-width="0.1">
    <polyline id="reference-spiral" points="" stroke="red"/>
    <path id="spiral" d=""/>
  </g>

</svg>
&#13;
&#13;
&#13;

不幸的是,我们可以看到天真的bezier版本与参考螺旋很不匹配。您可以尝试调整控制点比率,但是您会发现它总是看起来有点不稳定。

为了更好地逼近螺旋线,您需要使用覆盖圆的较小部分的贝塞尔曲线(即小于90度)。

我不打算在这里做,但你可能想试试自己。就个人而言,我坚持使用<polyline>版本。如果您需要更少或更多的积分,则可以修改pointsPerQuarter值。