【问题标题】:Draw a "squiggly line" in SVG在 SVG 中画一条“波浪线”
【发布时间】:2017-07-15 10:51:55
【问题描述】:

我正在尝试研究如何在任意 SVG path 元素上绘制波浪线。路径由 React 组件生成。例如,我正在尝试复制 in this question 行:

在 SVG 和/或 JavaScript 生成的路径中是否有一种简单的方法可以做到这一点?

我考虑过使用s 路径命令连接一系列曲线,但随后我需要计算曲线上的点。我也考虑过某种置换过滤器,但我不确定从哪里开始。

【问题讨论】:

  • 继续喝啤酒,直到它看起来像波浪形
  • 哈哈。可以工作...让我试试

标签: javascript svg


【解决方案1】:

在我看来,最简单的方法就是沿着这条路走。然后,在每一步中,插入一个二次贝塞尔曲线,其控制点位于它们之间并垂直于曲线。然后在下一步切换控制点所在的一侧。

function makeSquiggle(squigglePathId, followPathId, squiggleStep, squiggleAmplitude)
{
  var followPath = document.getElementById(followPathId);
  var pathLen = followPath.getTotalLength();

  // Adjust step so that there are a whole number of steps along the path
  var numSteps = Math.round(pathLen / squiggleStep);

  var pos = followPath.getPointAtLength(0);
  var newPath = "M" + [pos.x, pos.y].join(',');
  var side = -1;
  for (var i=1; i<=numSteps; i++)
  {
    var last = pos;
    var pos = followPath.getPointAtLength(i * pathLen / numSteps);

    // Find a point halfway between last and pos. Then find the point that is
    // perpendicular to that line segment, and is squiggleAmplitude away from
    // it on the side of the line designated by 'side' (-1 or +1).
    // This point will be the control point of the quadratic curve forming the
    // squiggle step.
    
    // The vector from the last point to this one
    var vector = {x: (pos.x - last.x),
                  y: (pos.y - last.y)};
    // The length of this vector
    var vectorLen = Math.sqrt(vector.x * vector.x + vector.y * vector.y);
    // The point halfwasy between last point and tis one
    var half = {x: (last.x + vector.x/2),
                y: (last.y + vector.y/2)};
    // The vector that is perpendicular to 'vector'
    var perpVector = {x: -(squiggleAmplitude * vector.y / vectorLen),
                      y: (squiggleAmplitude * vector.x / vectorLen)};
    // No calculate the control point position
    var controlPoint = {x: (half.x + perpVector.x * side),
                        y: (half.y + perpVector.y * side)};
    newPath += ("Q" + [controlPoint.x, controlPoint.y, pos.x, pos.y].join(','));
    // Switch the side (for next step)
    side = -side;
  }
  var squigglePath = document.getElementById(squigglePathId);
  squigglePath.setAttribute("d", newPath);
}


makeSquiggle("squiggle", "follow", 25, 20);
#follow {
  fill: none;
  stroke: grey;
  stroke-width: 2;
}

#squiggle {
  fill: none;
  stroke: red;
  stroke-width: 2;
}
<svg width="500" height="400">
  <path id="follow" d="M 50,300 C 100,100 300,0, 350,250 L 450,200"/>
  <path id="squiggle" d="M0,0"/>
</svg>

【讨论】:

  • 很好的答案,谢谢。为了让它在 React 中工作而无需先渲染 SVG 节点,我可以使用 svg-path-properties npm 包。
【解决方案2】:

最好的方法可能只是连接贝塞尔曲线,然后偏移和反转您想要创建的每条后续曲线的值,直到达到所需的长度。

【讨论】:

  • 是的,可以是顺序三次贝塞尔曲线或二次曲线,具体取决于样式需求。 :-)
  • 我想这里的挑战是我需要手动计算路径上的点。这应该不是不可能的,我只是希望有更简单的方法!
【解决方案3】:

函数式解决方案

注意: f(x) 是你的波浪线应该遵循的曲线,x0 是你开始绘制波浪线的地方,xn 是你结束这个例子的地方假设 x0 和 xn 之间的 f(x) 不断增长

如果你有一条光栅化的曲线,你想画的波浪线是 cos(x),你可以找到画线的点,如下所示:

  • 波浪线要绘制的线
  • curve波浪线应该遵循的曲线
  • point0 是 曲线 开始的点
  • pointN 是 curve 中 x == N
  • 的点
  • lenN 是从 point0 到 pointN 的曲线长度
  • h(图中)是pointN中曲线和波浪线之间的距离,是cos (lenN)
  • alphaN 是 x == n 的 曲线 的切线与 x 轴的夹角
  • a(在图片中)是 -cos(alphaN)
  • b(在图片中)是 sin(alphaN)
  • squigglyPointN 是 pointN.x + a, pointN.y + b

  'use strict'
  //
  // point = [int, int], point[0] = x, point[1] = y
  // rasterizedCurve = [point0, ...,pointN]
  //

  // int -> [int,...,int]
  function rangeFrom1ToN(N) {
    return Array(N).fill(0).map((x, index) => index).slice(1);
  }

  // [int, ...,int] -> [float, ..., float]
  function expandRange(Range, precision) {
    return Range.map(x => rangeFrom1ToN(precision).map((y, index) => x + 1/precision * index))
                .reduce((acc, val) => acc.concat(val));
  }

  function formatForSvg(points) {
    return points.map(x => x.toString()).reduce((acc, val) =>  {return acc + ' ' + val})
  }

  // rasterizedCurve, index -> int
  function derivative(curve, index){
    //
    // return dx' curve(x)
    //
    if (index === 0) {
        return 0;
    }
    const point1 = curve[index - 1];
    const point2 = curve[index];
    return (point2[1] - point1[1]) / (point2[0] - point1[0]);
  }

  // rasterizedCurve -> rasterizedCurve
  function squiggleAroundCurve(x, y, curve, index) {
    const len = lenCurve(curve, index);
    const h = Math.sin(len);

    const b = Math.sin(Math.atan2(1, derivative(curve, index))) * h;
    const a = Math.cos(Math.atan2(1, derivative(curve, index))) * h;

    x -= a;
    y += b;
    return [x, y];
  }

  function pow2(x) {
    return Math.pow(x,2);
   }
    function dist(point1, point2) {
    return Math.sqrt(pow2(point2[0] - point1[0]) + pow2(point2[1] - point1[1]))
  }

  // rasterizedCurve, int -> int
  function lenCurve(rasterizedCurve, index) {
    const curve = rasterizedCurve.slice(0, index);
    return curve.reduce((sum, point, index) => {
      let len = 0;
      if (index > 0) {
        len = dist(point, curve[index - 1]);
      }
        return sum + len;
    }, 0);
  }


  const Curve = expandRange(rangeFrom1ToN(90),50).map(x => [x, (Math.log(x) * 15)]);
  const SquiggledCurve = Curve.map((point, index) => squiggleAroundCurve(point[0], point[1], Curve, index))
  function zoom(curve, w) {
    return curve.map(point => [point[0] * w, point[1] * w]);
  }
  function getNode(n, v) {
    n = document.createElementNS("http://www.w3.org/2000/svg", n);
    for (var p in v)
      n.setAttributeNS(null, p.replace(/[A-Z]/g, function(m, p, o, s) { return "-" + m.toLowerCase(); }), v[p]);
    return n
  }

  var svg = getNode("svg");

  setTimeout(function() {
    document.body.appendChild(svg);
    const r = getNode('polyline', { points:formatForSvg(zoom(SquiggledCurve, 10)), fill:'none', stroke:'black'});
    const c = getNode('polyline', { points:formatForSvg(zoom(Curve, 10)), fill:'none', stroke:'black'});
    svg.appendChild(r);
    svg.appendChild(c);
  }, 1000);
  svg {
    width: 1100px;
    height: 900px;
  }

【讨论】:

    猜你喜欢
    • 2017-11-10
    • 2019-11-14
    • 1970-01-01
    • 1970-01-01
    • 2015-11-13
    • 2011-09-30
    • 2022-01-25
    • 1970-01-01
    • 2018-06-21
    相关资源
    最近更新 更多