本篇文章,將講述如何通過自定義的曲線函數,使用canvas的方式進行曲線的繪制。
為了通俗易懂,將以大家熟悉的橢圓曲線為例,進行橢圓的繪制。至於其他比較復雜的曲線,用戶只需通過數學方式建立起曲線函數,然后變換成為距離函數方程,替換即可。另外:代碼還沒進行任何優化。
(注:本文只適合那種能在一個點為原點、基於原點的每個角度只能存在一個點的曲線,通俗說就是,過原點作直線,與曲線相交的交點最多兩個,而且兩交點分別位於原點兩端。)
目錄結構
正文:
1、數學分析
首先講解下橢圓的構造,如圖所示,數學厲害的忽視這段。
======================================分割線,數學帝請忽略這一段==================================
橢圓構造圖
其中,OA,OB分別為半長軸和短長軸,通過此兩線段的距離能計算出半焦距FO的長度,再確定心O的坐標就能確認整個橢圓的范圍。
由此可知,清楚了OA,OB的距離,就能知道橢圓的形狀。
百度百科里面講到,
橢圓的標准方程有兩種,取決於焦點所在的坐標軸:
1)焦點在X軸時,標准方程為:x²/a²+y²/b²=1 (a>b>0)
2)焦點在Y軸時,標准方程為:y²/a²+x²/b²=1 (a>b>0)
其實兩個概念差不多一樣,將a 和 b在方程中的位置對調就行。不詳細講。
看好,我要變形了。→_→
假設θ為∠POF,P點距離x軸的長度為Y1的值,設為y;距離Y軸長度為X1值,設為X,P點基於O的坐標就是(x1,y1)。
tanθ = y/x
y = xtanθ
帶入橢圓方程 x²/a²+x2(tanθ)2/b²=1,解得(注:tanθ*tanθ,不記得是用tanθ2/tan2θ表示了,貌似是tan2θ,本文統一(tanθ)2表示)
x2 = a2b2 / (b2+a2(tanθ)2),帶入y = xtanθ,並進行化簡得出 x2+y2 = (a2b2(1+(tanθ)2)/ (b2+a2(tanθ)2),就是說,通過a,b,θ 能計算出OP 的距離來了。
======================================分割線,忽略了上面段的可以回來了====================================
2、曲線方程
因此,得到一個方程,返回的是OP的距離
/* func: ellipseFunc:return the length args: a:[number] ellipse's a b:[number] ellipse's b theta:[number] how much of Math.PI */ function ellipseFunc(a,b,theta) { // javascript Math對象下面的三角函數,傳入的theta值必須是轉換成弧度制的值,就是多少個3.141592…等等等的那個弧度制 return Math.pow(((a*a*b*b)*(1+Math.tan(theta)*Math.tan(theta)))/(b*b+a*a*Math.tan(theta)*Math.tan(theta)),1/2); }
3、畫一個點
既然曲線函數完畢,下面就行畫圖了,先從畫一個點來說,網上很對關於描繪一個點的帖子,我選了ctx.fillRect(x,y,a,b),這是繪制矩形的函數,x、y為繪制矩形的起點,就是左上角,設置a=b=1,就能繪制一個長寬各為1px大小的矩陣,個人喜歡使用其他也行。
下面是繪制一個點的函數,填充風格沒有定義,在傳入_ctx時是什么fillStyle就填充什么風格。
/* func: drawPoint:draw a point args: _ctx:[object]the canvas's getContent("2d") variable point:[object] the point where to draw a dot such as {"x":200,"y":200} strokwidth:[number] the draw line's width and height */ function drawPoint(_ctx,point,strokwidth){ strokwidth = strokwidth || 1; if(!(_ctx !== undefined && _ctx !== null)) return false; var x = point.x, y = point.y; _ctx.fillRect(x,y,strokwidth,strokwidth); return true; }
4、畫形狀
點的繪制講述完畢,開始畫曲線了,用for循環,畫圖吧。
/* func: drawShape:draw a shape args: canvasId:[string]the canvas's id func:[function]the shape function ellipse:[object] the ellipse's a and b length such as {"a":300,"b":200} center:[object] the draw center such as {"x":400,"y":400} */ function drawShape(canvasId,func,ellipse,center){ var _c = document.getElementById(canvasId); if(_c === null) return false; var _ctx = _c.getContext("2d"); // 默認橢圓中心為canvas的中心 var a = ellipse.a || 0, b = ellipse.b || 0, centerX = center.x || _c.width/2, centerY = center.y || _c.height/2, drawX,drawY,pointCX,pointCY; shapeGet = func; for(var i = 0;i <= 2*Math.PI; i+=0.0001){// 通過弧度繪圖,精確到每個0.0001弧度畫圖,可以更加精確。但是小圖的話,沒必要那么精確,浪費CPU時間。 length = shapeGet(a,b,i); pointCX = length*Math.cos(i); pointCY = length*Math.sin(i); drawX = centerX + pointCX; drawY = centerY - pointCY; drawPoint(_ctx,{"x":drawX,"y":drawY},1); } return true; }
調用方式
drawShape("myCanvas",ellipseFunc,{"a":300,"b":200},{"x":400,"y":400});
頁面的全部源碼如下:

<!DOCTYPE html> <html> <head> <meta charset="utf-8"> </head> <body> <div id="cloud"> <canvas id="myCanvas" width="800" height="800"></canvas> </div> <script> /* func: ellipseFunc:return the length args: a:[number] ellipse's a b:[number] ellipse's b theta:[number] how much of Math.PI */ function ellipseFunc(a,b,theta) { // javascript Math對象下面的三角函數,傳入的theta值必須是轉換成弧度制的值,就是多少個3.141592…等等等的那個弧度制 return Math.pow(((a*a*b*b)*(1+Math.tan(theta)*Math.tan(theta)))/(b*b+a*a*Math.tan(theta)*Math.tan(theta)),1/2); } /* func: drawPoint:draw a point args: _ctx:[object]the canvas's getContent("2d") variable point:[object] the point where to draw a dot such as {"x":200,"y":200} strokwidth:[number] the draw line's width and height */ function drawPoint(_ctx,point,strokwidth){ strokwidth = strokwidth || 1; if(!(_ctx !== undefined && _ctx !== null)) return false; var x = point.x, y = point.y; _ctx.fillRect(x,y,strokwidth,strokwidth); return true; } /* func: drawShape:draw a shape args: canvasId:[string]the canvas's id func:[function]the shape function ellipse:[object] the ellipse's a and b length such as {"a":300,"b":200} center:[object] the draw center such as {"x":400,"y":400} */ function drawShape(canvasId,func,ellipse,center){ var _c = document.getElementById(canvasId); if(_c === null) return false; var _ctx = _c.getContext("2d"); // 默認橢圓中心為canvas的中心 var a = ellipse.a || 0, b = ellipse.b || 0, centerX = center.x || _c.width/2, centerY = center.y || _c.height/2, drawX,drawY,pointCX,pointCY; shapeGet = func; for(var i = 0;i <= 2*Math.PI; i+=0.0001){// 通過弧度繪圖,精確到每個0.0001弧度畫圖,可以更加精確,0.0001更加歡迎。但是小圖的話,沒必要那么精確,浪費CPU時間。 length = shapeGet(a,b,i); pointCX = length*Math.cos(i); pointCY = length*Math.sin(i); drawX = centerX + pointCX; drawY = centerY - pointCY; drawPoint(_ctx,{"x":drawX,"y":drawY},1); } return true; } drawShape("myCanvas",ellipseFunc,{"a":300,"b":200},{"x":400,"y":400}); </script> </body> </html>
5、廢話
本文產生的原因,本來是想做詞雲的,給定詞雲的形狀,在這個形狀內填充詞語,產生了這個念頭,詞雲還沒實現,關鍵是如何才能讓填充詞語相互不覆蓋的問題。后來,選擇在github里面搜索算了,選擇了一個jQuery.awesomeCloud.plugin,但是填充效率確實壓力山大。想過去模擬他的方法,做一個出來,因此先從畫自定義曲線開始了。