說明
寫這篇文章是因為某天看到這樣一個公式 r=a(1-cosθ)
,我上網搜了下,原來是笛卡爾心形線的極坐標方程,這個方程里面的確有一個浪漫又悲情的愛情故事,感興趣的朋友可以點這里看看,而至於這個故事是真是假,這 並不重要。
而這篇文章的目的是要用前端的方式,畫出笛卡爾心形線。
本來我想,這么經典的公式,網上應該已經有人實現過了吧。
我搜了搜,不得不佩服網友們,有 Java 實現的,有 C# 實現的,也有 canvas 實現的,還能用 ECharts 畫 ,可以學習學習。
好的,開始正文!
先來了解下心形線
心形線,是一個圓上的固定一點在它繞着與其相切且半徑相同的另外一個圓周滾動時所形成的軌跡,因其形狀像心形而得名。
因為 canvas 是直角坐標系的,所以先來看
平面直角坐標系 畫法
先貼出網上搜來的 心形線的平面直角坐標系方程表達式
分別為 x^2+y^2+a*x=a*sqrt(x^2+y^2)
和 x^2+y^2-a*x=a*sqrt(x^2+y^2
為什么會有兩個方程表達式?
因為心形線的水平方向 和 垂直方向 對應的方程表達式不同,而用相同的方程表達式畫的心形線,把每個點的 x 坐標和 y 坐標交換下,又會改變方向,所以會有兩個方程表達式。
好了,開始畫吧,看看這位朋友的做法
思路
根據方程表達式得到所有點的坐標,然后把每個點連接起來,然后填充,最后就行成一個心形了。
參數方程
x=a*(2*sin(t)+sin(2*t))
y=a*(2*cos(t)+cos(2*t))
x,y
分別表示一個點的 x 坐標 和 y 坐標,a
:是一個常數,用來控制心形的大小,t
:代表 弧度t
的取值范圍:-pi<=t<=pi 或 0<=t<=2*pi
代碼
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<canvas width="400" height="400"></canvas>
<script>
var canvas = document.querySelector('canvas');
var context = canvas.getContext('2d');
context.lineWidth = 3;
// 將畫布的原點(0,0),移動到(200,200)
// 移動原點是為了能讓整個心形顯示出來
context.translate(200,200);
// t 代表弧度
var t=0;
// maxt 代表 t 的最大值
var maxt = 2*Math.PI;
// vt 代表 t 的增量
var vt = 0.01;
// 需要循環的次數
var maxi = Math.ceil(maxt/vt);
// 保存所有點的坐標的數組
var pointArr=[];
// x 用來暫時保存每次循環得到的 x 坐標
var x=0;
// y 用來暫時保存每次循環得到的 y 坐標
var y=0;
// 根據方程得到所有點的坐標
for(var i=0;i<=maxi;i++){
// x=a*(2*sin(t)+sin(2*t))
x=50*(2*Math.sin(t)+Math.sin(2*t));
// y=a*(2*cos(t)+cos(2*t))
y=50*(2*Math.cos(t)+Math.cos(2*t));
t+=vt;
pointArr.push([x,y]);
}
// 根據點的坐標,畫出心形線
context.moveTo(pointArr[0][0],pointArr[0][1]);
draw();
function draw(){
context.fillStyle='#c00';
// 把每個點連接起來
for(var i=1;i<pointArr.length;i++){
x = pointArr[i][0];
y = pointArr[i][1];
context.lineTo(x,y);
}
context.fill();
}
</script>
</body>
</html>
效果圖
平面直角坐標系 畫法 (空心心形)
上面的代碼是畫一個實心的心形,當然我們也可以畫空心的,只需要做出一點點的修改就可以。
我們只需要改改 draw( ) 函數就好,把原來的 fill( ) 方法,改為 stroke( ) 方法,並且把 strokeStyle 設置了顏色就行了。
function draw(){
//context.fillStyle='#c00';
context.strokeStyle='#c00';
// 把每個點連接起來
for(var i=1;i<pointArr.length;i++){
x = pointArr[i][0];
y = pointArr[i][1];
context.lineTo(x,y);
}
//context.fill();
context.stroke();
}
極坐標系畫法
極坐標系是這樣的
極坐標系中確定一個點的位置,靠的是極點(圖中點O),和 角度 來確定的。
更多關於極坐標系的知識,可以看看這里
看看這位朋友的做法
思路
根據極坐標方程 r=a(1+sinθ)
,得到 r ,以 r 作為半徑,根據 r 連續的去畫圓弧,畫完一圈后,心形就出來了。
心形線 極坐標方程r=a(1+sinθ)
代碼
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<canvas width="400" height="400"></canvas>
<script>
var canvas = document.querySelector('canvas');
var context = canvas.getContext('2d');
// 將畫布的原點(0,0),移動到(200,100)
// 移動原點是為了能讓整個心形顯示出來
context.translate(200, 100);
// 畫心形
draw();
function draw() {
// 畫圓弧時,圓的半徑
var r = 0;
// start 代表畫弧線時的 起始角
var start = 0;
// end 代表畫弧線時的 結束角
var end = 0;
// 一個常數,用來控制心形的大小
var a = 100;
context.fillStyle = '#e21f27';
//連續的畫圓弧
for (var q = 0; q < 500; q++) {
start += Math.PI * 2 / 500;
// 當 結束角 是 Math.PI * 2 時也就是已經畫了一圈了,心形就出來了
end = start + Math.PI * 2 / 500;
// 根據極坐標方程 r=a(1+sinθ),得到 r(半徑)
r = a * (1 + Math.sin(start));
// 畫弧線
context.arc(0, 0, r, start, end, false);
}
context.fill();
}
</script>
</body>
</html>
效果圖
極坐標系 畫法 (空心心形)
用極坐標系 畫法,畫空心心形,也是一樣的需要改改 draw( ) 函數,把原來的 fill( ) 方法,改為 stroke( ) 方法,並且把 strokeStyle 設置了顏色就行了。
function draw() {
var r = 0;
var start = 0;
var end = 0;
var a = 100;
//context.fillStyle = '#e21f27';
context.strokeStyle = '#e21f27';
for (var i = 0; i < 500; i++) {
start += Math.PI * 2 / 500;
end = start + Math.PI * 2 / 500;
r = a * (1 + Math.sin(start));
context.arc(0, 0, r, start, end, false);
}
//context.fill();
// 改用 stroke() 方法
context.stroke();
}
可能你會覺得這樣的心形並不好看。
看看這個參數方程吧!
x=16 * (sin(t)) ^ 3;
y=13 * cos(t) - 5 * cos(2 * t) - 2 * cos(3 * t) - cos(4 * t)。
根據這個參數方程,用上面說的平面直角坐標系的畫法,把代碼里的方程換一下,就可以畫出這樣的心形。
代碼
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<canvas width="400" height="400"></canvas>
<script>
var canvas = document.querySelector('canvas');
var context = canvas.getContext('2d');
context.lineWidth = 3;
// 將畫布的原點(0,0),移動到(200,200)
// 移動原點是為了能讓整個心形顯示出來
context.translate(200,200);
// t 代表弧度
var t=0;
// vt 代表 t 的增量
var vt = 0.01;
// maxt 代表 t 的最大值
var maxt = 2*Math.PI;
// 需要循環的次數
var maxi = Math.ceil(maxt/vt);
// 保存所有點的坐標的數組
var pointArr=[];
// 控制心形大小
var size = 10;
// x 用來暫時保存每次循環得到的 x 坐標
var x=0;
// y 用來暫時保存每次循環得到的 y 坐標
var y=0;
// 根據方程得到所有點的坐標
for(var i=0;i<=maxi;i++){
// x=16 * (sin(t)) ^ 3;
var x = 16 * Math.pow(Math.sin(t),3);
// y=13 * cos(t) - 5 * cos(2 * t) - 2 * cos(3 * t) - cos(4 * t)
var y = 13 * Math.cos(t) - 5 * Math.cos(2 * t) -2 * Math.cos(3 * t)- Math.cos(4 * t);
t+=vt;
pointArr.push([x*size,-y*size]);
}
// 根據點的坐標,畫出心形線
context.moveTo(pointArr[0][0],pointArr[0][1]);
draw();
function draw(){
context.fillStyle='#c00';
// 把每個點連接起來
for(var i=1;i<pointArr.length;i++){
x = pointArr[i][0];
y = pointArr[i][1];
context.lineTo(x,y);
}
context.fill();
}
</script>
</body>
</html>
也許我們還可以再做點什么,比如加點動畫,看看下面這個吧。
點這里下載源碼,里面已經加了很詳細的注釋了。
總結
這篇文章主要是說用笛卡爾心形線方程畫心形,但是想要畫出心形的方式絕對是多種多樣的,單純的用CSS也可以,復雜點 用貝塞爾曲線也能畫出來,大家不妨去試試,說不定又有什么新發現呢。