使用JavaScript平滑关闭一个SVG路径

huangapple go评论99阅读模式
英文:

Smoothly close an SVG path with JavaScript

问题

Sure, here's the translated part:

我正在处理一些代码,其中用户放下一个数字或点/标记,然后通过它们绘制贝塞尔曲线的SVG线条(因此平滑的线连接所有点)。它基于 https://codepen.io/francoisromain/pen/dzoZZj

svg.innerHTML = svgPath(points, bezierCommand)

我创建的一个示例路径如下:

  1. <svg width="100px" height="100px" viewBox="100 200 500 400">
  2. <path d="M 346,186.5 C 374,208.875 452,237.875 458,276 C 464,314.125 433.75,340.5 370,339 C 306.25,337.5 253,307.75 203,270 C 153,232.25 134.75,209 170,188 C 205.25,167 300.5,186.5 344,186">
  3. </svg>

到目前为止,一切都运行良好,直到我要关闭线条。如果我添加 Z,它会突然连接这些点。

如果需要关闭线条,关闭应该是一个曲线,以便最后一个点到第一个点以及第二个点之间有一个平滑的过渡...也就是整个路径成为一个“平滑”的环。

我尝试添加贝塞尔曲线,但连接点最终成为一个尖锐的凸起。我还尝试在第一个点附近添加另一个点,然后使用 Z,但同样出现尖锐凸起。

应该如何做到这一点呢?

英文:

I'm working on some code where a user drops a number or points/markers and a Bezier curved SVG line is drawn through them (so a smooth line connects all the points). It's based off

https://codepen.io/francoisromain/pen/dzoZZj

svg.innerHTML = svgPath(points, bezierCommand)

An example path that I've created is:

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-html -->

  1. &lt;svg width=&quot;100px&quot; height=&quot;100px&quot; viewBox=&quot;100 200 500 400&quot;&gt;
  2. &lt;path d=&quot;M 346,186.5 C 374,208.875 452,237.875 458,276 C 464,314.125 433.75,340.5 370,339 C 306.25,337.5 253,307.75 203,270 C 153,232.25 134.75,209 170,188 C 205.25,167 300.5,186.5 344,186&quot;&gt;
  3. &lt;/svg&gt;

<!-- end snippet -->

So far it's working well till I get to closing the line. If I add Z it abruptly joins the points.

The closing needs to be (if necessary) a curve so that there's a gentle transition from the last point to the first and on to the second... i.e. the whole path becomes a "smooth" loop.

I tried adding a Bezier curve but the join ends up being a sharp bump. And I tried adding another point close to the first and then using Z but again I get a sharp bump.

How can this be done?

答案1

得分: 1

我修改了原始函数,以返回一个 pathData 数组(根据 SVG 2 SVGPathData 接口草案),而不是一个 d 属性字符串。
这样,我们可以轻松地操作和排序命令。

我还改变了坐标结构,使我们使用 点对象

  1. [
  2. { x: 344, y: 186 }
  3. ]

而不是

  1. [
  2. [ 344, 186 ]
  3. ]

这非常方便,因为大多数原生方法,如 getPointAtLength() 或属性,如 points,也返回坐标对象。

通过附加前2个点来扩展折线

  1. points = [
  2. { x: 344, y: 186 },
  3. { x: 458, y: 276 },
  4. { x: 370, y: 339 },
  5. { x: 203, y: 270 },
  6. { x: 170, y: 188 },
  7. // 从开头复制的点
  8. { x: 344, y: 186 },
  9. { x: 458, y: 276 }
  10. ];

如果是闭合路径,附加前2个点:

  1. if (closed) {
  2. points = points.concat(points.slice(0, 2));
  3. }

这样函数将在第一个/最后一个和第二个顶点之间创建一个平滑曲线。

使用JavaScript平滑关闭一个SVG路径

显然,这个路径有一个重叠的曲线段,我们需要删除它。此外,第一个 C 需要一些调整。

复制最后的 C 命令的第一个控制点并删除最后的命令

我们可以复制最后一个命令的第一个控制点到第一个 C 命令。

  1. // 复制最后一个命令的第一个控制点到第一个 curveto
  2. if (closed) {
  3. let comLast = pathData[pathData.length - 1];
  4. let valuesLastC = comLast.values;
  5. let valuesFirstC = pathData[1].values;
  6. pathData[1] = {
  7. type: "C",
  8. values: [valuesLastC[0], valuesLastC[1], valuesFirstC.slice(2)].flat()
  9. };
  10. // 删除最后一个 curveto
  11. pathData = pathData.slice(0, pathData.length - 1);
  12. }
英文:

I modified the original function to return a pathData (according to the SVG 2 SVGPathData interface draft) the array instead of a d attribute string.
This way, we can easily manipulate and sort commands.

I've also changed the coordinate structure so we're working with point objects.

  1. [
  2. { x: 344, y: 186 }
  3. ]

instead of

  1. [
  2. [ 344, 186 ]
  3. ]

This pretty handy, since most native methods like getPointAtLength() or properties like points also return coordinates as objects

Extend the polyline by appending the first 2 point to the point array

  1. points = [
  2. { x: 344, y: 186 },
  3. { x: 458, y: 276 },
  4. { x: 370, y: 339 },
  5. { x: 203, y: 270 },
  6. { x: 170, y: 188 },
  7. //duplicated points from the beginning
  8. { x: 344, y: 186 },
  9. { x: 458, y: 276 },
  10. ];
  1. // append first 2 points for closed paths
  2. if (closed) {
  3. points = points.concat(points.slice(0, 2));
  4. }

This way the function will create a smooth curve between first/last and second vertice.

使用JavaScript平滑关闭一个SVG路径

Obviously, this path has an overlapping curve segment, we need to remove. Besides, the first C and needs some adjustment.

Copy last C commands 1. control point and delete last command

We can copy the last command's 1st control point to the first C command.

  1. // copy last commands 1st controlpoint to first curveto
  2. if (closed) {
  3. let comLast = pathData[pathData.length - 1];
  4. let valuesLastC = comLast.values;
  5. let valuesFirstC = pathData[1].values;
  6. pathData[1] = {
  7. type: &quot;C&quot;,
  8. values: [valuesLastC[0], valuesLastC[1], valuesFirstC.slice(2)].flat()
  9. };
  10. // delete last curveto
  11. pathData = pathData.slice(0, pathData.length - 1);
  12. }

<!-- begin snippet: js hide: false console: false babel: false -->

<!-- language: lang-js -->

  1. let points = [
  2. { x: 344, y: 186 },
  3. { x: 458, y: 276 },
  4. { x: 370, y: 339 },
  5. { x: 203, y: 270 },
  6. { x: 170, y: 188 }
  7. ];
  8. let smoothing = 0.3;
  9. let pathData = getCurvePathData(points, smoothing, true);
  10. // serialize pathData to d attribute string
  11. let d = pathDataToD(pathData, 1);
  12. path.setAttribute(&quot;d&quot;, d);
  13. // Render the svg &lt;path&gt; element
  14. function getCurvePathData(points, smoothing = 0.2, closed=true){
  15. // append first 2 points for closed paths
  16. if (closed) {
  17. points = points.concat(points.slice(0, 2));
  18. }
  19. // Properties of a line
  20. const line = (pointA, pointB) =&gt; {
  21. const lengthX = pointB.x - pointA.x;
  22. const lengthY = pointB.y - pointA.y;
  23. return {
  24. length: Math.sqrt(Math.pow(lengthX, 2) + Math.pow(lengthY, 2)),
  25. angle: Math.atan2(lengthY, lengthX)
  26. };
  27. };
  28. // Position of a control point
  29. const controlPoint = (current, previous, next, reverse) =&gt; {
  30. const p = previous || current;
  31. const n = next || current;
  32. const o = line(p, n);
  33. const angle = o.angle + (reverse ? Math.PI : 0);
  34. const length = o.length * smoothing;
  35. const x = current.x + Math.cos(angle) * length;
  36. const y = current.y + Math.sin(angle) * length;
  37. return { x, y };
  38. };
  39. let pathData = [];
  40. pathData.push({ type: &quot;M&quot;, values: [points[0].x, points[0].y] });
  41. for (let i = 1; i &lt; points.length; i++) {
  42. let point = points[i];
  43. const cp1 = controlPoint(points[i - 1], points[i - 2], point);
  44. const cp2 = controlPoint(point, points[i - 1], points[i + 1], true);
  45. //console.log( i, &#39;a&#39;, a)
  46. const command = {
  47. type: &quot;C&quot;,
  48. values: [cp1.x, cp1.y, cp2.x, cp2.y, point.x, point.y]
  49. };
  50. pathData.push(command);
  51. }
  52. // copy last commands 1st controlpoint to first curveto
  53. if (closed) {
  54. let comLast = pathData[pathData.length - 1];
  55. let valuesLastC = comLast.values;
  56. let valuesFirstC = pathData[1].values;
  57. pathData[1] = {
  58. type: &quot;C&quot;,
  59. values: [valuesLastC[0], valuesLastC[1], valuesFirstC.slice(2)].flat()
  60. };
  61. // delete last curveto
  62. pathData = pathData.slice(0, pathData.length - 1);
  63. }
  64. return pathData;
  65. };
  66. // convert pathdata to d attribute string
  67. function pathDataToD(pathData, decimals=3){
  68. let d = pathData
  69. .map((com) =&gt; {
  70. return `${com.type}${com.values.map(value=&gt;{return +value.toFixed(decimals)}).join(&quot; &quot;)}`;
  71. })
  72. .join(&quot; &quot;);
  73. return d;
  74. }

<!-- language: lang-html -->

  1. &lt;svg viewBox=&quot;0 0 500 500&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; id=&quot;svg&quot;&gt;
  2. &lt;path id=&quot;pathPoly&quot; fill=&quot;none&quot; stroke=&quot;green&quot;&gt;&lt;/path&gt;
  3. &lt;path id=&quot;path&quot; fill=&quot;none&quot; stroke=&quot;#000&quot;&gt;&lt;/path&gt;
  4. &lt;/svg&gt;

<!-- end snippet -->

See original post by François Romain: Smooth a Svg path with cubic bezier curves)

huangapple
  • 本文由 发表于 2023年7月17日 17:09:11
  • 转载请务必保留本文链接:https://go.coder-hub.com/76702965.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定