flo*_*her 13 javascript svg smooth path
我正在寻找一种解决方案,将一个徒手的,用户绘制的SVG路径转换为更平滑的路径,该路径由许多auf LineTo段组成.
首选语言是JavaScript,但欢迎任何建议.
jor*_*aul 29
首先,我建议使用一个好的图形库,如raphael.它将简化实际使用javascript执行绘图的过程.
一种非常简单的平滑方法是使用等效的curveto命令转换所有lineto命令,并根据每个线段的角度计算一些控制点.例如,
<svg width="1000" height="1000" version="1.1"
xmlns="http://www.w3.org/2000/svg">
<path d="
M250 150
L150 350
L350 350
L250 150
" />
</svg>
Run Code Online (Sandbox Code Playgroud)
变
<svg width="1000" height="1000" version="1.1"
xmlns="http://www.w3.org/2000/svg">
<path d="
M250 150
C250 150 150 350 150 350
C150 350 350 350 350 350
C350 350 250 150 250 150
" />
</svg>
Run Code Online (Sandbox Code Playgroud)
这两者都应该绘制一个等边三角形
下一步是计算控制点的位置.通常,您希望平滑角两侧的控制点落在穿过顶点的假想线上.在等边三角形的顶点的情况下,这将是水平线.经过一些操作,你可以得到这样的东西:
<svg width="1000" height="1000" version="1.1"
xmlns="http://www.w3.org/2000/svg">
<path d="
M250 150
C230 150 140 333 150 350
C160 367 340 367 350 350
C360 333 270 150 250 150
" />
</svg>
Run Code Online (Sandbox Code Playgroud)
棘手的部分是计算控制点,但这变成了一个简单的触发问题.正如我之前提到的,这里的目标是将两个控制点放在一条平分角点顶点的线上.例如,假设我们有两个线段:
A. (0,0) to (3,2)
B. (0,0) to (1,-4)
the absolute angle of A is arctan(2/3) = 33.69 deg
the absolute angle of B is arctan(-4/1) = -75.96 deg
the bisection angle of AB is (33.69 + -75.96)/2 = -21.135
the tangent angle is AB is (-21.135 + 90) = 68.865
Run Code Online (Sandbox Code Playgroud)
知道切线角度后,我们就可以计算出控制点的位置
smoothness = radius = r
tangent angle = T
Vertex X = Xv
Vertex Y = Yv
Control Point 1:
Xcp1 = cos(T)*r
Ycp1 = sin(T)*r
Control Point 2:
Xcp2 = cos(T)*(-r)
Ycp2 = sin(T)*(-r)
Run Code Online (Sandbox Code Playgroud)
最后一个问题是将每个控制点放在实际的curveTo命令中:
CX1 Y1 X2 Y2 X3 Y3
Run Code Online (Sandbox Code Playgroud)
X3和Y3定义顶点位置.X1 Y1和X2 Y2定义控制点.您可以将X1 Y1视为定义如何进入顶点的向量,将X2 Y2定义为定义如何离开的向量.既然你有两个控制点,你必须决定
CXcp1 Ycp1 Xcp2 Ycp2 0 0
Run Code Online (Sandbox Code Playgroud)
要么
CXcp2 Ycp2 Xcp1 Ycp1 0 0
Run Code Online (Sandbox Code Playgroud)
这是一个重要的决定.如果你向后移动它们,形状将看起来像一个循环.到目前为止,您应该能够确定如何做出这个决定......
同样,这是一个非常简单的解决方案,但它往往看起来很适合手绘路径.一个更好的解决方案可能会更进一步,并将交叉点向内移向每个线段交叉点的凹面部分.这更具挑战性.
假设用户绘图是一个元组数组,我们可以这样做
const points = [[100, 50], [50, 15], [5, 60], [10, 20], [20, 10], [30, 190], [40, 10], [50, 60], [60, 120], [70, 10], [80, 50], [90, 50], [120, 10], [150, 80], [160, 10] ]
const lineProperties = (pointA, pointB) => {
const lengthX = pointB[0] - pointA[0]
const lengthY = pointB[1] - pointA[1]
return {
length: Math.sqrt(Math.pow(lengthX, 2) + Math.pow(lengthY, 2)),
angle: Math.atan2(lengthY, lengthX)
}
}
const controlPointCalc = (current, previous, next, reverse) => {
const c = current
const p = previous ? previous : c
const n = next ? next : c
const smoothing = 0.2
const o = lineProperties(p, n)
const rev = reverse ? Math.PI : 0
const x = c[0] + Math.cos(o.angle + rev) * o.length * smoothing
const y = c[1] + Math.sin(o.angle + rev) * o.length * smoothing
return [x, y]
}
const svgPathRender = points => {
const d = points.reduce((acc, e, i, a) => {
if (i > 0) {
const cs = controlPointCalc(a[i - 1], a[i - 2], e)
const ce = controlPointCalc(e, a[i - 1], a[i + 1], true)
return `${acc} C ${cs[0]},${cs[1]} ${ce[0]},${ce[1]} ${e[0]},${e[1]}`
} else {
return `${acc} M ${e[0]},${e[1]}`
}
},'')
return `<path d="${d}" fill="none" stroke="black" />`
}
const svg = document.querySelector('.svg')
svg.innerHTML = svgPathRender(points)Run Code Online (Sandbox Code Playgroud)
<svg viewBox="0 0 200 200" version="1.1" xmlns="http://www.w3.org/2000/svg" class="svg">
</svg>Run Code Online (Sandbox Code Playgroud)
详细解释见本文。