贝塞尔曲线并非是由贝塞尔发明的,但是是因为他把这个东西应用到当时的汽车领域而闻名的,所以取名为贝塞尔曲线。
在我看来,用简单的话来理解一下贝塞尔曲线,他是通过少量几个点,使用一套公式,生成一条平滑曲线。
原理
先盗用人家的图,嘿嘿。
平面ABC 3个点。 在AB上找一个点D,在BC上找一个点E,使得AD:AB = BE:BC 然后在DE上找一个点F,使得DF:DE = AD:AB = BE:BC 接着,我们将D点从A点 --> B点慢慢移动,在这个过程中,会产生一系列的F点,将这些F点相连,就会形成一条曲线,嘿嘿,就是我们的贝塞尔曲线, 从这里可以看出,这里有3个关键点,起始点、终止点、控制点。 数学上的推理验证,这里就不讲了,直接给出公式。二阶贝塞尔曲线,一个控制点
三阶贝塞尔曲线,二个控制点
一阶贝塞尔曲线,就是一条直线
为了完整性,我给出贝塞尔曲线的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, //xwaveHeight = 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连线的中点,具体的项目我晚一点附上地址。
by cs