阅读之前你需要知道的知识包括
由于二次贝塞尔曲线绘制后,将只有一处弯曲,在多节点连接时,呈现效果很差。并且在45°,135°,225°,315°时,需要做特殊的处理,否侧得到的曲线的弧度过大。
在确定使用三次贝塞尔曲线后,需要通过计算得出曲线绘制时的两个控制点C1,C2。然后通过CanvasRenderingContext2D.bezierCurveTo进行绘制。
由于我们需要两个控制点,所以,我们将会把起点SP(start point)和终点EP(end point)间的连线S-E均分为4份。得到如下点: $$ \begin{align*} Split_{m} = (\frac{(X_{SP}+X_{EP})}2,\frac{(Y_{SP}+Y_{EP})}2)\ \end{align*} $$ 得到S-E的公式L(x)为 $$ L(x) = \frac{X_{Split_{m}}}{Y_{Slit_{m}}}x $$ 根据L(x)可知S-E的斜率满足 $$ \tan \theta = \frac{X_{Split_{m}}}{Y_{Slit_{m}}} $$ 然后将$Split_{m}$作为坐标系的原点,建立直角坐标系,得到 $$ \begin{align*} len = \sqrt{(X_{Split_{m}}-X_{SP})^{2}+(Y_{Split_{m}}-Y_{SP})^{2}}\ \ \theta = \arctan \frac{X_{Split_{m}}}{Y_{Slit_{m}}}\ \ Y_{offset} = len·\cos \theta \ \ C1=(X_{Split_{m}},Y_{Split_{m}}-len)\ C2=(X_{Split_{m}},Y_{Split_{m}}+len) \end{align*} $$
typescript/** * @param props * @typeof props { start: number[]; end: number[]; canvas: CanvasRenderingContext2D; } */ export const drawLine = (props: Common.LineProps) => { const { start, end, canvas: ctx, color } = props; const getMidCoord = (c1: number, c2: number) => { if (c1 === c2) { return c1; } return (c1 + c2) / 2; }; const [x1, y1] = start; const [x2, y2] = end; const [midX, midY] = [getMidCoord(x1, x2), getMidCoord(y1, y2)]; const drawMirror = (y1: number, y2: number) => { if (y1 > y2) { return ctx.bezierCurveTo(control2[0], control2[1], control1[0], control1[1], end[0], end[1]); } else { return ctx.bezierCurveTo(control1[0], control1[1], control2[0], control2[1], end[0], end[1]); } }; const degCos = Math.cos(Math.atan((x1 - midX) / (y1 - midY))); const lineLen = Math.sqrt(Math.pow(y1 - midY, 2) + Math.pow(x1 - midX, 2)) * 2; const control1 = [midX, midY - degCos * (lineLen / 2)]; const control2 = [midX, midY + degCos * (lineLen / 2)]; ctx.beginPath(); ctx.moveTo(start[0], start[1]); drawMirror(y1, y2); ctx.lineWidth = 2; ctx.strokeStyle = color ? color : "#000"; ctx.stroke(); ctx.closePath(); };