貝塞爾曲線並非是由貝塞爾發明的,但是是因為他把這個東西應用到當時的汽車領域而聞名的,所以取名為貝塞爾曲線。
在我看來,用簡單的話來理解一下貝塞爾曲線,他是通過少量幾個點,使用一套公式,生成一條平滑曲線。
原理
先盜用人家的圖,嘿嘿。
平面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, //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連線的中點,具體的專案 github.com/chengchengc…
by cs