js原生---绘制贝塞尔曲线

利用这个就可以自己在线绘制贝塞尔曲线~还可以调整节点位置

主要思路

关于渲染:用两层canvas渲染,第一层是静止渲染,只渲染静态不变的线段和圆点,第二层是动态渲染,渲染点的运动形成的曲线运动情况。
关于曲线的构成:根据贝塞尔函数
贝塞尔函数

step1:

用数组存储所有点击的节点,同时canvas绘制线段和圆点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//绘制线段
if (len > 0 && isLegalPoint(point)) {
ctx1.strokeStyle = 'hsl(0, 0%, 50%)';
ctx1.moveTo(pointArr[len - 1].x, pointArr[len - 1].y);
ctx1.lineTo(point.x, point.y);
ctx1.stroke();
ctx1.closePath();
}

///绘制圆点
ctx1.beginPath();
ctx1.fillStyle = 'hsl(0, 0%, 50%)';
ctx1.fillText('[' + point.x + ',' + point.y + ']', 15, 25 * (len + 1));
ctx1.arc(point.x, point.y, 3, 0, Math.PI * 2);
ctx1.fill();
ctx1.closePath();

step2:

每两个点利用贝塞尔函数求出点下一步的位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//利用递归实现 + t值的变化得出每个点的坐标
function bezier(arr, t) {
var x = 0,
y = 0;
var n = arr.length - 1;
arr.forEach(function (p, index) {
if (!index) {
x += p.x * Math.pow(1 - t, n - index) * Math.pow(t, index);
y += p.y * Math.pow(1 - t, n - index) * Math.pow(t, index);
} else {
x += factorial(n) / factorial(index) / factorial(n - index) * p.x * Math.pow(1 - t, n - index) * Math.pow(t, index);
y += factorial(n) / factorial(index) / factorial(n - index) * p.y * Math.pow(1 - t, n - index) * Math.pow(t, index);
}
})
return {
x: x,
y: y
}
}

function factorial(n) {
if (n <= 1) {
return 1;
} else {
return n * factorial(n - 1);
}
}

step3:

由step2求得的贝塞尔点进行两两绘制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
function drawNodeLine(nodeArr, t, item) {

var nodes = nodeArr;

if (nodeArr.length === count) {
nodeArr.forEach(function (node, index) {
ctx2.fillStyle = '#696969';
ctx2.fillText('[' + node.x + ',' + node.y + ']', 15, 25 * (index + 1));
})
}
if (nodes.length === 1) {
bezierNode.push(nodes[0]);
bezierNode.forEach(function (node, index) {
if (index) {
ctx2.beginPath();
ctx2.strokeStyle = '#af0000';
ctx2.moveTo(bezierNode[index - 1].x, bezierNode[index - 1].y);
ctx2.lineTo(node.x, node.y);
ctx2.stroke();
}
})

}
//绘制线段和圆点
nodes.forEach(function (node, index) {
ctx2.beginPath();
ctx2.fillStyle = color[item % 6];
ctx2.arc(node.x, node.y, 3, 0, Math.PI * 2);
ctx2.fill();
if (index) {
ctx2.beginPath();
ctx2.strokeStyle = color[item % 6];
ctx2.moveTo(nodes[index - 1].x, nodes[index - 1].y);
ctx2.lineTo(node.x, node.y);
ctx2.stroke();
}
})

//求得bezierNode的坐标
if (nodes.length > 1) {
var nextNode = [];
for (var i = 0; i < nodes.length - 1; i++) {
var arr = [
{
x: nodes[i].x,
y: nodes[i].y
},
{
x: nodes[i + 1].x,
y: nodes[i + 1].y
}
];
nextNode.push(bezier(arr, t));
}
item++;
drawNodeLine(nextNode, t, item);
}

}

step4:

最后的拖拽实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
function move() {
var num, isDrag = false;
box.onmousedown = function (e) {
var pos = {
x: e.offsetX,
y: e.offsetY
}
pointArr.forEach(function (node, index) {
if (Math.abs(node.x - pos.x) < 5 && Math.abs(node.y - pos.y) < 5) {
isDrag = true;
num = index;
}
})
box.onmousemove = function (e1) {
if (!isDrag) { return; }
ctx2.clearRect(0, 0, 800, 600);
pointArr[num].x = e1.offsetX;
pointArr[num].y = e1.offsetY;
pointArr.forEach(function (node, index) {
ctx2.fillStyle = '#696969';
ctx2.fillText('[' + node.x + ',' + node.y + ']', 15, 25 * (index + 1));

ctx2.beginPath();
ctx2.fillStyle = 'hsl(0, 0%, 50%)';
ctx2.arc(node.x, node.y, 3, 0, Math.PI * 2);
ctx2.fill();
ctx2.closePath();

if (index) {
ctx2.beginPath();
ctx2.strokeStyle = 'hsl(0, 0%, 50%)';
ctx2.moveTo(pointArr[index - 1].x, pointArr[index - 1].y);
ctx2.lineTo(node.x, node.y);
ctx2.stroke();
ctx2.closePath();
}

})
var bn = [];
for (var i = 0; i < 1; i += 0.01) {
bn.push(bezier(pointArr, i));
console.log(bn);
}
bn.forEach(function (b, index) {
if (index) {
ctx2.beginPath();
ctx2.strokeStyle = 'red';
ctx2.moveTo(bn[index - 1].x, bn[index - 1].y);
ctx2.lineTo(b.x, b.y);
ctx2.closePath();
ctx2.stroke();
}
})
}
box.onmouseup = function () {
box.onmousemove = null;
isDrag = false;
}
}

}

难点分析:
取三个点为例,每两个点之间就会得出一个运动点,所以在三个点的情况,需要递归两次,来求出两个点的下一步位置,以及通过这两个点获取到的贝塞尔曲线位置

1.gif

2.gif

附上源码