貝塞爾曲線并非是由貝塞爾發明的,但是是因為他把這個東西應用到當時的汽車領域而聞名的,所以取名為貝塞爾曲線。
在我看來,用簡單的話來理解一下貝塞爾曲線,他是通過少量幾個點,使用一套公式,生成一條平滑曲線。
原理
先盜用人家的圖,嘿嘿。
平面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 );_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, 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; 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; 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 );
就可以了。
其實我覺得貝塞爾曲線在使用過程中,最關鍵的是控制點的選擇,不同點的選擇,會展現不同的效果,但是選擇控制點,是一件挺有意思的事。
下面我們再來看一個案例,粘性拖動
要實現這個功能,來理一下思路,首先來描繪一下這個輪廓,一樣的套路,是不是,來,思考一下,這個圖形是由什么組成的。
畫的丑,別介意,這么看這個輪廓,是不是出來了,你可以想象,是由2個半圓的圓弧和2條曲線,可以先畫ABCD這個路徑,再畫2個圓,這樣這個輪廓就出來了。接下來,再看這個曲線如何完成。這個曲線開始和結束點已經有了,再找一個控制點也能畫出來,那么控制點在哪里,我下的定義簡單粗暴,在2圓心的鏈接線的終點,然后再把ABCD 4個點描述出來,這個路徑就解決了,如何描述ABCD,請允許我盜圖
如何讓這個圖形動起來,可以這么想第一個圓,可以是手開始觸摸的點,也可以自己先寫死,另一個圓是手拖動的位置,所以只要動態的改變第二個圓心的位置,那么這個拖動的效果就出來了。 我在拖動的時候,d的距離在改變,那么制定一個規則,d越大,第一個圓的半徑就越小,那么基本上就可以實現了。
***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(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
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎
總結
以上是生活随笔為你收集整理的贝塞尔曲线理解与应用的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。