.基礎閱讀
0.0 關于DMMA的介紹
1.1 DFM塑膠件DFM總章
2.注射成型(Injection Molding)工藝
注射成型(Injection Molding),簡稱注塑,是一種通過將材料注射到模具中制造零件的制造方法。是塑膠加工工藝的一種。
在這個過程中,塑料被放置到料斗,然后料斗將塑料加熱注入,它是通過一長的腔室與一個往復螺桿推壓。之后,它被軟化成流體狀態。噴嘴位于腔室的末端,流體塑料通過噴嘴強制冷卻,閉合模具。當塑料冷卻和固化時,半成品從壓機中退出。
2.3 注塑工藝視頻
先看視頻理解,再去現場確認,這是基本的學習路子。
1)最小桌面注塑機
https://v.qq.com/x/page/s0705oxvz1t.html
3.DFM注塑件設計指南
本節就開始介紹設計注塑件需要注意的設計指南。
3.1 注塑模具可行性設計
產品設計需要考慮注射模具結構的可行性和提高模具的使用壽命。
3.1.1 理解注塑的原理,設計符合注塑工藝的零件
作者反復思量之后,可能對于新手或行業外的人,這一條反而是最先需要解釋的。如果以前是只接觸金屬加工工藝的人,就需要特意去理解一下,什么是注塑了:如上面的2.2小節的圖所示。不能用做機械加工的思路去做塑膠件,也不要去故意挑戰注塑工藝的極限,去搞專門做不出來的零件。
如下圖所示:
也許有些人會認為這是一種創新,會強行要求供應商制造。但你出得起這種工藝的錢,別人也行,分分鐘會被其他的對手抄襲,比較得不償失的。
3.1.2卡扣等結構應為斜銷(或滑塊)預留足夠的退出空間
卡扣等結構是塑膠件常用的一種裝配方式,通過模具中的斜銷(或滑塊)側向抽芯結構成型而成。斜銷(或滑塊)在零件脫模時有一個從卡扣中退出的行程,零件的設計需要為斜銷(或滑塊)的退出提夠足夠的運動空間,否則會出現斜銷(或滑塊)無法退出或者斜銷(或滑塊)在退出過程中與零件上其他特征(如支柱等)發生干涉的現象,如圖3-71所示。
3.1.3 避免模具出現薄鐵以及強度太低的設計
在塑膠件中,如果兩個特征距離非常近,那么在模具上相對應的部位就是一塊薄鐵,如圖3-72所示,這容易造成模具強度低、壽命短,因此需要避免在模具上出現薄鐵以及強度太低的設計。
3.2 零件壁厚
在塑膠件的設計中,零件壁厚是首先要考慮的參數,零件壁厚決定了零件的力學性能、零件的外觀、零件的可注射性以及零件的成本等。可以說,零件壁厚的選擇和設計決定了零件設計的成功與失敗。
3.2.1 零件壁厚必須適中
由于塑膠材料的特性和注射工藝的特殊性,塑膠件的壁厚必須在一個合適的范圍內,不能太薄,也不能太厚。
壁厚太薄,零件注射時流動阻力大,塑膠熔料很難充滿整個型腔,不得不通過性能更高的注射設備來獲得更高的充填速度和注射壓力。
壁厚太厚,零件冷卻時間增加(據統計,零件壁厚增加1倍,冷卻時間增加4倍),零件成型周期增加,零件生產效率低;同時過厚的壁厚很容易造成零件產生縮水、氣孔、翹曲等質量問題。
不同的塑膠材料對塑膠件的合適壁厚有不同的要求,甚至不同塑膠材料生產商生產的同一種塑膠材料也可能存在不同合適壁厚要求。常用塑膠材料零件的合適壁厚范圍見表3-7。當塑膠件壁厚值接近表中的合適壁厚值的上下限時,產品設計工程師應當向塑膠材料生產商征求意見。
3.2.2 盡量減少零件壁厚
決定塑膠件壁厚的關鍵因素包括:
壁厚應盡量做到最小,因為較厚的零件壁厚不但會增加材料成本、增加零件重量,同時會延長零件成型的周期,從而增加生產成本。圖3-19所示為某款ABS塑料零件壁厚與冷卻時間的關系。
為了保證和提高零件強度,產品設計工程師往往傾向于選擇較厚的零件壁厚。
事實上,通過選擇較厚零件壁厚來保證和提高零件強度不是最好的方法。零件強度的提高可以通過添加加強筋、設計曲線或波浪形的零件剖面等來獲得,這不但可以減少零件的材料浪費,也縮短了零件注射成型的周期。
3.2.3 零件壁厚均勻
最理想的零件壁厚分布是在零件的任一截面上零件厚度均勻一致。不均勻的零件壁厚會引起零件不均勻的冷卻和收縮,從而造成零件表面縮水、內部產生氣孔、件翹曲變形、尺寸精度很難保證等缺陷。
常見塑膠件均勻壁厚設計的范例如圖3-20所示。
如果零件均勻壁厚不可能獲得,那么至少需要保證零件壁厚處與壁薄處有光滑的過渡,避免零件壁厚岀現急劇的變化。急劇變化的零件壁厚影響塑膠熔料的流動,容易在塑膠背面產生應力痕,影響產品外觀;同時易導致應力集中,降低塑膠件的強度,使得零件很難承受載荷或外部沖擊。
四種零件壁厚不均勻處的壁厚設計如圖3-21所示。
最差的壁厚設計見圖3-21a,零件壁厚出現急劇變化;
較好的壁厚設計見圖3-21b和3-21c,壁厚壁薄處均勻過渡,一般來說,過渡區域的長度為厚度的3倍;
最好的壁厚設計見圖3-21d,不但零件壁厚光滑過渡,而且在零件壁厚處使用了掏空的設計,既可以保證零件不發生縮水,又可以保證零件強度。
3.2.4 軟件壁厚分析功能
關于塑膠件的壁厚,可以用軟件的壁厚分析功能查看,更加直觀清晰。
3.3 避免尖角
塑膠件的內部和外部需要避免產生尖角,尖角會阻礙塑膠熔料的流動,容易產生外觀缺陷;同時在尖角處容易產生應力集中,降低零件強度,使得零件在承受載荷時失效。因此,在塑膠件的尖角處,應當添加圓角,使得零件光滑過渡。
//因此,結構工程師在3d模型上應該盡量加上圓角。塑膠件開模時用的是3d圖。
3.3.1 避免在零件外部尖角(分模線處例外)
塑膠件外部圓角設計如圖3-22所示。
當然,避免零件外部尖角也不可一概而論。零件分型面處的圓角會造成模具結構復雜,增加模具成本,同時零件上容易出現斷差,影響外觀。在零件分型面處直角的設計較好,如圖3-23所示。
如果在連接處增加一段約1.5m的平面,在飛邊和澆口的去除過程中,飛邊和澆口很容易被去除,如圖5-18所示。(注塑件一般不用這么注意)
3.3.2 避免在塑膠熔料流動方向上產生尖角
在塑膠件塑膠熔料流動方向上避免產生尖角,如圖3-24所示,圖中箭頭的方向為塑膠熔料的流動方向。在原始的設計中,尖角易導致零件在注射過程中產生困氣,局部的高溫造成塑膠分解,在零件表面產生外觀缺陷,同時尖角容易產生內應力;在改進的設計中,通過設計的優化避免尖角的產生,保證塑膠熔料的流動順暢。
3.3.3 避免在壁連接處產生尖角(內圓角0.3T<R<0.8T,一般0.5T)
應力集中是塑膠件失效的主要原因之一,應力集中降低了零件的強度,使得零件很容易在沖擊載荷和疲勞載荷作用下失效。
應力集中大多發生在零件尖角處。塑膠件應當避免尖角的設計,在尖角的地方添加圓角,以減小和避免應力集中的發生。零件尖角容易出現在零件主壁與側壁連接處、壁與加強筋連接處、壁與支柱連接處等。
零件內部圓角與應力集中系數的關系如圖3-25所示。其中T為零件壁厚,R為零件內圓角,p為零件承受的載荷。
由圖3-25可見,當R<0.3T時,應力急劇升高;當R>0.8T時,則基本沒有應力集中現象發生。
一般來說,零件截面連接處內部圓角R為0.5T,外部圓角為1.5T,既保證了零件的均勻壁厚,又減少了零件連接處應力集中,如圖3-26所示。當然,圓角也不可太大,否則容易使得零件局部壁 厚太厚,造成縮水。
3.3.4 注塑件圓角也是對模具電極的一種保護
在模具加工中,放電加工是一種常用的加工方式,特別是針對復雜曲面。在這種加工方式中,就需要用到電極。
電極的尖角、棱邊等凸起部位,在放電加工中比平坦部位損耗要快。
所以一些有擔當的模具工程師,反而會要求零件設計有圓角,不然自己也會給塑膠模具加上最小的0.2的圓角。
作者和一位模具工程師的關于圓角的聊天情況如下(目的是為了確認模型的所有邊是否要打上圓角);
3.3.5 建模中打圓角的步驟
這只是作者的總結和建議。
塑膠圓角的建模處理并非一次性就可以成功的,建議遵守以下要求。
3.3.5.1 建模最后的步驟才打圓角
可見國標GB26099的規范,圓角建模如下圖:
3.3.5.2 塑膠零件模型所有的邊都打上圓角,一般第一次打圓角建議值為R0.5
如上述的避免尖角的要求,既然外部尖角、流動方向、連接處都希望有圓角,那么就等于整個塑膠模型都需要圓角。所以作者建議,塑膠模型所有的邊全部打上圓角。分型線的圓角可以讓模具工程師后期刪除。
打圓角步驟:一般先用邊圓角打重要的邊,再打不重要的邊,最后用面圓角打剩余的邊。面圓角少用,因為不容易控制和修改。
圓角可以用邊圓角分類多打幾次,不要一次性打完所有圓角,這樣也不容易控制和修改。
3.3.5.3 按照-避免尖角--的原則,修改圓角值,達到最優化的要求
當然,一些特殊的,一眼可見的圓角可以直接打R1等值。
TML5新增一個元素canvas以及與這個元素伴隨而來的一套編程接口Canvas API,可以在頁面上繪制出任何想要的、動態的圖形與圖像,創造出豐富多彩的Web頁面;它可以用于動畫、游戲畫面、數據可視化、圖片編輯以及實時視頻處理等方面;
canvas元素相當于在頁面上放置了一塊畫布,其本身并不能實現圖形繪制,也沒有任何外觀,只是一塊無色透明的區域,需要幾組API進行操作,最重要的是就是具備基本繪圖能力的2D上下文(context)及文本API,利用這些API,可以添加線條、圖片、文字、繪畫、加入高級動畫等;現在還可以使用用于繪制硬件加速的WebGL的3D上下文;
基本用法:
要使用canvas元素,必須先設置其width和height屬性,指定可以繪圖的區域大小;該標簽中的內容,為后備信息,當瀏覽器不支持canvas時,就會顯示這些信息,如:
<canvas id="drawing" width="400" height="300">你的瀏覽器不支持canvas</canvas>
canvas 的默認大小為 300px×150px,其對應的DOM元素也具有width和height屬性,并且,可以通過CSS設置其樣式;
<style>
#drawing{background-color: lightblue; width:400px; height:300px;}
</style>
<canvas id="drawing"></canvas>
<script>
var drawing = document.getElementById("drawing");
console.log(drawing);
console.log(drawing.width + ":" + drawing.height); // 還是默認的300pxX150px
</script>
canvas是無色透明的,如果不添加任何樣式或不繪制任何圖形,在頁面上是看不到該元素的;
繪制圖形的步驟:
1、取得canvas元素:
var canvas = document.getElementsByTagName("canvas")[0];
2、取得上下文(context):進行圖形繪制時,需要使用到圖形上下文(graphics context),圖形上下文是一個封裝了很多繪圖功能的對象;使用canvas對象的getContext()方法獲得圖形上下文,在該方法中需要傳入“2d”;
var context = canvas.getContext("2d");
3、設定繪圖樣式:在繪制的時候,首先要設定好繪圖的樣式,然后再調用有關方法進行圖形的繪制,如:fillStyle屬性,設定填充的樣式,在該屬性中填入顏色值;strokeStyle屬性,設置邊框的樣式,值也是顏色值;使用lineWidth屬性設置邊框的寬度;
context.fillStyle = "yellow";
context.strokeStyle = "green";
context.lineWidth = 10;
4、填充與繪制邊框:使用相應的繪制方法進行繪制,如使用fill()方法填充與stroke()方法描邊;填充是指填充圖形內部;描邊是指不填充圖形內部,只繪制圖形的邊框;對于矩形來說, fillRect()方法繪制矩形內部,strokeRect()方法繪制矩形邊框;
context.fillRect(0,0,100,100);
context.strokeRect(0,0,100,100);
2D上下文(context):
使用canvas對象的getContext()方法獲得圖形上下文,在該方法中需要傳入“2d”,就會獲取一個在2D環境中繪制圖形的CanvasRenderingContext2D類型的上下文對象,被稱為渲染上下文(The rendering context),目前只支持2D,沒有3D之類的,但不排除今后會擴展;
// 確定瀏覽器是否支持canvas元素
if(canvas.getContext){
var context = canvas.getContext("2d");
console.log(context); // CanvasRenderingContext2D
// ...
}
使用2D繪圖上下文提供的方法,可以繪制簡單的2D圖形,比如矩形、弧線和路徑;如果利用它們結合不同的填充和描邊樣式,就能繪制出非常復雜的圖形;如:
context.fillStyle = "rgba(0, 0, 200, 0.5)";
context.fillRect (50, 50, 100, 100);
每個canvas只能有一個上下文對象,多次調用getContext()返回的是同一個對象;
var context = drawing.getContext("2d");
var context1 = drawing.getContext("2d");
console.log(context === context1); // true
其擁有一個canvas屬性,是與當前上下文關聯的HTMLCanvasElement對象的只讀引用;
canvas的尺寸和坐標:
canvas默認的坐標系是以<canvas>元素的左上角(0, 0)為坐標原點;所有圖形元素的坐標都基于這個原點計算,x值越大表示越靠右,y值越大表示越靠下,反之亦然;
畫布的尺寸和坐標系都是以CSS像素為單位的;
默認情況下,canvas的width和height表示水平和垂直兩個方向上可用的像素數目;
畫布的尺寸和CSS定義的尺寸是完全不同的概念:畫布的尺寸是由<canvas>元素的width和height屬性定義的,而CSS中定義的尺寸是畫布元素在頁面中顯示的尺寸;如果兩者定義的尺寸不相同,則畫布上的像素會自動縮放,以適合CSS中定義的尺寸;另外,畫布中的坐標,也是根據畫布的width和height屬性定義的;
畫布上的點可以使用浮點數來指定坐標,但是它們不會自動轉換成整型值,畫布會采用反鋸齒的方式來模擬部分填充的像素;
畫布的尺寸是不能隨意更改的,除非完全重置畫布;重置畫布的width和heigth屬性,會清空整個畫布,擦除當前的路徑并且會重置所有的圖形屬性(包括當前的變換和裁剪區域)為初始狀態;
繪制基本圖形:
填充和描邊:
2D上下文的基本繪圖操作是填充和描邊;填充,就是用指定的樣式(顏色、漸變或圖像)填充圖形;描邊,就是只在圖形的邊緣畫線;(這個,前面講過了)
大多數2D上下文操作都會細分為填充和描邊兩個操作,而操作的結果取決于兩個屬性:fillStyle和strokeStyle;
這兩個屬性的值可以是字符串、漸變對象或模式對象,而且它們的默認值都是“#000000”;如果為它們指定表示顏色的字符串值,格式是與CSS一致的,如:
// ...
context.fillStyle = "#0000ff";
context.strokeStyle = "red";
context.fillRect(50,50,200,100);
context.strokeRect(50,200,200,100);
// ...
繪制矩形:
在canvas中,繪圖有兩種形式:矩形和路徑,除了矩形,所有其他類型的圖形都是通過一條或者多條路徑組合而成的;
矩形是唯一一種可以直接在2D上下文中繪制的形狀,其分別使用fillRect(x,y,width,height)、strokeRect(x,y,width,height)方法來繪制填充矩形和繪制矩形邊框,兩者均接收4個參數:矩形的x、y坐標、矩形的寬和高,單位為像素;填充和描邊的顏色通過fillStyle和strokeStyle兩個屬性指定,如:
function draw(id){
var drawing = document.getElementById(id);
if(drawing.getContext){
var context = drawing.getContext("2d");
// 繪制畫布背景
context.fillStyle = "#EEEEFF";
context.fillRect(0,0,canvas.width, canvas.height);
// 繪制藍色矩形
context.fillStyle = "rgba(0,0,255,0.5)";
context.strokeStyle = "blue";
context.fillRect(50,50,100,100);
context.strokeRect(50,50,100,100);
// 繪制紅色矩形
context.strokeStyle = "#ff0000";
context.fillStyle = "rgba(255,0,0,0.2)";
context.strokeRect(100,100,100,100);
context.fillRect(100,100,100,100);
}
}
draw("canvas");
示例:繪制國際象棋棋盤:
for (i=0; i<8; i++){ // 行
for (j=0; j<8; j++){ // 列
if ((i+j) % 2 == 0)
context.fillStyle = 'black';
else
context.fillStyle= 'white';
context.fillRect(j*50, i*50, 50, 50);
}
}
示例:繪制柱狀圖表
var data = [100, 50, 20, 30, 100];
var colors = ["red", "orange", "yellow", "green", "blue"];
context.fillStyle = "white";
context.fillRect(0, 0, canvas.width, canvas.height);
for(var i=0; i<data.length; i++){
var v = data[i];
context.fillStyle = colors[i];
context.fillRect(25+i*50, 280-v*2, 50, v*2);
}
關于矩形,還有一個clearRect(x, y, width, height)方法,該方法將指定的矩形區域中的圖形進行擦除,使得矩形區域中的顏色全部變為透明,其參數與前兩個方法一致;如:
// 在兩個矩形重疊的部分清除一個小矩形
context.clearRect(110,110,30,30);
// 重新繪制
context.strokeStyle = "black";
context.fillStyle = "black";
context.fillRect(250,25,100,100);
context.clearRect(270,45,60,60);
context.strokeRect(275,50,50,50);
以上繪制的矩形,由于沒用到路徑,所以一旦繪制,會立即生效,并在canvas中渲染;
清理畫布和恢復畫布狀態:
<input type="button" value="清空畫布" onClick="clearMap();" />
<script type="text/javascript">
var c = document.getElementById("canvas");
var context = c.getContext("2d");
context.fillStyle = "red";
context.strokeStyle = "yellow";
context.beginPath();
context.arc(200,150,100,-Math.PI*5/6,true);
context.stroke();
function clearMap() {
context.clearRect(0,0,300,200);
}
</script>
繪制路徑:
圖形的基本元素是路徑,其是通過不同顏色和寬度的線段或曲線相連形成的不同形狀的點的集合;一個路徑,一般都是閉合的;
使用路徑繪制圖形需要一些步驟:創建路徑起始點、使用相關命令畫出路徑、之后把路徑封閉;一旦路徑生成,就可以通過描邊或填充路徑區域來渲染圖形;
2D上下文提供了多個繪制路徑的方法,通過這些方法就可以繪制出復雜的圖形和線條:
beginPath()方法,新建一條路徑,不需要參數,表示要開始繪制新路徑,其后再調用一系列方法來實際地繪制路徑;其后繪制的路徑都是子路徑,都屬于同一條路徑;
再使用moveTo()方法,移動當前點,它并不繪制線條,它的作用就是確定路徑起始點;
接著使用繪圖方法,例如:lineTo()方法,可以從上一點開始繪制一條線段;或者使用rect()方法,繪制一個矩形路徑;再或者使用arc()方法,繪制一條弧線,等等;
closePath()方法,閉合路徑;此時,就會自動繪制一條連接到路徑起點的線條,路徑的創建工作就完成了;如:
context.beginPath();
context.moveTo(50,50);
context.lineTo(100,50);
context.arc(50,50,50,0,Math.PI/2);
context.moveTo(200,70);
context.arc(200,70,50,0,Math.PI/2,true);
context.rect(300,50,100,100);
context.closePath();
如果路徑已經完成,可以設置繪圖樣式,并進行繪圖,如用fillStyle設置填充樣式,并調用fill()方法填充,生成實心的圖形;或使用strokeStyle屬性設置邊框樣式,并調用stroke()方法對路徑描邊,即勾勒圖形輪廓;
// ...
context.fillStyle = "green";
context.fill();
context.strokeStyle = "yellow";
context.lineWidth = 10;
context.stroke();
本質上,路徑是由很多子路徑構成,這些子路徑都是在一個列表中,所有的子路徑(線、弧形等)構成一個完整的路徑,當調用stroke()或fill()方法后就會繪制圖形;
而當每次調用closePath()方法之后,列表就被清空重置,之后就可以重新繪制新的圖形,如:
context.beginPath();
context.moveTo(50,50);
context.lineTo(100,50);
context.arc(50,50,50,0,Math.PI/2);
context.closePath();
context.fillStyle = "red";
context.strokeStyle = "blue";
context.lineWidth = 12;
context.fill();
context.stroke();
context.beginPath();
context.moveTo(200,70);
context.arc(200,70,50,0,Math.PI/2,true);
context.closePath();
context.fillStyle = "blue";
context.strokeStyle = "red";
context.lineWidth = 8;
context.fill();
context.stroke();
context.beginPath();
context.rect(300,50,100,100);
context.closePath();
context.fillStyle = "green";
context.strokeStyle = "yellow";
context.lineWidth = 4;
context.fill();
context.stroke();
閉合路徑closePath()方法不是必需的,如果不使用closePath(),則路徑不閉合,如:
context.beginPath();
context.arc(150, 150,50, 0, Math.PI);
// context.closePath();
context.stroke();
如果不使用beginPath()開始新路徑,并不會關閉之前的路徑,并且該路徑會永遠保留著;就算調用fill()方法或stroke()方法進行繪制,路徑也不會消失;因此,后續的繪制,還會重復繪制該路徑;
移動筆觸(當前坐標點):
moveTo(x,y)方法:將光標(筆觸)移動到指定坐標點,繪制圖形的時候就以這個坐標點為起點,也就是確定一個子路徑的起點,當個起點也稱為當前點;其并不繪制任何內容,但也屬于路徑描述的一部分;
context.moveTo(50, 50);
context.lineTo(150, 150);
在畫布中第一次調用相關的繪制路徑的方法,有時候并不需要使用moveTo()指定起點位置,起點位置是由該方法的參數指定的;但后續的路徑繪制,必須要明確起點位置
當調用beginPath()方法開始新的一段路徑時,繪制的新路徑的起點由當前的繪制方法自動確定,此時,并不需要明確調用moveTo()方法;當 canvas初始化或者beginPath()調用后,通常會使用moveTo()函數設置起點;
示例:笑臉
context.beginPath();
context.arc(75, 75, 50, 0, Math.PI * 2, true);
context.moveTo(110, 75);
context.arc(75, 75, 35, 0, Math.PI); // 口 (順時針)
context.moveTo(65, 65);
context.arc(60, 65, 5, 0, Math.PI * 2, true); // 左眼
context.moveTo(95, 65);
context.arc(90, 65, 5, 0, Math.PI * 2, true); // 右眼
context.closePath(); // 不是必須的
context.stroke();
繪制直線(線段):
繪制線段需要lineTo()方法:lineTo(x,y)方法:指定從起點到終點(x, y)的一條直線;可重復使用lineTo方法,會以上一個lineTo()方法指定的點為起點,以當前lineTo()為終點再次創建直線;不斷重復這個過程,可以繪制復雜的圖形;
context.moveTo(50,50);
context.lineTo(100,100);
context.lineTo(100,200);
執行stroke()繪制直線;
context.moveTo(50,50);
context.lineTo(200,200);
context.lineTo(50,200);
context.stroke();
開始點和之前的繪制路徑有關,之前路徑的結束點就是接下來的開始點,開始點也可以通過moveTo()函數改變;
繪制直線也屬于路徑,因此在必要的情況下,也需要使用beginPath(),否則也屬于連續繪制,或調用closePath()方法閉合路徑;
// ...
context.beginPath();
context.moveTo(50,50);
context.lineTo(200,200);
context.lineTo(50,200);
context.lineWidth = 20;
context.closePath();
// ...
示例:繪制三角形
// 繪制三角形
context.beginPath();
context.moveTo(75, 50);
context.lineTo(100, 75);
context.lineTo(100, 25);
context.fill();
示例:繪制多邊形
CanvasRenderingContext2D.prototype.polygon = function(n ,x, y, r, angle, anticlockwise){
this.n = n || 3;
this.x = x || 10;
this.y = y || 10;
this.r = r || 50;
this.angle = angle || 0;
this.anticlockwise = anticlockwise || false;
this.moveTo(this.x + this.r * Math.sin(this.angle), this.y - this.r * Math.cos(this.angle));
var delta = 2 * Math.PI / this.n;
for(var i=1; i<this.n; i++){
this.angle += this.anticlockwise ? -delta : delta;
this.lineTo(this.x + this.r * Math.sin(this.angle), this.y - this.r * Math.cos(this.angle));
}
this.closePath();
}
var canvas = document.getElementsByTagName("canvas")[0];
if(canvas.getContext){
var context = canvas.getContext("2d");
context.beginPath();
context.polygon(3, 50, 70, 50); // 三角形
context.polygon(4, 150, 60, 50, Math.PI / 4); // 正方形
context.polygon(5, 255, 55, 50); // 五邊形
context.polygon(6, 365, 53, 50, Math.PI / 6); // 六邊形
context.polygon(4, 365, 53, 20, Math.PI / 4, true); // 六邊形中的小正方形
context.fillStyle = "#ccc";
context.strokeStyle = "#008";
context.lineWidth = 5;
context.fill();
context.stroke();
}
</script>
填充規則:
當用到fill()、clip()和isPointinPath()時,可以選擇一個填充規則,即傳入一個fillRule參數,該填充規則根據某處在路徑的外面或者里面來決定該處是否被填充,這對于自己與自己路徑相交或者路徑被嵌套的情況是有用的;兩個可能的值:
非零環繞原則(nonzero):
主要解決繪圖中交叉路徑的填充問題;
要檢測一個點P是否在路徑的內部,使用非零環繞原則:想象一條從點P出發沿著任意方向無限延伸(一直延伸到路徑所在的區域外)的射線;首先初始化一個計數器為0,然后對所有穿過這條射線的路徑進行枚舉;每當一條路徑順時針方向穿過射線的時候,計數器就加1;反之,就減1;最后,枚舉完所有路徑之后,如果計數器的值不是0,那么就認為P是在路徑內;反之,如果計數器的值是0,則認為P在路徑外;
總之,由順、逆時針穿插次數決定是否填充某一區域;
奇偶環繞規則(evenodd):奇數表示在路徑內,偶數表示在路徑外;
從任意位置p作一條射線,若與該射線相交的路徑的數目為奇數,則p是路徑內部的點,否則是外部的點;
如:
context.beginPath();
context.arc(50, 50, 30, 0, Math.PI*2);
context.arc(50, 50, 15, 0, Math.PI*2, true);
// context.fill();
// context.fill("nonzero");
context.fill("evenodd");
如:繪制一個五角星
context.arc(100, 100, 100, 0, Math.PI*2);
context.fillStyle = '#D43D59';
context.fill();
context.beginPath();
context.moveTo(100, 0);
context.lineTo(100+Math.cos(Math.PI*3/10)*100, 100+Math.sin(Math.PI*3/10)*100);
context.lineTo(100-Math.cos(Math.PI*1/10)*100, 100-Math.sin(Math.PI*1/10)*100);
context.lineTo(100+Math.cos(Math.PI*1/10)*100, 100-Math.sin(Math.PI*1/10)*100);
context.lineTo(100-Math.cos(Math.PI*3/10)*100, 100+Math.sin(Math.PI*3/10)*100);
context.lineTo(100, 0);
context.closePath();
context.strokeStyle = "rgb(0,0,0)";
context.fillStyle = "#246AB2"
// context.fill('nonzero');
context.fill('evenodd');
context.stroke();
如:繪制一個大箭頭圖標
context.arc(250, 250, 150, 0, Math.PI * 2);
context.moveTo(250, 150);
context.lineTo(350, 250);
context.lineTo(150, 250);
context.closePath();
context.moveTo(200, 250);
context.lineTo(300, 250);
context.lineTo(300, 350);
context.lineTo(200, 350);
context.closePath();
context.fillStyle = "#0D6EB8";
// context.fill("nonzero");
context.fill("evenodd");
示例:使用數學方程繪制圖案圖形
var dx = 150, dy = 150;
var s = 100;
context.beginPath();
context.fillStyle = "rgb(100,255,100)";
context.strokeStyle = "rgb(0,0,100)";
var dig = Math.PI / 15 * 11;
for(var i=0; i<30; i++){
var x = Math.sin(i * dig);
var y = Math.cos(i * dig);
console.log(dx + x * s, dy + y * s);
context.lineTo(dx + x * s, dy + y * s);
}
context.closePath();
// context.fill();
context.fill("evenodd");
context.stroke();
// 以下包裝成一個函數
function draw(offsetX, n){
var dig = Math.PI / 15 * n;
context.beginPath();
for(var i = 0 ; i < 30 ; i++){
var x = Math.sin(i * dig);
var y = Math.cos(i * dig);
context.lineTo(offsetX + x * 80, 150 + y * 80);
}
context.closePath();
context.fillStyle = "green";
// context.fill();
context.fill("evenodd");
context.strokeStyle = "#666";
context.stroke();
}
var data = [14,13,19,7,26];
data.forEach(function(v, i){
draw((i + 1) * 160, v);
});
示例:使用鼠標實時繪制圖形
canvas.onmousedown = function(e) {
this.X1 = e.offsetX;
this.Y1 = e.offsetY;
this.isMouseDown = true;
};
canvas.onmousemove = function(e) {
if (this.isMouseDown) {
this.X2 = e.offsetX;
this.Y2 = e.offsetY;
this.drawing(this.X1, this.Y1, this.X2, this.Y2, e);
}
};
canvas.onmouseup = function(e) {
this.isMouseDown = false;
};
canvas.drawing = function (x1, y1, x2, y2, e) {
if (!context) {
return;
} else {
context.fillStyle = "red";
context.strokeStyle = "blue";
context.lineWidth = 5;
context.beginPath();
context.moveTo(x1, y1);
context.lineTo(x2, y2);
context.stroke();
this.X1 = this.X2;
this.Y1 = this.Y2;
context.closePath();
}
}
例子:迷你圖
迷你圖(sparkline)是指用于顯示少量數據的圖形,通常會被嵌入文本流中,形如:server:小圖;迷你圖是由作者Edward Tufte杜撰的,他將該詞用于描述“內嵌在文字、數字、圖片中的小且高分辨率的圖形”;迷你圖是數據密集、設計簡單、單詞大小的圖形,如:
<style>
.sparkline{background-color:#ddd; color:red; display: inline-block;}
</style>
Load:<span class="sparkline">3 5 7 6 6 9 11 15</span>,
source:<span class="sparkline" data-height="36" data-width="100">6 14 8 9 10 13 18</span>
complete:<span class="sparkline" data-ymax="18" data-ymin="6">12,3,8,2,88</span>
<script>
window.onload = function(){
var elts = document.getElementsByClassName("sparkline");
main: for(var e = 0; e<elts.length; e++){
var elt = elts[e];
var content = elt.textContent || elt.innerText;
var content = content.replace(/^\s+|\s+$/g, "");
var text = content.replace(/#.*$/gm, "");
text = text.replace(/[\n\r\t\v\f]/g, " ");
var data = text.split(/\s+|\s*,\s*/);
for(var i=0; i<data.length; i++){
data[i] = Number(data[i]);
if(isNaN(data[i]))
continue main;
}
var style = getComputedStyle(elt, null);
var color = style.color;
var height = parseInt(elt.getAttribute("data-height")) ||
parseInt(style.fontSize) || 20;
var width = parseInt(elt.getAttribute("data-width")) ||
data.length * (parseInt(elt.getAttribute("data-dx")) || 6);
var ymin = parseInt(elt.getAttribute("data-ymin")) ||
Math.min.apply(Math, data);
var ymax = parseInt(elt.getAttribute("data-ymax")) ||
Math.max.apply(Math, data);
if(ymin >= ymax)
ymax = ymin + 1;
var canvas = document.createElement("canvas");
canvas.width = width;
canvas.height = height;
canvas.title = content;
elt.innerHTML = "";
elt.appendChild(canvas);
var context = canvas.getContext("2d");
for(var i=0; i<data.length; i++){
var x = width * i / data.length;
var y = (ymax - data[i]) * height / (ymax - ymin);
context.lineTo(x,y);
}
context.strokeStyle = color;
context.stroke();
}
}
</script>
繪制曲線:
角度和弧度:
夾角:從一個點發射(延伸)出兩條線段,兩條線相交的部分會構成一個夾角;
角度:兩條相交直線中的任何一條與另一條相疊合時必須轉動的量的量度,單位符號為°;
周角:一條直線圍繞起點需要與自己相疊合時必須轉動的量的量度被稱為周角,周角等分為360度;
弧度:角的度量單位,弧長等于半徑的弧其所對的圓心角為1弧度(弧長等于半徑時,射線夾角為1弧度);
角度與弧度的換算公式為:弧度 = 角度 * (Math.PI / 180);
在使用JavaScript編寫代碼進行相關計算的時候,經常需要使用Math提供的方法:
Math.sin(弧度) 夾角對面的邊與斜邊的比值;
Math.cos(弧度) 夾角側面的邊與斜邊的比值;
圓形上點坐標的計算公式:
坐標 = ( x0 + Math.cos(angle) x R,y0 + Math.sin(angle) x R )
其中x0和y0為圓心坐標,angle為弧度,R為圓的半徑;
例如:使用三角函數來繪制曲線:
context.beginPath();
for(var x = 30, y = 0; x<1000; x++){
// 高度 * 波長 + 中心軸位置
y = 50 * Math.sin(x / 25) + 100;
context.lineTo(x, y);
}
context.stroke();
繪制弧形和圓形:
arc(x, y, radius, startAngle, endAngle[, anticlockwise])方法:
此方法可以繪制一條弧,其以(x, y)為圓心,以radius為半徑繪制一條弧線(圓),從 startAngle開始到endAngle結束的弧度,按照anticlockwise給定的方向(默認為順時針)來繪制;參數anticlockwise指定是否按逆時針方向進行繪制,為true時,逆時針,反之順時針;
// context.arc(100,100,50,0, Math.PI*3/2);
context.arc(100,100,50,0, Math.PI*3/2,true);
context.stroke();
arc()方法中表示角的單位是弧度,不是角度;角度都是按順序時針計算的,無論是按時針還是按逆時針;
由于arc()繪制的是路徑,所以在必要的情況下,需要調用beginPath();如果需要扇形,可以使用closePath()方法閉合路徑;
當第一個調用arc()或在beginPath()方法后第一個調用arc()方法時,會在確定其圓心、半徑及開始角度的情況下,自動確定起點位置;其它情況下,需要使用moveTo()指定起點位置,例如:先將當前點和弧形的起點用一條直線連接,然后再用圓的一部分(圓弧)來連接弧形的起點和終點,并把弧形終點作為新的當前點;
// 繪制一個圓弧
context.beginPath();
context.moveTo(50, 50);
context.arc(100, 100, 50, Math.PI*3/2, 0);
context.lineTo(150,150);
context.stroke();
// 繪制一個san形(楔形)
context.beginPath();
context.moveTo(200, 50);
context.arc(200, 50, 50, 0, Math.PI / 2, false);
context.closePath();
context.stroke();
// 同樣的san形(楔形),方向不同
context.beginPath();
context.moveTo(350, 100);
context.arc(350, 100, 50, 0, Math.PI / 2, true);
context.closePath();
context.stroke();
arc()不僅可以繪制圓弧,也可以用來繪制圓形,即startAngle, endAngle形成Math.PI*2(360度),如:
context.beginPath();
context.arc(100, 100, 80, 0, Math.PI*2);
// context.arc(100, 100, 80, Math.PI / 4, Math.PI * 2 + Math.PI / 4);
context.closePath(); // 沒有必要調用這一句
context.lineWidth = 10;
context.fillStyle = "red";
context.fill();
context.stroke();
繪制有交叉路徑時弧時,要注意填充規則,如:
context.beginPath();
context.arc(100, 100, 50, 0, Math.PI * 2, true);
context.arc(100, 100, 60, 0, Math.PI * 2, false);
context.fill();
context.beginPath();
context.arc(300, 100, 50, 0, Math.PI, true);
context.arc(300, 100, 60, 0, Math.PI * 2, true);
// 使用奇偶環繞規則
context.fill('evenodd');
context.beginPath();
context.arc(100, 100, 60, 0, Math.PI * 2, true);
context.arc(140, 100, 60, 0, Math.PI * 2, false);
context.arc(180, 100, 60, 0, Math.PI * 2, true);
context.fill();
循環繪制多個,如:
for(var i=0;i<15;i++){
context.strokeStyle = "#FF00FF";
context.beginPath();
context.arc(0,350,i*10,0,Math.PI*3/2,true);
context.closePath();
context.stroke();
}
for(var i=0;i<10;i++){
context.beginPath();
context.arc(i*25,i*25,i*10,0,Math.PI*2,true);
context.closePath();
context.fillStyle = 'rgba(255,0,0,0.25)';
context.fill();
}
繪制不同的圓弧:
for(var i = 0; i < 4; i++){
for(var j = 0; j < 3; j++){
context.beginPath();
var x = 25 + j * 50; // x 坐標值
var y = 25 + i * 50; // y 坐標值
var radius = 20; // 圓弧半徑
var startAngle = 0; // 開始點
var endAngle = Math.PI + (Math.PI * j) / 2; // 結束點
var anticlockwise = i % 2 == 0 ? false : true; // 順時針或逆時針
context.arc(x, y, radius, startAngle, endAngle, anticlockwise);
if (i>1){
context.fill();
} else {
context.stroke();
}
}
}
繪制不同圓角的矩形:
// 繪制圓角矩形
context.beginPath();
context.moveTo(360, 0);
context.arc(380, 30, 30, Math.PI * 3 / 2, 0); // 上邊和右上角
context.arc(390, 100, 20, 0, Math.PI / 2); // 右上角和右下角
context.arc(340, 110, 10, Math.PI / 2, Math.PI); // 底邊和左下角
context.arc(330, 0, 0, Math.PI, 0); // 左邊和左上角
context.closePath();
context.fill();
context.stroke();
示例:繪制一個WIFI圖標:
// wifi圖標
context.lineWidth = 3;
for(var i=0;i<3;i++){
context.beginPath();
context.arc(100, 150, 15 + i*12, Math.PI, Math.PI*3/2);
context.stroke();
}
context.beginPath();
context.arc(98, 148, 3, 0, Math.PI*2);
context.fill();
示例:繪制一個時鐘表盤
context.beginPath();
// 外圓
context.arc(100,100,99,0,Math.PI * 2, false);
// 內圓
context.moveTo(194, 100);
context.arc(100,100,94,0,Math.PI * 2, false);
// 分針
context.moveTo(100,100);
context.lineTo(100,15);
// 時針
context.moveTo(100,100);
context.lineTo(35,100);
context.closePath();
context.stroke();
示例:繪制一個手鐲
// 使用了非零環繞規則
function bracelet(){
context.beginPath();
context.arc(300,190,150,0,Math.PI*2,false); //順時針
context.arc(300,190,100,0,Math.PI*2,true); //逆時針
context.fillStyle="rgba(100,140,230,0.5)";
context.strokeStyle = context.fillStyle;
context.shadowColor="rgba(0,0,0,0.8)";
context.shadowOffsetX = 12;
context.shadowOffsetY = 12;
context.shadowBlur = 15;
context.fill();
context.stroke();
}
bracelet();
示例:繪制等分圓:
//描邊
drawCircle(100,100,40,2,true);
drawCircle(200,100,40,3,true);
drawCircle(300,100,40,4,true);
drawCircle(400,100,40,20,true);
drawCircle(500,100,40,100,true);
drawCircle(600,100,40,200,true);
//填充
drawCircle(100,200,40,2);
drawCircle(200,200,40,3);
drawCircle(300,200,40,4);
drawCircle(400,200,40,20);
drawCircle(500,200,40,100);
drawCircle(600,200,40,200);
function drawCircle(x,y,r,n,isStroke){
for(var i = 0 ; i < n ; i++){
//計算開始和結束的角度
var angle = 2 * Math.PI / n;
var startAngle = angle * i;
var endAngle = angle * (i + 1);
context.beginPath();
//設置繪制圓的起點
context.moveTo(x,y);
context.arc(x,y,r,startAngle,endAngle,false);
if(isStroke){
// context.strokeStyle = getRandomColor();
context.stroke();
}else{
context.fillStyle = getRandomColor();
context.fill();
}
}
}
//獲取填充的顏色/隨機
function getRandomColor(){
var r = getRandom();
var g = getRandom();
var b = getRandom();
return "rgb("+r+","+g+","+b+")";
}
function getRandom(){
return Math.floor(Math.random() * 256);
}
示例:繪制餅形圖
var data = [100, 50, 20, 30, 100];
context.fillStyle = "white";
context.fillRect(0,0,canvas.width,canvas.height);
var colors = [ "red","orange", "yellow","green", "blue"];
var total = 0;
for(var i=0; i<data.length; i++)
total += data[i];
var prevAngle = 0;
for(var i=0; i<data.length; i++) {
var fraction = data[i]/total;
var angle = prevAngle + fraction * Math.PI * 2;
context.fillStyle = colors[i];
context.beginPath();
context.moveTo(150,150);
context.arc(150,150, 100, prevAngle, angle, false);
context.lineTo(150,150);
context.fill();
context.strokeStyle = "black";
context.stroke();
prevAngle = angle;
}
示例:餅形的類
function PieChart(context){
this.context = context || document.getElementById("canvas").getContext("2d");
this.x = this.context.canvas.width / 2;
this.y = this.context.canvas.height / 2;
this.r = 120;
this.outLine = 20;
this.dataList = null;
}
PieChart.prototype = {
constructor:PieChart,
init:function(dataList){
this.dataList = dataList || [{value:100}];
this.transformAngle();
this.drawPie();
},
drawPie:function(){
var startAngle = 0,endAngle;
for(var i = 0 ; i < this.dataList.length ; i++){
var item = this.dataList[i];
endAngle = startAngle + item.angle;
this.context.beginPath();
this.context.moveTo(this.x,this.y);
this.context.arc(this.x,this.y,this.r,startAngle,endAngle,false);
var color= this.context.strokeStyle= this.context.fillStyle= this.getRandomColor();
this.context.stroke();
this.context.fill();
this.drawPieLegend(i);
startAngle = endAngle;
}
},
drawPieLegend:function(index){
var space = 10;
var rectW = 40;
var rectH = 20;
var rectX = this.x + this.r + 80;
var rectY = this.y + (index * 30);
this.context.fillRect(rectX,rectY,rectW,rectH);
// this.context.beginPath();
},
getRandomColor:function(){
var r = Math.floor(Math.random() * 256);
var g = Math.floor(Math.random() * 256);
var b = Math.floor(Math.random() * 256);
return 'rgb('+r+','+g+','+b+')';
},
transformAngle:function(){
var self = this;
var total = 0;
this.dataList.forEach(function(item,i){
total += item.value;
})
this.dataList.forEach(function(item,i){
self.dataList[i].angle = 2 * Math.PI * item.value/total;
})
},
}
var data = [{value:20},{value:26},
{value:20},{value:63},{value:25}]
var pie = new PieChart().init(data);
arcTo(x1, y1, x2, y2, radius)方法:
根據控制點(x1, y1)、(x2, y2)和半徑繪制圓弧路徑;其根據當前點與給定的控制點1連接的直線,和控制點1與控制點2連接的直線,作為使用指定半徑的圓的切線,畫出兩條切線之間的弧線路徑;
說明:圖例中的x0和y0,為當前點,x1和y1代表第一個控制點的坐標,x2和y2代表第二個控制點的坐標,radius代表圓弧半徑;
context.beginPath();
context.moveTo(50,50);
context.arcTo(200, 60, 250, 300, 60);
context.stroke();
// 繪制提示點
context.fillRect(48,48, 4, 4); // 起點
context.fillRect(198,58, 4, 4); // 起點
context.fillRect(248,298, 4, 4); // 起點
// 繪制條切線
context.setLineDash([3]);
context.beginPath();
context.moveTo(50, 50);
context.lineTo(200, 60);
context.lineTo(250, 300);
context.stroke();
如果半徑radius為0,則會繪制一點直線;
如,繪制一個圓角矩形:
context.moveTo(400, 50);
context.arcTo(500, 50, 500, 150, 30);
context.arcTo(500, 150, 400, 150, 20);
context.arcTo(400, 150, 400, 50, 10);
context.arcTo(400, 50, 500, 50, 0);
context.stroke();
bezierCurveTo(cp1x,cp1y,cp2x,cp2y,x,y)方法:
貝塞爾曲線(也稱為三次貝塞爾曲線),將從當前點到指定坐標點中間的貝塞爾曲線添加到路徑中,貝塞爾曲線需要兩個控制點,分別為cp1x、cp1y和cp2x、cp2y,x和y是貝塞爾曲線的終點坐標;
context.moveTo(100, 300);
context.bezierCurveTo(160,200,280,300,320,250)
context.stroke();
context.fillRect(97, 297, 6, 6); // 起點
context.fillRect(317, 247, 6, 6); // 終點
context.fillRect(160-3, 200-3, 6,6); // 標記控制點
context.fillRect(280-3, 300-3, 6,6); // 標記控制點
// 線
context.beginPath();
context.strokeStyle = "red";
context.moveTo(100, 300);
context.lineTo(160, 200);
context.moveTo(320, 250);
context.lineTo(280, 300);
context.stroke();
結合正反弦函數繪圖:
var dx = dy = 150;
var s = 100;
context.beginPath();
context.fillStyle = "lightgreen";
var dig = Math.PI / 15 * 11;
context.moveTo(dx,dy);
for(var i=0; i<30; i++){
var x = Math.sin(i * dig);
var y = Math.cos(i * dig);
context.bezierCurveTo(dx+x*s, dy+y*s-100, dx+x*s+100, dy+y*s, dx+x*s, dy+y*s);
}
context.closePath();
context.fill();
context.stroke();
示例:繪制心形
//三次貝塞爾曲線,繪制心形
context.beginPath();
context.moveTo(75, 40);
context.bezierCurveTo(75, 37, 70, 25, 50, 25);
context.bezierCurveTo(20, 25, 20, 62.5, 20, 62.5);
context.bezierCurveTo(20, 80, 40, 102, 75, 120);
context.bezierCurveTo(110, 102, 130, 80, 130, 62.5);
context.bezierCurveTo(130, 62.5, 130, 25, 100, 25);
context.bezierCurveTo(85, 25, 75, 37, 75, 40);
context.fill();
quadraticCurveTo(cx, cy, x, y)方法:
繪制二次貝塞爾曲線,相對來說,二次貝塞爾曲線的繪制比貝塞爾曲線的繪制容易一些,因為繪制貝塞爾曲線需要兩個控制點,而繪制二次貝塞爾曲線時只需要一個控制點;因此quadraticCurveTo方法只需要四個參數就可以了,分別是控制點的坐標(cx, cy)、二次貝塞爾曲線終點的坐標(x,y);
context.moveTo(75, 250);
context.quadraticCurveTo(100,200,175,250);
context.stroke();
context.fillRect(72, 247, 6, 6); // 起點
context.fillRect(172, 247, 6, 6); // 起點
context.fillRect(100-3, 200-3, 6,6); // 控制點
// 線
context.beginPath();
context.strokeStyle = "red";
context.moveTo(75, 250);
context.lineTo(100, 200);
context.lineTo(175, 250);
context.stroke();
示例:對話氣泡
context.beginPath();
context.moveTo(75, 25);
context.quadraticCurveTo(25, 25, 25, 62.5);
context.quadraticCurveTo(25, 100, 50, 100);
context.quadraticCurveTo(50, 120, 30, 125);
context.quadraticCurveTo(60, 120, 65, 100);
context.quadraticCurveTo(125, 100, 125, 62.5);
context.quadraticCurveTo(125, 25, 75, 25);
context.stroke();
繪制圓弧或橢圓:
ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise)方法:
繪制一條橢圓路徑;橢圓的圓心在(x,y)位置,半徑分別是radiusX 和 radiusY ,按照anticlockwise (默認順時針)指定的方向,從 startAngle 開始繪制,到 endAngle 結束;參數rotation表示橢圓的旋轉角度(單位是度,不是弧度),而startAngle和endAngle單位是弧度;
context.lineWidth = 20;
context.beginPath();
context.ellipse(200,100,100,50,0,0,Math.PI*2);
context.stroke();
context.beginPath();
context.ellipse(200,250,50,30,Math.PI / 4,0,Math.PI*2,true);
context.stroke();
如果radiusX與radiusY的值一致,繪制出來的是一個正圓;
// 把上例代碼改成
context.ellipse(i*25,i*25,i*10,i*20,30,0,Math.PI*2,true);
該方法一開始不是標準方法,現在還未完全標準化,一開始只有Chrome支持,IE不支持此方法;
參數方程法繪制橢圓:
function ParamEllipse(context, x, y, a, b){
var step = (a > b ) ? 1 / a : 1 / b;
context.beginPath();
context.moveTo(x + a, y);
for(var i=0; i<2*Math.PI; i += step){
context.lineTo(x + a * Math.cos(i), y + b * Math.sin(i));
}
context.closePath();
context.stroke();
}
context.lineWidth = 10;
ParamEllipse(context, 130, 80, 100,20);
均勻壓縮法繪制橢圓:
function EvenCompEllipse(context, x, y, a, b){
context.save();
var r = (a > b) ? a : b;
var ratioX = a / r; // x軸縮放比
var ratioY = b / r; // y軸縮放比
context.scale(ratioX, ratioY); // 進行縮放(均勻壓縮)
context.beginPath();
// 從橢圓的左端點開始逆時針繪制
context.moveTo((x + a) / ratioX, y / ratioY);
context.arc(x / ratioX, y / ratioY, r, 0 , Math.PI*2);
context.closePath();
context.stroke();
context.restore();
}
context.lineWidth = 10;
EvenCompEllipse(context, 130, 200, 100, 20);
使用三次貝塞爾曲線模擬橢圓:
function BezierEllipse(context, x, y, a, b){
// 0.5和0.6是兩個關鍵系數
var ox = 0.5 * a, oy = 0.6 * b;
context.save();
context.translate(x, y);
context.beginPath();
// 從橢圓的下端點開始逆時針繪制
context.moveTo(0, b);
context.bezierCurveTo(ox, b, a, oy, a, 0);
context.bezierCurveTo(a, -oy, ox, -b, 0, -b);
context.bezierCurveTo(-ox, -b, -a, -oy, -a, 0);
context.bezierCurveTo(-a, oy, -ox, b, 0, b);
context.closePath();
context.stroke();
context.restore();
}
context.lineWidth = 10;
BezierEllipse(context, 470, 80, 100, 20);
二次貝塞爾曲線模擬橢圓:
// 貝塞爾控制點x=(橢圓寬度/0.75)/2
CanvasRenderingContext2D.prototype.oval = function (x, y, width, height) {
var k = (width/0.75)/2,
w = width/2,
h = height/2;
this.beginPath();
this.moveTo(x, y-h);
this.bezierCurveTo(x+k, y-h, x+k, y+h, x, y+h);
this.bezierCurveTo(x-k, y+h, x-k, y-h, x, y-h);
this.closePath(); return this;
}
context.oval(300,100,200,50);
context.lineWidth = 10;
context.stroke();
rect(x, y, width, height)方法:
繪制一個左上角坐標為(x,y),寬高為width以及height的矩形路徑,其路徑會自動閉合;
當該方法執行的時候,moveTo()方法自動設置坐標參數(0,0),即當前筆觸自動重置回默認坐標;
context.rect(10, 10, 100, 100);
context.fill();
roundrect(x, y, width, height)方法:
這是由Chrome實現的繪制圓角矩形的方法,其它瀏覽器都不支持,如:
context.beginPath();
context.roundRect(50,40,100,100,50);
context.closePath();
context.stroke();
兼容處理:
if(!CanvasRenderingContext2D.prototype.roundRect){
CanvasRenderingContext2D.prototype.roundRect = function(x, y, w, h, r){
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.r = r;
this.moveTo(this.x + this.r ,this.y);
this.arcTo(this.x + this.w , this.y , this.x + this.w , this.y + this.h , this.r);
this.arcTo(this.x + this.w , this.y + this.h , this.x , this.y + this.h , this.r);
this.arcTo(this.x , this.y + this.h , this.x , this.y , this.r);
this.arcTo(this.x , this.y , this.x + this.r , this.y , this.r);
}
}
var canvas = document.getElementsByTagName("canvas")[0];
if(canvas.getContext){
var context = canvas.getContext("2d");
context.beginPath();
context.roundRect(50,40,100,100,50);
context.closePath();
context.stroke();
context.beginPath();
context.roundRect(200,40,100,100,50);
context.closePath();
context.fill();
context.beginPath();
context.roundRect(350,40,100,100,10);
context.stroke();
context.beginPath();
context.roundRect(500,40,100,100,20);
context.closePath();
context.fill();
context.beginPath();
context.roundRect(650,40,120,100,30);
context.closePath();
context.stroke();
}
組合應用:
var canvas = document.getElementsByTagName("canvas")[0];
if(canvas.getContext){
var context = canvas.getContext("2d");
roundedRect(context, 12, 12, 150, 150, 15);
roundedRect(context, 19, 19, 150, 150, 9);
roundedRect(context, 53, 53, 49, 33, 10);
roundedRect(context, 53, 119, 49, 16, 6);
roundedRect(context, 135, 53, 49, 33, 10);
roundedRect(context, 135, 119, 25, 49, 10);
context.beginPath();
context.arc(37, 37, 13, Math.PI / 7, -Math.PI / 7);
context.lineTo(31, 37);
context.fill();
for(var i = 0; i < 8; i++){
context.fillRect(51 + i * 16, 35, 4, 4);
}
for(i = 0; i < 6; i++){
context.fillRect(115, 51 + i * 16, 4, 4);
}
for(i = 0; i < 8; i++){
context.fillRect(51 + i * 16, 99, 4, 4);
}
context.beginPath();
context.moveTo(83, 116);
context.lineTo(83, 102);
context.bezierCurveTo(83, 94, 89, 88, 97, 88);
context.bezierCurveTo(105, 88, 111, 94, 111, 102);
context.lineTo(111, 116);
context.lineTo(106.333, 111.333);
context.lineTo(101.666, 116);
context.lineTo(97, 111.333);
context.lineTo(92.333, 116);
context.lineTo(87.666, 111.333);
context.lineTo(83, 116);
context.fill();
context.fillStyle = "white";
context.beginPath();
context.moveTo(91, 96);
// context.bezierCurveTo(88, 96, 87, 99, 87, 101);
// context.bezierCurveTo(87, 103, 88, 106, 91, 106);
// context.bezierCurveTo(94, 106, 95, 103, 95, 101);
// context.bezierCurveTo(95, 99, 94, 96, 91, 96);
// 使用這一句代替以上的4個方法
context.ellipse(91,101,4,5,0,0,Math.PI*2);
context.moveTo(103, 96);
// context.bezierCurveTo(100, 96, 99, 99, 99, 101);
// context.bezierCurveTo(99, 103, 100, 106, 103, 106);
// context.bezierCurveTo(106, 106, 107, 103, 107, 101);
// context.bezierCurveTo(107, 99, 106, 96, 103, 96);
// 使用這一句代替以上的4個方法
context.ellipse(103,101,4,5,0,0,Math.PI*2);
context.fill();
context.fillStyle = "black";
context.beginPath();
context.arc(101, 102, 2, 0, Math.PI * 2, true);
context.fill();
context.beginPath();
context.arc(89, 102, 2, 0, Math.PI * 2, true);
context.fill();
}
// 封裝的一個用于繪制圓角矩形的函數
function roundedRect(context, x, y, width, height, radius){
context.beginPath();
context.moveTo(x, y + radius);
context.lineTo(x, y + height - radius);
context.quadraticCurveTo(x, y + height, x + radius, y + height);
context.lineTo(x + width - radius, y + height);
context.quadraticCurveTo(x + width, y + height, x + width, y + height - radius);
context.lineTo(x + width, y + radius);
context.quadraticCurveTo(x + width, y, x + width - radius, y);
context.lineTo(x + radius, y);
context.quadraticCurveTo(x, y, x, y + radius);
context.stroke();
}
Path2D:
Canvas 2D API包含一個Path2D的接口,此接口用來聲明路徑,此路徑會被2D上下文使用; Path2D擁有2D上下文的相同的路徑方法,它允許在canvas中根據需要創建可以保留并重用的路徑;
可以使用Path2D對象的各種方法繪制直線、矩形、圓形、橢圓以及曲線;如:moveTo(x,y)、lineTo(x,y)、rect(x,y,w,h)、arc(x,y,radius,startAngle,endAngle[,anticlockwise])、arcTo(x1,1,x2,y2,radiusX[,radius,rotation])、ellipse(x,y,radius,radius,rotation,startAngle,endAngle[,anticlockwise])、bezierCurveTo(cp1x,cp1y,cp2x,cp2y,x,y)、quadraticCurveTo(cpx,cpy,x,y)、closePath()
構造函數:Path([path | svgPath]),返回一個新的 Path2D 對象;參數path為另一個Path2D對象,這將該對象所代表的路徑復制給新創建的Path2D對象;
new Path2D();
new Path2D(path);
new Path2D(d);
還可以在Path2D的構造函數中傳遞一個代表SVG路徑的字符串;
var path = new Path2D("M10 10 h 80 v 80 h -80 Z");
context.fill(path);
創建完Path2D對象后,就可以利用所有繪制路徑的方法繪制路徑,并可以調用closePath()方法閉合路徑,如:
var path1 = new Path2D();
path1.rect(10,10,100,100);
var path2 = new Path2D(path1);
path2.moveTo(220,60);
path2.arc(170,60,50,0,Math.PI*2);
之后可以使用context對象的fill(path)和stroke(path)方法進行填充和描邊,參數path指向的Path2D對象;
context.stroke(path2);
// ...
for(var i=0;i<10;i++){
var path = new Path2D();
path.arc(i*25,i*25,i*10,0,Math.PI*2,true);
path.closePath();
context.fillStyle = "rgba(255,0,0,0.25)";
context.fill(path);
}
如:
var rectangle = new Path2D();
rectangle.rect(10, 10, 50, 50);
var circle = new Path2D();
circle.moveTo(125, 35);
circle.arc(100, 35, 25, 0, 2 * Math.PI);
context.stroke(rectangle);
context.fill(circle);
addPath(path [, transform])方法:
添加一條新路徑到對當前路徑;參數path為需要添加的 Path2D 路徑,參數transform是可選的,作為新增路徑的變換矩陣;
var p1 = new Path2D();
p1.rect(0,0,100,100);
var p2 = new Path2D();
p2.rect(0,0,100,100);
var m = document.createElementNS("http://www.w3.org/2000/svg", "svg").createSVGMatrix();
m.a = 1; m.b = 0;
m.c = 0; m.d = 1;
m.e = 300; m.f = 0;
p1.addPath(p2, m);
context.fill(p1);
圖形屬性:
圖形屬性指定了畫布的通用圖形狀態,例如fillStyle、strokeStyle等屬性,繪圖方法并沒有設置這些屬性,所以這些屬性應該在調用相關繪圖方法之前進行設置;這種將從圖形狀態和繪制指令分離的思想是畫布API很重要的概念,和在HTML文檔中應用CSS樣式來實現結構和樣式分離是類似的;
context對象上定義了15個圖形屬性:
fillStyle和strokeStyle屬性:指定了區域填充和線條勾勒的樣式;即可以指定顏色,也可以把它們設置為CanvasPattern或者CanvasGradient對象,以實現背景圖案或漸變色;
如果使用顏色,其使用標準的CSS顏色值,默認為“#000000”;具體的顏色格式有:
var colors = [
"#f44", // 16進制RGB,紅色
"#44ff44", // 16進制RRGGBB,綠色
"rgb(60,60,255)", // 0-255之間的整數表示的RGB,藍色
"rgb(100%, 25%, 100%)", // 百分比表示的RGB,紫色
"rgba(100%, 25%, 100%, 0.5)", // RGB加上0-1的alpha,半透明紫色
"rgba(0,0,0,0)", // 全透明黑色
"transparent", // 全透明
"hsl(60,100%, 50%)", // 全飽和黃色
"hsl(60, 75%, 50%)", // 低包包黃色
"hsl(60, 100%, 75%)", // 全飽和暗黃色
"hsl(60, 100%, 25%)", // 全飽和亮黃色
"hsla(60, 100%, 50%, 0.5)" // 全飽和黃色,50%透明
];
一旦設置了strokeStyle或fillStyle的值,那么這個新值就會成為新繪制的圖形的默認值,如果要給后續繪制的圖形使用不同的顏色,需要重新設置fillStyle或strokeStyle的值;
for (var i=0;i<6;i++){
for (var j=0;j<6;j++){ // 255/6=42.5
context.fillStyle = 'rgb(' + Math.floor(255-42.5*i) + ',' +
Math.floor(255-42.5*j) + ',0)';
context.fillRect(j*25,i*25,25,25);
}
}
如:
for (var i=0;i<6;i++){
for (var j=0;j<6;j++){
context.strokeStyle = 'rgb(0,' + Math.floor(255-42.5*i) + ',' +
Math.floor(255-42.5*j) + ')';
context.beginPath();
context.arc(12.5+j*25,12.5+i*25,10,0,Math.PI*2,true);
context.stroke();
}
}
如:
context.fillStyle = 'rgb(255,221,0)';
context.fillRect(0,0,150,37.5);
context.fillStyle = 'rgb(102,204,0)';
context.fillRect(0,37.5,150,37.5);
context.fillStyle = 'rgb(0,153,255)';
context.fillRect(0,75,150,37.5);
context.fillStyle = 'rgb(255,51,0)';
context.fillRect(0,112.5,150,37.5);
// 畫半透明矩形
for (var i=0; i<10; i++){
context.fillStyle = 'rgba(255,255,255,'+(i+1)/10+')';
for (var j=0; j<4; j++){
context.fillRect(5+i*14, 5+j*37.5, 14, 27.5)
}
}
globalAlpha屬性:全局透明度,其值是介于0到1之間的值,用于指定所有繪制的透明度,默認值為1;如:
context.fillStyle = "#ff0000";
context.fillRect(50,50,100,100);
// 修改全局透明度
context.globalAlpha= 0.5;
context.fillStyle = "rgba(0,0,255,1)";
context.fillRect(50,100,100,100);
如果將其設為0,所有繪制的圖形都會變成全透明;
繪制的每個像素都會將其alpha值乘以設置的globalAlpha值,例如:如果設置為0.5,那么所有繪制的原本不透明的像素都會變成50%的透明度,而原本是50%透明的,就變成25%的不透明度;
globalAlpha 屬性在需要繪制大量擁有相同透明度的圖形時候相當高效;如:
// 畫背景
context.fillStyle = '#FD0';
context.fillRect(0,0,75,75);
context.fillStyle = '#6C0';
context.fillRect(75,0,75,75);
context.fillStyle = '#09F';
context.fillRect(0,75,75,75);
context.fillStyle = '#F30';
context.fillRect(75,75,75,75);
context.fillStyle = '#FFF';
context.globalAlpha = 0.2;
// 畫半透明圓
for (var i=0;i<7;i++){
context.beginPath();
context.arc(75, 75, 10+10*i, 0, Math.PI*2, true);
context.fill();
}
線型Line styles:
lineWidth屬性:指定線條的寬度,值可以是任意整數;實際上可以是小于1的小數
context.lineWidth = 0.5;
context.strokeRect(100,50, 300, 200);
線可以通過stroke()、strokeRect()和strokeText()方法繪制;
lineWidth屬性沒有相像中的那么簡單:我們可以將路徑視為一個無限細的線條,當調用stroke()方法進行輪廓描邊時,它們是處于路徑的中間,兩邊都是lineWidth寬度的一半;
for (var i = 0; i < 10; i++){
context.lineWidth = 1+i;
context.beginPath();
context.moveTo(5+i*14, 5);
context.lineTo(5+i*14, 140);
context.stroke();
}
示意圖:
如果勾勒一條閉合的路徑并只希望線段出現在路徑之外,那么首先勾勒該路徑,然后用不透明顏色填充閉合區域,將出現在路徑內的勾勒部分隱藏;又或者如果只希望出在閉合路徑內,可以調用clip()方法;
線段寬度是受當前坐標系變換影響的,如果通過坐標系變換來對坐標軸進行縮放,例如調用scale(2,1)方法就會對X軸進行縮放,但是對Y軸不產生影響,如此,垂直的線段要比原先和它一樣寬的水平線段寬一倍;
lineCap屬性:為直線添加線帽,值:butt默認值,不為直線添加線帽,即在線段端直接結束;round圓形線帽,即在原端點的基礎上延長一個半圓;square正方形線帽,即在原端點的基礎上,再延長線段寬度一半的長度;如圖:
context.beginPath();
context.lineWidth = 10;
context.lineCap = "round";
context.moveTo(20,20);
context.lineTo(20,200);
context.stroke();
var lineCap = ['butt','round','square'];
// 繪制上下兩條水平線條
context.strokeStyle = '#09f';
context.beginPath();
context.moveTo(60,10);
context.lineTo(200,10);
context.moveTo(60,140);
context.lineTo(200,140);
context.stroke();
// 繪制垂直三條不同端點的線條
context.strokeStyle = 'black';
for (var i = 0; i < lineCap.length; i++) {
context.lineWidth = 15;
context.lineCap = lineCap[i];
context.beginPath();
context.moveTo(80+i*50,10);
context.lineTo(80+i*50,140);
context.stroke();
}
lineJoin屬性:指定兩條線段交匯時的拐角形狀,即在頂點處如何連接,值:miter默認值,尖角,表示一直延伸兩條線段的外側邊緣直到在某一點匯合;round圓角,表示將匯合的頂點變成圓形;bevel斜角,表示把匯合的頂點切除;
context.beginPath();
context.lineWidth = 10;
context.lineJoin = "bevel";
context.moveTo(20,150);
context.lineTo(80,50);
context.lineTo(160,150);
context.stroke();
// 分別使用3種不同的lineJoin
var lineJoin = ['round','bevel','miter'];
context.lineWidth = 10;
for (var i = 0; i < lineJoin.length; i++) {
context.lineJoin = lineJoin[i];
context.beginPath();
context.moveTo(200, 50+i*40);
context.lineTo(250, 95+i*40);
context.lineTo(300, 50+i*40);
context.lineTo(350, 95+i*40);
context.lineTo(400, 50+i*40);
context.stroke();
}
miterLimit屬性:當只有lineJoin屬性值是miter時才會起作用;當兩條線段相交的夾角為銳角的時候,兩條線段的斜接部分可以變得很長,該屬性可以指定斜接部分長度的上限,默認值是10.0;
context.lineWidth = 20;
console.log(context.miterLimit); // 10
context.miterLimit = 7;
context.moveTo(50,50);
context.lineTo(150,50);
// context.lineTo(50,150);
// context.lineTo(50,120);
context.lineTo(50,80); // 夾角小,斜接長度更長
context.stroke();
斜接最大長度是當前線寬與miterLimit屬性值的乘積的一半,即斜接限定值(miterLimit)是斜接長度與一半線寬的比值;如果指定的miterLimit值比這個比值還小的話,最終繪制出來的頂點就會是斜切的(bevel)而不是斜接的;
當給屬性賦值時,0、負數、 Infinity 和 NaN 都會被忽略;
setLineDash(segments)方法:自定義虛線形狀;參數segments為一個數組,描述了線段和線段間隙的交替長度;
context.beginPath();
context.setLineDash([5, 15]);
context.moveTo(0, 50);
context.lineTo(300, 50);
context.stroke();
如果數組只存在一個值,則表示線段與間隙長度都等于這個值;
如果數組中的元素超過2個,則數組中的數值數量為偶數,即第奇個數代表線段長度,第偶數個數表示間隙長度;
如果數組中的元素超過2個,且不為偶數,會自動復制一份該值,使其成為偶數量;
如果要切換回至實線模式,將 dash list 設置為一個空數組即可;
context.setLineDash([5]);
context.setLineDash([5,10,15,20]);
// context.setLineDash([5,10,15])變成context.setLineDash([5,10,15,5,10,15]);
context.setLineDash([5,10,15]);
context.setLineDash([]);
常見的虛線模式,如:
var y = 15;
drawDashedLine([]);
drawDashedLine([1, 1]); // 或[1]
drawDashedLine([10, 10]);
drawDashedLine([20, 5]);
drawDashedLine([15, 3, 3, 3]);
drawDashedLine([20, 3, 3, 3, 3, 3, 3, 3]);
drawDashedLine([12, 3, 3]); // Equals [12, 3, 3, 12, 3, 3]
function drawDashedLine(pattern) {
context.beginPath();
context.setLineDash(pattern);
context.moveTo(0, y);
context.lineTo(300, y);
context.stroke();
y += 20;
}
getLineDash方法:返回一個包含當前虛線樣式、長度為非負偶數的數組,如果數組元素的數量是奇數,數組元素會被復制并重復,如:
console.log(context.getLineDash()); // [12, 3, 3, 12, 3, 3]
lineDashOffset屬性:設置虛線偏移量,值是浮點數,初始值為0.0,可以為負;正值向左偏,負值向右偏,如:
context.lineDashOffset = 5;
示例:螞蟻線效果:
function draw(id){
var canvas = document.getElementById(id);
if(canvas==null){
return false;
}
var context = canvas.getContext("2d");
context.fillStyle = "#EEEEFF";
context.fillRect(0,0,400,300);
march(context); // [mɑ?t?]
}
var i=0;
function march(context){
i++;
if(i>16){
i=0;
}
console.log(i);
context.clearRect(5,5,110,110);
context.setLineDash([4,2]);
context.lineDashOffset = -i;
context.strokeRect(10,10,100,100);
setTimeout(function(){
march(context);
},50);
}
draw("canvas");
繪制漸變:
繪制線性漸變:
fillStyle方法除了填充指定顏色外,還可以用來指定填充的對象,比如漸變對象;
createLinearGradient(x1, y1, x2, y2)方法:
返回一個CanvasGradient漸變對象,參數x1與y1為漸變起始點坐標,x2和y2為結束點坐標;如:
var gradient = context.createLinearGradient(30, 30, 70, 70);
console.log(gradient); // CanvasGradient
使用該對象的addColorStop(pos, color)方法指定色標,參數pos指定色標位置,即一個偏移量,值為0到1之間的浮動點,如0.5 表示顏色會出現在正中間,第二個參數color指定顏色;該方法必須調用二次以上;
gradient.addColorStop(0, "white");
gradient.addColorStop(1, "black");
創建了漸變對象并指定了顏色后,再把該對象賦值給fillStyle或strokeStyle屬性,從而使用漸變來進行填充或描邊;
context.fillStyle = gradient;
context.strokeStyle = gradient;
context.fillRect(0, 0, 50, 50);
context.lineWidth = 20;
context.strokeRect(30, 30, 50, 50);
漸變的坐標位置是相對于畫布的,所以繪制的圖形和漸變對象的坐標必須匹配;否則,有可能只會顯示部分漸變效果;因此,確保漸變與圖形對齊非常重要,如:
function createRectLinearGradient(context, x, y, width, height){
return context.createLinearGradient(x, y, x + width, y + height);
}
// ... 使用
var gradient = createRectLinearGradient(context, 30, 30, 50, 50);
console.log(gradient); // CanvasGradient
如使用兩個漸變對象分別進行填充和描邊:
// Create gradients
var lingrad = context.createLinearGradient(0,0,0,150);
lingrad.addColorStop(0, '#00ABEB');
lingrad.addColorStop(0.5, '#fff');
lingrad.addColorStop(0.5, '#26C000');
lingrad.addColorStop(1, '#fff');
var lingrad2 = context.createLinearGradient(0,50,0,95);
lingrad2.addColorStop(0.5, '#000');
lingrad2.addColorStop(1, 'rgba(0,0,0,0)');
context.fillStyle = lingrad;
context.strokeStyle = lingrad2;
context.fillRect(10,10,130,130);
context.strokeRect(50,50,50,50);
如繪制若干個漸變圓和矩形:
var g = context.createLinearGradient(0,0,300,0);
g.addColorStop(0,"rgba(0,0,255,0.5)");
g.addColorStop(1,"rgba(255,0,0,0.5)");
context.fillStyle = g;
for(var i=0; i<10; i++){
// 漸變圓
// context.beginPath();
// context.arc(i*25,i*25,i*10,0,Math.PI*2,true);
// context.closePath();
// context.fill();
// 漸變矩形
context.fillRect(i*25, i*25, i*10, i*10);
}
繪制徑向漸變:
同理,可以使用createRadialGradient(x1, y1, radius1, x2, y2, radius2)方法來繪制徑向漸變; x1和y1為開始圓的圓心坐標,radius1為開始圓的半徑,x2和y2為結果圓的圓心坐標,radius2為結束圓的半徑;其他同線性漸變相同,也是使用addColorStop()方法添加色標;
var g = context.createRadialGradient(300,300,100,300,300,300);
g.addColorStop(0.0, "transparent");
g.addColorStop(0.7, "rgba(100,100,100,.9)");
g.addColorStop(1.0, "rgba(0,0,0,0)");
context.fillStyle = g;
context.fillRect(0,0,drawing.width, drawing.height);
如,繪制漸變圓:
var g1 = context.createRadialGradient(400,0,0,400,0,400);
g1.addColorStop(0.1,'rgb(255,255,0)');
g1.addColorStop(0.3,'rgb(255,0,255)');
g1.addColorStop(1,'rgb(0,255,255)');
context.fillStyle = g1;
context.fillRect(0,0,400,300);
var g2 = context.createLinearGradient(250,250,0,250,250,300);
g2.addColorStop(0.1,"rgba(255,0,0,0.5)");
g2.addColorStop(0.7,"rgba(255,255,0,0.5)");
g2.addColorStop(1,"rgba(0,0,255,0.5)");
context.fillStyle = g2;
for(var i=0;i<10;i++){
context.beginPath();
context.arc(i*25,i*25,i*10,0,Math.PI*2,true);
context.closePath();
context.fill();
}
如:
// 創建漸變
var radgrad = context.createRadialGradient(40,40,0,52,50,30);
radgrad.addColorStop(0, '#A7D30C');
radgrad.addColorStop(0.9, '#019F62');
radgrad.addColorStop(1, 'rgba(1,159,98,0)');
var radgrad2 = context.createRadialGradient(105,105,20,112,120,50);
radgrad2.addColorStop(0, '#FF5F98');
radgrad2.addColorStop(0.75, '#FF0188');
radgrad2.addColorStop(1, 'rgba(255,1,136,0)');
var radgrad3 = context.createRadialGradient(95,15,10,102,20,40);
radgrad3.addColorStop(0, '#00C9FF');
radgrad3.addColorStop(0.8, '#00B5E2');
radgrad3.addColorStop(1, 'rgba(0,201,255,0)');
var radgrad4 = context.createRadialGradient(0,150,50,0,140,90);
radgrad4.addColorStop(0, '#F4F201');
radgrad4.addColorStop(0.8, '#E4C700');
radgrad4.addColorStop(1, 'rgba(228,199,0,0)');
// 畫圖形
context.fillStyle = radgrad4;
context.fillRect(0,0,150,150);
context.fillStyle = radgrad3;
context.fillRect(0,0,150,150);
context.fillStyle = radgrad2;
context.fillRect(0,0,150,150);
context.fillStyle = radgrad;
context.fillRect(0,0,150,150);
模式(圖案)Pattern:
模式就是重復的圖像,也稱為填充圖案,可以用來填充或描邊圖形;使用createPattern(image,type)方法即可創建新模式,參數image為一個<img>元素或image對象,參數type指定重復的類型,其值與CSS的background-repeat值相同:no-repeat、repeat-x、repeat-y、repeat;返回的對象是CanvasPattern類型的模式對象;
創建完模式對象后,再賦給fillStyle即可,如:
var image = document.getElementsByTagName("img")[0];
var pattern = context.createPattern(image, "repeat");
console.log(pattern); // CanvasPattern
context.fillStyle = pattern;
context.fillRect(50, 50, 100, 100);
模式與漸變一樣,都是從畫布的原點(0,0)開始的;將模式對象賦給fillStyle屬性,只表示在某個特定的區域內顯示重復的圖像,而不是要從某個位置開始繪制重復的圖像;
還可以采用一個<canvas>元素作為另外一個<canvas>元素的圖案,如:
var offscreen = document.createElement("canvas"); // 創建一個屏幕外canvas
offscreen.width = offscreen.height = 10; // 設置大小
offscreen.getContext("2d").strokeRect(0,0,6,6); // 繪制
var pattern = context.createPattern(offscreen, "repeat");
context.fillStyle = pattern;
context.fillRect(0,0, drawing.width,drawing.height);
CanvasPattern類型的模式對象沒有任何屬性,只有一個setTransform()方法,其用于對圖案進行變形;
createPattern()方法的第一個參數,也可以是一個video元素,或者另一個canvas元素
繪制陰影:
2D上下文可以給圖形繪制陰影效果,其使用context對象的關于陰影屬性進行設置:shadowOffsetX、shadowOffsetY:橫向或縱向位移,負值表示陰影會往上或左位移,正值則表示會往下或右位移,默認值為0;
shadowColor:陰影顏色,默認是完全透明的,如果不指定透明度或顏色,該陰影是不可見的;
shadowBlur:可選屬性,表示圖形陰影邊緣的模糊范圍,值設定在0到10之間;
context.shadowOffsetX = 5;
context.shadowOffsetY = 5;
context.shadowColor = "rgba(0, 0, 0, 0.5)";
context.shadowBlur = 4;
context.fillStyle = '#FF0000';
context.fillRect(10,10, 50, 50);
context.fillStyle = 'rgba(0, 0, 255, 0.5)';
context.fillRect(10, 100, 50, 50);
context.shadowOffsetX = 10;
context.shadowOffsetY = 10;
context.shadowColor = "rgba(100,100,100,0.5)";
context.shadowBlur = 7.5;
context.translate(0,50);
for(var i=0;i<3;i++){
context.translate(50,50);
createStar(context);
context.fill();
}
function createStar(context){
var dx = 100;
var dy = 0;
var s = 50;
context.beginPath();
context.fillStyle = "rgba(255,0,0,0.5)";
var dig = Math.PI / 5 * 4;
for(var i=0;i<5;i++){
var x = Math.sin(i*dig);
var y = Math.cos(i*dig);
context.lineTo(dx+x*s,dy+y*s);
}
context.closePath();
}
shadowColor不允許使用圖案和漸變色;
shadowOffsetX和shdowOffsetY屬性總是在默認的坐標空間中度量的,它不受rotate()和scale()方法的影響;
保存與恢復狀態:
使用save()和restore()兩個方法,可以分別用于保存和恢復圖形上下文的當前繪制狀態;這里的繪畫狀態指的是坐標原點、變換矩陣,以及圖形上下文對象的當前屬性值等很多內容;
canvas狀態存儲在棧中,當調用save()方法時會將當前狀態設置保存到棧中,當多次調用save()時,會在棧中保存多個狀態,之后,在做完想做的工作后,再調用restore()從棧中取出之前保存的狀態進行恢復,即在棧結構中向前返回一級,連續調用restore()則可以逐級返回;如:
context.fillStyle = "#FF0000";
context.save();
context.fillStyle = "#00FF00";
context.translate(100, 100);
context.save();
context.fillStyle = "#0000FF";
// 從點(100,100)開始繪制藍色矩形
context.fillRect(0,0, 100, 100);
context.restore();
// 從點(110,110)開始繪制綠色矩形
context.fillRect(10,10,100,200);
context.restore();
// 從點(0,0,)開始繪制紅色矩形
context.fillRect(0,0,100,200);
save()保存的只是對繪圖上下文的設置和變換,不會保存繪圖的內容;具體可以應用在以下場合:圖像或圖形變形(包括即移動,旋轉和縮放)、圖像裁剪、改變圖形上下文的屬性(strokeStyle、fillStyle、globalAlpha、lineWidth、lineCap、lineJoin、miterLimit、lineDashOfffset、shadowBlur、shadowColor、shadowOffsetX、shadowOffsetY、globalCompositeOperation、font、textAlign、textBaseline、direction、imageSmothingEnabled);
示例:
context.fillRect(0,0,150,150); // 使用默認設置繪制一個矩形
context.save(); // 保存默認狀態
context.fillStyle = '#09F' // 在原有配置基礎上對顏色做改變
context.fillRect(15,15,120,120); // 使用新的設置繪制一個矩形
context.save(); // 保存當前狀態
context.fillStyle = '#FFF' // 再次改變顏色配置
context.globalAlpha = 0.5;
context.fillRect(30,30,90,90); // 使用新的配置繪制一個矩形
context.restore(); // 重新加載之前的顏色狀態
context.fillRect(45,45,60,60); // 使用上一次的配置繪制一個矩形
context.restore(); // 加載默認顏色配置
context.fillRect(60,60,30,30); // 使用加載的配置繪制一個矩形
繪制變換圖形:
在繪制圖形的時候,有可能需要旋轉、縮放圖形等變形處理;
變換方法:
translate(dx, dy):移動坐標原點到(dx, dy)處;參數dx和dy為X軸和Y軸的偏移量;
context.translate(100,100)
context.strokeRect(0,0,200,100);
context.beginPath();
context.arc(100,100,100,0,Math.PI*2);
context.stroke();
示例:
for (var i = 0; i < 3; i++) {
for (var j = 0; j < 3; j++) {
context.save();
context.fillStyle = 'rgb(' + (51 * i) + ', ' + (255 - 51 * i) + ', 255)';
context.translate(10 + j * 50, 10 + i * 50);
context.fillRect(0, 0, 25, 25); // 只關注寬和高,不再關注起始坐標
context.restore();
}
}
在一般的使用中,在使用變換時,會與狀態的保存與恢復配合使用,便于下一次的繪制;
rotate(angle):以坐標原點為中心點旋轉,angle為弧度,為正以順時針旋轉,為負則以逆時針旋轉;
context.rotate(Math.PI / 4);
context.strokeRect(200,50,200,100);
context.beginPath();
context.moveTo(200, 20);
context.lineTo(300, 20);
context.stroke();
在旋轉時,是以原點為中心點旋轉的,必要時,需要移動原點,否則有可能會旋轉到畫布的外側;
context.translate(100,100);
context.rotate(Math.PI / 4);
context.fillRect(0,0,200,100);
示例:繪制若干小不同顏色的小圓
context.translate(75,75);
for (var i=1; i<6; i++){
context.save();
context.fillStyle = 'rgb('+(51*i)+','+(255-51*i)+',255)';
for (var j=0; j<i*6; j++){
context.rotate(Math.PI*2/(i*6));
context.beginPath();
context.arc(0, i*12.5, 5, 0, Math.PI*2, true);
context.fill();
}
context.restore();
}
scale(scaleX, scaleY):放大縮小,兩個參數都是實數,值為0到1,默認值為1;可以為負數,x 為水平縮放因子,y 為垂直縮放因子,如果比1小,會縮小圖形,如果比1大會放大圖形;
context.scale(2, 1);
context.lineWidth = 20;
context.strokeRect(50,50,100,50);
默認情況下,canvas的1個單位為1個像素,如果設置的縮放因子是0.5,那么1個單位就變成對應0.5個像素,這樣繪制出來的形狀就會是原先的一半;同理,設置為2.0時,1個單位就對應變成了2像素,繪制的結果就是圖形放大了2倍;
如果線寬為1像素,且縮放因子小于1,此時,還是1像素呈現
context.save();
context.scale(0.5, 1); // 線寬為1的,因子小于1的還是1像素呈現
// context.lineWidth = 2;
context.strokeRect(50, 50, 100, 50);
context.restore();
context.save();
context.scale(10, 3);
context.fillRect(1, 100, 10, 10);
context.restore();
畫布初始情況下,是以左上角坐標為原點的第一象限,如果參數為負實數,相當于以 x 或 y 軸作為對稱軸鏡像反轉;例如:
context.translate(0, canvas.height);
context.scale(1,-1);
context.save();
context.lineWidth = 20;
context.moveTo(180,10);
context.lineTo(330,150);
context.lineTo(30,150);
context.closePath();
context.stroke(); // 變成倒三角了
context.restore();
context.translate(canvas.width, 0);
context.scale(-1, 1);
context.font = '48px serif';
context.fillText('ZERO', 10, 200);
變換有可能很簡單,有可能很復雜,這都要視情況而定,如繪制螺旋的長方形:
context.fillStyle = "#EEEEFF";
context.fillRect(0,0,400,300);
context.translate(200,50);
// context.strokeRect(0, 0, 4, 4); // 現原點
context.fillStyle = 'rgba(255,0,0,0.25)';
for(var i=0; i<50; i++){ // 把50轉成3,觀察繪制過程
context.translate(25,25);
// context.strokeRect(0, 0, 4, 4); // 現原點
context.scale(0.95,0.95);
context.rotate(Math.PI / 10);
context.fillRect(0,0,100,50);
}
示例:繪制表盤和表針
// ...把前的代碼換成以下的
context.translate(100,100);
context.rotate(1);
// 分針
context.moveTo(0, 0);
context.lineTo(0, -85);
// 時針
context.moveTo(0,0);
context.lineTo(-65,0);
// ...
示例:科赫雪花分形:
var deg = Math.PI / 180;
function snowflake(c, n, x, y, len){
c.save();
c.translate(x, y);
c.moveTo(0, 0);
draw(n);
c.rotate(-120 * deg);
draw(n);
c.rotate(-120 * deg);
draw(n);
c.closePath();
c.restore();
function draw(n){
c.save();
if(n == 0){
c.lineTo(len, 0);
}else{
c.scale(1/3, 1/3);
draw(n - 1);
c.rotate(60 * deg);
draw(n - 1);
c.rotate(-120 * deg);
draw(n - 1);
c.rotate(60 * deg);
draw(n - 1);
}
c.restore();
c.translate(len, 0);
}
}
snowflake(context, 0, 5, 115, 125);
snowflake(context, 1, 145, 115, 125);
snowflake(context, 2, 285, 115, 125);
snowflake(context, 3, 425, 115, 125);
snowflake(context, 4, 565, 115, 125);
context.stroke();
矩陣變換:
當利用坐標變換不能滿足我們的需要時,可以利用矩陣變換的技術;矩陣變換是專門用來實現圖形變形的,它與坐標一起配合使用,以達到變形的目的;
當context創建完畢后,進行一系列的屬性設置和繪圖操作,都是使用默認的坐標系;除了這個默認的坐標系,還有一個默認的“當前變換矩陣“,其作為當前圖形狀態的一部分,定義了畫布的當前坐標系;如果不對這個變換矩陣進行修改,那么接下來繪制的圖形將以畫布的最左上角為坐標原點繪制圖形,繪制出來的圖形也不經過縮放、變形處理,會被直接繪制,但是如果對這個變換矩陣進行修改,會導致使用不同的變換矩陣應用處理,從而產生不同的效果;當前變換矩陣是用來將指定的坐標轉換成為默認坐標系中的等價坐標。
transform(a, b, c, d, e, f),變換矩陣;
其使用一個新的變換矩陣與當前變換矩陣進行乘法運算,該變換矩陣的描述是:
a(m11) c(m21) e(dx)
b(m12) d(m22) f(dy)
0 0 1
其中,a(m11)、b(m12)、c(m21)、d(m22)這4個參數用來決定如何變形,分別表示水平縮放、垂直傾斜、水平傾斜、垂直縮放;e(dx)和f(dy)參數表示水平和垂直移動坐標原點;
context.save();
context.fillStyle = "red";
context.fillRect(0,0,100,100);
context.fillRect(100,120,100,100);
context.restore();
context.translate(100,120);
context.transform(2, Math.PI / 4, -Math.PI / 4, 0.5, 50, 50);
context.fillRect(0,0,100,100);
示例:通過變換繪制螺旋
context.translate(200,50);
for(var i=0; i<50; i++){
context.save();
context.transform(0.95,0,0,0.95,30,30);
context.rotate(Math.PI/12);
context.beginPath();
context.fillStyle = 'rgba(255,0,0,0.5)';
context.arc(0,0,50,0,Math.PI*2,true);
context.closePath();
context.fill();
}
示例:多條彩色弧
var colors = ["red","orange","yellow","green","blue","navy","purple"];
context.lineWidth = 10;
context.transform(1,0,0,1,100,0);
for(var i=0;i<colors.length;i++){
context.transform(1,0,0,1,0,10);
context.strokeStyle = colors[i];
context.beginPath();
context.arc(50,100,100,0,Math.PI,true);
context.stroke();
}
setTransform(a, b, c, d, e, f)方法:這個方法會將當前的變形矩陣重置為默認矩陣,然后用相同的參數調用transform()方法;從根本上來說,該方法是取消了當前變形,然后再設置為指定的變形,如:
// context.transform(1,1,0,1,0,0);
context.setTransform(1,1,0,1,0,0); // 起到相同的效果
context.fillRect(0,0,100,100);
它經常被用在,臨時將畫布重置為默認坐標系,如:
context.strokeStyle = "red";
context.strokeRect(30,10,60,20);
// 旋轉45度
var rad = 45 * Math.PI / 180;
context.setTransform(Math.cos(rad),Math.sin(rad),-Math.sin(rad),Math.cos(rad),0,0);
context.strokeStyle = "blue";
context.strokeRect(30,10,60,20);
// 放大2.5倍
context.setTransform(2.5,0,0,2.5,0,0);
context.strokeStyle = "green";
context.strokeRect(30,10,60,20);
// 移動坐標原點
context.setTransform(1,0,0,1,40,80);
context.strokeStyle = "gray";
context.strokeRect(30,10,60,20);
context.save(); // 保存當前默認坐標系
context.setTransform(1,0,0,1,0,0); // 恢復到默認坐標系
// 使用默認的坐標進行繪圖操作
context.restore(); // 恢復保存的坐標系
setTransform()與transform()區別:
// context.transform(2,0,0,1,0,0);
context.setTransform(2,0,0,1,0,0);
context.fillRect(0,0,100,100);
// context.transform(2,0,0,2,0,0);
context.setTransform(2,0,0,2,0,0);
context.fillStyle = "red";
context.fillRect(50,50,100,100);
再如:
var sin = Math.sin(Math.PI/6);
var cos = Math.cos(Math.PI/6);
context.translate(100, 100);
var c = 0;
for (var i=0; i <= 12; i++) {
c = Math.floor(255 / 12 * i);
context.fillStyle = "rgb(" + c + "," + c + "," + c + ")";
context.fillRect(0, 0, 100, 10);
context.transform(cos, sin, -sin, cos, 0, 0);
}
context.setTransform(-1, 0, 0, 1, 100, 100);
context.fillStyle = "rgba(255, 128, 255, 0.5)";
context.fillRect(0, 50, 100, 100);
resetTransform():方法
重置當前變形矩陣,它和調用以下語句是一樣的:context.setTransform(1, 0, 0, 1, 0, 0);
context.rotate(45 * Math.PI / 180);
context.fillRect(70,0,100,30);
context.resetTransform();
深入討論:
translate、scale、rotate三個方法,實際上都是隱式的修改了變換矩陣;
translate()方法只是簡單的將坐標原點進行上下左右移動;而rotate()方法會將坐標軸根據指定的角度進行順時針旋轉;scale()方法實現對X軸或Y軸上的距離進行延長和縮短;
假定變換后坐標系中的點的坐標是(x, y),其對應的默認坐標系的坐標為(x‘, y‘);
調用translate(dx, dy)方法等效于:
x’ = x + dx;
y’ = y + dy;
調用scale(sx, sy)方法等效于:
x’ = sx * x;
y’ = sy * y;
調用rotate(a)方法,可以通過三角法則計算,如:
x’ = x * cos(a) – y * sin(a);
y’ = y * cos(a) + x * sin(a);
坐標系變換是與變換順序相關的;假設從畫布默認的坐標系開始,先進行位移,再進行伸縮;如此操作后,要想將現有坐標系中的點(x, y)映射成默認坐標系中的點(x’’, y’’),必須首先應用等效縮放等式把該點映射到未縮放坐標系中的一個中間點(x’, y’),然后再使用等效的位移將中間點再映射到原來坐標系中的點(x’’, y’’),結果是:
x’’ = sx * x + dx;
y’’ = sy * y + dy;
如果先調用scale()再調用translate()的話,那等效的結果就不同了,結果是:
x’’ = sx * (x + dx);
y’’ = sy * (y + dy);
translate、scale、rotate三個方法都可以使用transform方法進行處理:
translate(x, y)可以使用transform(1, 0, 0, 1, x, y)代替,參數1,0,0,1表示不對圖形進行縮放傾斜操作,只是位移x和y;
scale(x, y)可以使用transform(x, 0, 0, y, 0, 0)代替,參數x,0,0,y表示將圖形橫向擴大x倍,縱向擴大y倍;
rotate(angle)替換方法:
transform(Math.cos(angle * Math.PI / 180), Math.sin(angle * Math.PI / 180),-Math.sin(angle * Math.PI / 180), Math.cos(angle * Math.PI / 180),0,0)
其中前4個參數以三角函數的形式結合起來,共同完成圖形按angle角度的順時針旋轉處理;
近各個渲染器風頭漸起
是時候放一點Vray干貨了!
Vray提升真實的照片級渲染的3點技巧
快點隨小吧一探究竟吧
↓↓↓
1、使用表面瑕疵
留心現實世界的物體,你可以看到他們的表面總是有一些瑕疵的,諸如污垢、劃痕或者指紋,所以當渲染中的對象看起來太干凈或太完美,它就會顯得有點不真實。
【貼圖下載】
這時我們需要一個特殊的黑白紋理貼圖,稱為表面瑕疵貼圖。在如下兩個網站你可以找到大量優秀的免費樣本——
? https://www.poliigon.com/search?is_fr
? http://dgruwier.dk/textures.html
【創建混合材質】
打開Vray資源編輯器轉到材質選項卡,創建材質;接下來將漫反射顏色設置為白色,創建一個25‘×25’的方格,并將這種材質應用到表面,然后開始交互式渲染。
選擇一種材質作為基底設置(這里是壓木材質),使用紋理作為不透明貼圖,設置為疊加層,現在可以在我們的地板材質上看到一些劃痕了。
2、倒角你的邊線
同樣的思路,現實世界中也沒有完美的90度邊緣,但在計算機圖形中我們默認獲得直線和銳邊,為了中和這點,必要時需要將尖角切削或倒角。
Round Corner工具
選擇我們想要倒角的邊緣,使用Fredo6插件集中的三維圓角(SUAPP編號155),調整偏移值設置圓角。
要解決邊緣的圓角影響紋理錯位這一問題,需要確保這些幾何形狀在一個組中,然后右鍵單擊該組,并找到Vray UV Tools —> 三平面投影(世界),這樣就能修復紋理。
(倒圓角后VS倒圓角前)
3、使用環境光遮擋
什么是環境光遮擋?
環境遮擋(或AO)是一種著色技術,讓你在3D對象的裂縫和裂縫中創建柔和的陰影。
環境遮擋可以提供更多深度,并具體到幾何深度,為你的場景中添加更多真實感。
如何將AO添加到渲染?
方法一:
使用Vray內置的Ambient Occlusion設置;在設置選項卡下找到“全局照明”,展開向下找到并開啟“Ambient Occlusion”。
(半徑調整↑)
(遮擋量調整↑)
方法二:
創建一個環境光遮蔽通道;首先在渲染元素選項欄添加一個“ExtraTex”渲染元素,然后點擊下側添加一張污垢紋理貼圖,渲染完成后保存這個渲染元素以及原始渲染。
然后打開Photoshop,把這個圖層放在渲染原圖的頂部,然后將“混合模式”設置為“正片疊底”。
通過調整不透明度減輕或加強效果,甚至使用圖層蒙版隱藏部分你不想表現的環境遮擋物。
注:圖文只是簡述
詳細操作流程建議觀看視頻更佳
戳這里:完整視頻
【Vray軟件下載地址】
http://www.sketchupbar.com/download
【案例SU模型及PSD文件下載地址】
https://www.patreon.com/archinspirations
*請認真填寫需求信息,我們會在24小時內與您取得聯系。