整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          第75節 Canvas繪圖(上)-前端開發之Java

          第75節 Canvas繪圖(上)-前端開發之JavaScript-王唯

          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":non-zero winding rule(非零環繞規則), 默認值;
          • "evenodd":even-odd winding rule;(奇偶環繞規則)

          非零環繞原則(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()指定起點位置,例如:先將當前點和弧形的起點用一條直線連接,然后再用圓的一部分(圓?。﹣磉B接弧形的起點和終點,并把弧形終點作為新的當前點;

          // 繪制一個圓弧
          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();
          }

          繪制不同的圓?。?/p>

          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:勾勒線段時的顏色、漸變或圖案
          • linewidth屬性:指定線條的寬度,值可以是任意整數;
          • lineCap屬性:如何渲染線段的末端,即為直線添加線帽,值:butt默認值,不為直線添加線帽,round圓形線帽,square正方形線帽;
          • lineJoin屬性:如何渲染頂點,即指定兩條直線交匯時的拐角形狀,值:miter默認值,尖角;round圓角;bevel斜角;
          • miterLimit:斜接頂點的最大長度;
          • font:繪制文本時的CSS字體;
          • textAlign:文本水平對齊;
          • textBaseline:垂直對齊方式;
          • shadowBlur:陰影模糊程序;
          • shadowColor:陰影顏色;
          • shadowOffsetX:陰影水平偏移量;
          • shadowOffsetY:陰影垂直偏移量;
          • globalAlpha:繪制像素時要添加的透明度;
          • globalCompositeOperation:如何合并新的像素點和下面的像素點;

          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角度的順時針旋轉處理;

          TML5中的一個新增元素——元素以及伴隨這個元素而來的一套編程接口——canvas API。使用canvas API可以在頁面上繪制出任何你想要的、非常漂亮的圖形與圖像,創造出更加豐富多彩、賞心悅目的Web頁面。

          canvas的概念最初是由蘋果公司提出的,用于在Mac OS X WebKit中創建控制板部件(Dashboard Widget)。在canvas出現之前,開發人員若要在瀏覽器中使用繪圖API,只能使用Adobe的Flash和SVG(可伸縮矢量圖形)插件,或者只有IE才支持的VML(矢量標記語言),以及JavaScript中的一些技術。假設我們要在沒有canvas元素的條件下繪制一條對角線,此時如果沒有一套二維繪圖API的話,這會是一項相當復雜的工作。HTML5中的canvas就能夠提供這樣的功能,對瀏覽器端來說這個功能非常有用,因此canvas被納入了HTML5規范。

          在canvas元素里進行繪畫,并不是指拿鼠標來作畫。在網頁上使用canvas元素時,它會創建一塊矩形區域。默認情況下該矩形區域寬為300像素,高為150像素,用戶可以自定義具體的大小或者設置canvas元素的其他特性。在頁面中加入了canvas元素后,我們便可以通過JavaScript來自由地控制它。可以在其中添加圖片、線條以及文字,也可以在里面繪圖,甚至還可以加入高級動畫。

          【前端開發】最新前端入門教程,html css基礎教程+移動端前端視頻教程。_嗶哩嗶哩_bilibili

          ython程序開發之簡單小程序實例

          (9)利用Canvas繪制圖形和文字

          一、項目功能

          利用Tkinter組件中的Canvas繪制圖形和文字。

          二、項目分析

          要在窗體中繪制圖形和文字,需先導入Tkinter組件,然后用Canvas完成繪制。

          三、程序源代碼

          源碼截圖:

          #!/usr/bin/python3.6

          # -*- coding: GBK -*-

          from tkinter import *

          root=Tk()

          root.title('繪制圖形與字體')

          # 創建畫布,設置其背景色為白色

          cv=Canvas(root,bg='white', width=500, height=300)

          cv.pack(fill=BOTH, expand=YES)

          # 創建一個正方形,設置填充色為藍色

          cv.create_rectangle(10,10,50,50,fill='blue')

          # 創建一個圓形,設置填充色為黑色

          cv.create_oval(90, 10, 140, 50,fill='green')

          # 創建一個扇形

          coord=300, 10, 180, 100

          # 設置扇形的起始角度為30,弧度為120,設置填充色為綠色

          cv.create_arc(coord, start=30, extent=120, fill="magenta")

          # 在窗體指定位置中插入自定義圖片

          mypic=PhotoImage(file="myball.gif")

          cv.create_image(370, 35,image=mypic)

          # 創建字體

          columnFont=('黑體', 15)

          for i, st in enumerate(['大飛狼', '小面羊', '大美呂', '小孔容', '天屎好美']):

          cv.create_text((10 + i * 100, 90),

          text=st,

          font=columnFont,

          fill='red',

          anchor=W,

          justify=LEFT)

          cv.pack()

          root.mainloop()

          top.mainloop()

          四、代碼解釋

          第一行為引用python版本,本實例為python3.6

          第二行是程序編碼引用,因為在程序中包含有中文字符,所以必須引用GBK,否則就會報錯。

          第七行至第三十三行為功能實現主體,每段代碼的具體功能可在代碼的注釋中查看。

          五、運行

          1、在新窗體中繪制圖形和字體

          圖形的前三個分別是用Canvas的create_rectangle(繪制矩形)、create_oval(繪制圓形)、create_arc(繪制弧形)實現,第四個為插入的自制GIF圖片。

          下一篇:《Python程序開發之簡單小程序實例(10)》

          更多精彩內容將在以后的章節分享給朋友們,請添加好友至收藏,歡迎點贊并關注后期內容更新!


          主站蜘蛛池模板: 一区二区三区久久精品| 亚洲国产欧美一区二区三区 | 亚洲A∨无码一区二区三区| 国产自产对白一区| 色窝窝无码一区二区三区成人网站| 国产精品久久久久一区二区| 亚洲sm另类一区二区三区| 国产99精品一区二区三区免费| 国产在线精品观看一区| 精品人无码一区二区三区 | 亚洲视频在线一区二区| 亚洲AV日韩综合一区尤物| 国精无码欧精品亚洲一区| 国产激情一区二区三区 | 性色AV 一区二区三区| 亚洲中文字幕在线无码一区二区| 亚洲色婷婷一区二区三区| 国产一区二区三区无码免费| 国产精品99无码一区二区| 国产精品无码一区二区三区免费| 无码乱人伦一区二区亚洲| 伊人激情AV一区二区三区| 亚洲一区二区三区乱码A| 四虎永久在线精品免费一区二区 | 亚洲A∨无码一区二区三区| 久久精品国产一区二区电影| 丰满爆乳一区二区三区| 亚洲国产精品一区二区成人片国内| 91精品一区二区三区在线观看| 国产精品区AV一区二区| 久夜色精品国产一区二区三区| 国产婷婷色一区二区三区| 中文字幕乱码人妻一区二区三区| 免费精品一区二区三区第35| 国产av熟女一区二区三区| 无码少妇一区二区浪潮免费| 亚洲一区二区精品视频| 国产福利电影一区二区三区久久老子无码午夜伦不 | 亚洲日韩中文字幕一区| 国精产品999一区二区三区有限 | 一区二区在线免费观看|