使用三次贝塞尔曲线平滑SVG路径
创作时间:
作者:
@小白创作中心
使用三次贝塞尔曲线平滑SVG路径
引用
CSDN
1.
https://blog.csdn.net/qq_40646695/article/details/138533014
在SVG元素中绘制直线相对简单,但要实现平滑曲线则需要一些三角学知识。本文将详细介绍如何使用三次贝塞尔曲线来平滑SVG路径,并通过具体代码示例展示实现过程。
我们有一个表示线段点坐标的数组:
const points = [[5, 10], [10, 40], [40, 30], [60, 5], [90, 45], [120, 10], [150, 45], [200, 10]];
HTML页面中的SVG元素:
<svg viewBox="0 0 200 200" version="1.1" xmlns="http://www.w3.org/2000/svg" class="svg"></svg>
我们想从点数组中创建一个<path>
元素。
创建path由这个点
<path>
元素的d
属性始终以移动到命令:M x,y
开头,后面跟着几个命令,具体取决于形状的类型。结果类似于:
<path d="M 10,20 L 15,25 L 20,35">
表示直线。首先,让我们创建一个通用的svgPath
函数,它有两个参数:point
数组和command
函数。
// Render the svg <path> element
// I: - points (array): points coordinates
// - command (function)
// I: - point (array) [x,y]: current point coordinates
// - i (integer): index of 'point' in the array 'a'
// - a (array): complete array of points coordinates
// O: - (string) a svg path command
// O: - (string): a Svg <path> element
const svgPath = (points, command) => {
// build the d attributes by looping over the points
const d = points.reduce((acc, point, i, a) => i === 0
// if first point
? `M ${point[0]},${point[1]}`
// else
: `${acc} ${command(point, i, a)}`
, '');
return `<path d="${d}" fill="none" stroke="grey" />`;
};
现在,让我们创建两个命令函数:
lineCommand
:画直线。bezierCommand
:画平滑的线。
画直线
直线需要指令line to
,以字母L
开头,后跟终点x、y
的坐标。绘制直线的基本lineCommand
函数:
// Svg path line command
// I: - point (array) [x, y]: coordinates
// O: - (string) 'L x,y': svg line command
const lineCommand = point => `L ${point[0]} ${point[1]}`;
现在我们可以用它从点数组中画一条线:
const svg = document.querySelector('.svg');
svg.innerHTML = svgPath(points, lineCommand);
这给出了以下结果:
画平滑的曲线
三次贝塞尔曲线命令以字母C
开头,后跟三对坐标x1,y1 x2,y2 x,y
:
x1,y1
:起始控制点坐标x2,y2
:终点控制点坐标x,y
:结束锚点的坐标
需要注意:
- 起始锚点坐标由上一个命令给出。
- 结束锚点坐标来自原始点数组。
- 现在我们必须找到两个控制点的位置。
找到控制点的位置
我们用一条线将起始锚点和结束锚点周围的锚点连接起来(我们将其称为"对立线"):
为了使线条平滑,每个控制点的位置必须相对于其"对立线":
- 控制点位于与"对立线"平行且与当前锚点相切的线上。
- 在该切线上,从锚点到控制点的距离取决于"对立线"的长度和任意"平滑比"。
- 起始控制点与"对立线"的方向相同,而结束控制点则向后。
首先,找到"对立线"属性的函数:
// Properties of a line
// I: - pointA (array) [x,y]: coordinates
// - pointB (array) [x,y]: coordinates
// O: - (object) { length: l, angle: a }: properties of the line
const line = (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)
};
};
然后,找到控制点位置的函数:
// Position of a control point
// I: - current (array) [x, y]: current point coordinates
// - previous (array) [x, y]: previous point coordinates
// - next (array) [x, y]: next point coordinates
// - reverse (boolean, optional): sets the direction
// O: - (array) [x,y]: a tuple of coordinates
const controlPoint = (current, previous, next, reverse) => {
// When 'current' is the first or last point of the array
// 'previous' or 'next' don't exist.
// Replace with 'current'
const p = previous || current;
const n = next || current;
// The smoothing ratio
const smoothing = 0.2;
// Properties of the opposed-line
const o = line(p, n);
// If is end-control-point, add PI to the angle to go backward
const angle = o.angle + (reverse ? Math.PI : 0);
const length = o.length * smoothing;
// The control point position is relative to the current point
const x = current[0] + Math.cos(angle) * length;
const y = current[1] + Math.sin(angle) * length;
return [x, y];
};
创建贝塞尔曲线的函数C
命令:
// Create the bezier curve command
// I: - point (array) [x,y]: current point coordinates
// - i (integer): index of 'point' in the array 'a'
// - a (array): complete array of points coordinates
// O: - (string) 'C x2,y2 x1,y1 x,y': SVG cubic bezier C command
const bezierCommand = (point, i, a) => {
// start control point
const [cpsX, cpsY] = controlPoint(a[i - 1], a[i - 2], point);
// end control point
const [cpeX, cpeY] = controlPoint(point, a[i - 1], a[i + 1], true);
return `C ${cpsX},${cpsY} ${cpeX},${cpeY} ${point[0]},${point[1]}`;
};
最后,我们重用svgPath
函数来循环数组的点并构建<path>
元素。然后我们将<path>
附加到<svg>
元素。
const svg = document.querySelector('.svg');
svg.innerHTML = svgPath(points, bezierCommand);
热门推荐
C-TIRADS3类甲状腺结节严重吗?医生这样说
化学反应与热能:从基础概念到实际应用
数字化转型的战略规划应该怎么做?(附IBM-IT战略规划方法论PPT下载)
抗日战争全民族反日本侵略,加强了团结,奉献了生命,觉醒了意识
北京多家银行收紧无卡取款,忘带卡可用手机银行扫码取款
高校年薪很多“水分”?进校后,到手的钱可能砍了一半
MSDS是什么-MSDS报告有哪些内容
你要的高级词汇 (289): impinge
PS怎么用鼠标放大缩小:详细教程与实用技巧
2024房产做抵押担保怎么办手续
解密跑鞋中底材质:性能解析与选购指南
痛风炎症:造模方法分析、机制研究和治疗靶点!
《永劫无间》更新支持DLSS 4多帧生成,新驱动和Reflex更新发布
短线交易必杀技!15分钟K线战法,快速收割市场波动
如何理解香港黄金重量的换算?这些换算如何影响交易决策?
三口之家如何选择适合的户型
国产剧大女主发型,为什么多是二八左偏分
北京医保码已覆盖90%以上参保人,实现就医结算全覆盖
行政规范性文件:定义、特点与作用
MS Excel: 高亮当前行列 - 保持原有格式不被改变
单细胞测序的实验流程(上):单细胞的分离制备
杭州楼市小阳春提前到来,二手房带看量指标飙升
再审中驳回起诉如何救济
销售面试如何做客户管理
戏曲乐队的“文武场”相互配合,为戏曲表演营造出独特的音乐氛围和艺术效果
久咳不止怎么办?镇咳药使用指南来了
荞麦面真的狂吃不胖吗?
安心游览布拉格的 10 个小贴士
解“肌无力”之谜,轻松搞定爱车爬坡难题
生存技能有哪些?