贝塞尔曲线并非是由贝塞尔发明的,但是是因为他把这个东西应用到当时的汽车领域而闻名的,所以取名为贝塞尔曲线。
在我看来,用简单的话来理解一下贝塞尔曲线,他是通过少量几个点,使用一套公式,生成一条平滑曲线。
原理
先盗用人家的图,嘿嘿。
二阶贝塞尔曲线,一个控制点
三阶贝塞尔曲线,二个控制点
一阶贝塞尔曲线,就是一条直线
为了完整性,我给出贝塞尔曲线的n阶通式
想看这个公式推导,我给出一个文章链接 n公式推导推导。 但是在一般应用中,二阶,三阶贝塞尔曲线是已经够用了。应用
先简单的来使用一下,通过公式来描绘曲线。
***
d2(){
this.name = '二次贝赛尔曲线方程';
let _this = this;
let oCanvas = document.querySelector("#canvas"),
oGc = oCanvas.getContext('2d');
let percent = 0;
function animate() {
oGc.clearRect(0, 0, 800, 800);
oGc.beginPath();
oGc.strokeStyle = 'red';
oGc.moveTo( 40, 80 );
//oGc.quadraticCurveTo( 137, 80, 140, 280 );
_this.d2_(oGc,[40, 80],[137, 80],[140, 280],percent);
oGc.stroke();
percent = (percent + 1) % 100;
requestAnimationFrame(animate);
}
animate()
},
d2_(oGc,start,cp,end, percent){
for (var t = 0; t <= percent / 100; t += 0.01) {
var x = this.quadraticBezier(start[0], cp[0], end[0], t);
var y = this.quadraticBezier(start[1], cp[1], end[1], t);
oGc.lineTo(x, y);
}
},
quadraticBezier(p0, p1, p2, t) {
var k = 1 - t;
return k * k * p0 + 2 * (1 - t) * t * p1 + t * t * p2; // 这个方程就是二次贝赛尔曲线方程
},
***
复制代码
这个就是根据公式描述出相关的点,然后连接起来。 但是在实际应用中,很大程度上会在canvas中绘图,canvas提供2个api,
quadraticCurveTo:二阶贝塞尔曲线,参数是 控制点,结束点
bezierCurveTo :三阶贝塞尔曲线,参数是 控制点1,控制点2,结束点
你们发现没,它们没有开始点,它们的开始点是画笔开始的位置。
在举一个例子,画起伏波浪
直接讲思路,就是先画一个静止的波浪
好,现在来看一下,这个该怎么入手,先把这个轮廓描绘出来,要描绘,先拆分, 它是由一条曲线,3条直接拼接而成,有了这个思路,已经完成了一半, 那条曲线该如何绘制,其实我觉得思路不止一种,我们应该先自己给这个曲线下定义,我认为他应该是半圆的弧连接,应该是椭圆的弧链接,应该是其他。我先给它下一个定义我用二阶和三阶分别来描述这个曲线,1,2,3,4这4个点描述出来了,那么这个曲线也就绘制完成了
1: (0.5d,waveH)
2: (d, 0)
3: (1.5d,-waveH)
4: (2d,0)
我选择的这个规则是很中规中矩的,上一个波形是画2个二阶贝塞尔曲线,下一个波形是画一个3阶贝塞尔曲线。这个就可以把静止的波形给绘制出来了,然后你想象一个给这个坐标加横向偏移,加纵向偏移,他就可以起伏波动了
***
init2(){
this.name = '2阶';
let c = document.getElementById("myCanvas"),
ctx = c.getContext("2d"),
waveWidth = 800,
offset = 0, //x
waveHeight = 20, // 波浪大小
waveCount = 5,
startX = -200,
startY = 208,
progress = 0, //高度
progressStep = 0.5,
d2 = waveWidth / waveCount,
d = d2 / 2,
hd = d / 2;
ctx.fillStyle = "rgba(0,222,255, 0.2)";
function tick() {
offset -= 4; // x 移动
progress += progressStep;
if (progress > 220 || progress < 0) progressStep *= -1;
if (-1 * offset === d2) offset = 0;
ctx.clearRect(0, 0, c.width, c.height);
ctx.beginPath();
let offsetY = startY - progress; //y 坐标高低
ctx.moveTo(startX - offset, offsetY);
for (var i = 0; i < waveCount; i++) {
var dx = i * d2;
var offsetX = dx + startX - offset;
ctx.quadraticCurveTo(offsetX + hd, offsetY + waveHeight, offsetX + d, offsetY);
ctx.quadraticCurveTo(offsetX + hd + d, offsetY - waveHeight, offsetX + d2, offsetY);
}
ctx.lineTo(startX + waveWidth, 300);
ctx.lineTo(startX, 300);
ctx.fill();
requestAnimationFrame(tick);
}
tick();
},
***
复制代码
上面是二阶贝塞尔曲线,用三阶画的话,就是
ctx.quadraticCurveTo(offsetX + hd, offsetY + waveHeight, offsetX + d, offsetY);
ctx.quadraticCurveTo(offsetX + hd + d, offsetY - waveHeight, offsetX + d2, offsetY);
换成
ctx.bezierCurveTo(offsetX + hd, offsetY + waveHeight, offsetX + d + hd, offsetY-waveHeight, offsetX + d2, offsetY );
就可以了。
其实我觉得贝塞尔曲线在使用过程中,最关键的是控制点的选择,不同点的选择,会展现不同的效果,但是选择控制点,是一件挺有意思的事。
下面我们再来看一个案例,粘性拖动
***
data() {
return {
radius: 7,
x: 300,//手移动
y: 300,//手移动
anchorX: 200,// 控制点
anchorY: 200,// 控制点
startX: 100, //开始
startY: 100,//开始
}
},
mounted() {
document.removeEventListener('touchstart', this.wrapTouchStart);
document.addEventListener("touchstart", this.wrapTouchStart);
document.removeEventListener('touchmove', this.wrapTouchMove);
document.addEventListener('touchmove', this.wrapTouchMove);
document.removeEventListener('touchend', this.wrapTouchEnd);
document.addEventListener('touchend', this.wrapTouchEnd);
document.removeEventListener('touchcancel', this.wrapTouchCancel);
document.addEventListener('touchcancel', this.wrapTouchCancel);
},
methods: {
wrapTouchStart(e) {},
wrapTouchMove(e) {
this.x = e.changedTouches[0].clientX;
this.y = e.changedTouches[0].clientY;
this.anchorX = (e.changedTouches[0].clientX + this.startX) / 2;
this.anchorY = (e.changedTouches[0].clientY + this.startY) / 2;
this.d2();
},
wrapTouchEnd() {
this.radius = 20;
// 手势坐标
this.x = 300;
this.y = 300;
// 控制点坐标
this.anchorX = 200;
this.anchorY = 200;
// 起点坐标
this.startX = 100;
this.startY = 100;
},
wrapTouchCancel() {
let oCanvas = document.querySelector("#canvas"),
ctx = oCanvas.getContext('2d');
ctx.clearRect(0, 0, 360, 600);
},
d2() {
let _this = this;
let oCanvas = document.querySelector("#canvas");
ctx = oCanvas.getContext('2d');
ctx.strokeStyle = 'red';
var distance = Math.sqrt(Math.pow(this.y - this.startY, 2) + Math.pow(this.x - this.startX, 2));
this.radius = -distance / 15 + 20;
// 当气泡拉到一定程度,断开链条且链条消失
//if (this.radius < 7) {
if(distance > 250){
ctx.clearRect(0, 0, 360, 600);
ctx.beginPath();
ctx.arc(this.x, this.y, 20, 0, 2 * Math.PI);
ctx.strokeStyle = 'red';
ctx.fill();
console.log('end');
return;
}
let sin = (this.x - this.startX) / distance;
let cos = (this.y - this.startY) / distance;
var x1 = this.startX - this.radius * cos;
var y1 = this.startY + this.radius * sin;
var x2 = this.x - 20 * cos;
var y2 = this.y + 20 * sin;
var x3 = this.x + 20 * cos;
var y3 = this.y - 20 * sin;
var x4 = this.startX + this.radius * cos;
var y4 = this.startY - this.radius * sin;
ctx.clearRect(0, 0, 360, 600);
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.quadraticCurveTo(this.anchorX, this.anchorY, x2, y2);
ctx.lineTo(x3, y3);
ctx.quadraticCurveTo(this.anchorX, this.anchorY, x4, y4);
ctx.lineTo(x1, y1);
ctx.fillStyle = 'red';
ctx.stroke();
ctx.fill();
// 两圆圈
ctx.beginPath();
ctx.arc(this.startX, this.startY, this.radius, 0, 2 * Math.PI)
ctx.arc(this.x, this.y, 20, 0, 2 * Math.PI)
ctx.strokeStyle = 'red';
ctx.fill();
},
}
***
复制代码
到这里,应该要结束了,但是我想说这控制点,其实还有其他选择,还有一种是是AC连线的中点,和BD连线的中点,具体的项目我晚一点附上地址。