­

你不知道的Canvas(一)

  • 2019 年 10 月 24 日
  • 筆記

Canvas基礎

一、Canvas是什麼

Canvas是一個可以使用腳本(通常為JavaScript來繪製圖形的HTML) 元素.例如,它可以用於繪製圖表、製作圖片構圖或者製作簡單的動畫,主要用來繪製2D圖形。

Canvas的默認高度為300px*150px,可以使用HTML的高度和寬度屬性來自定義Canvas 的尺寸。為了在 Canvas 上繪製圖形,我們使用一個JavaScript上下文對象,它能動態創建影像。

二、<canvas>元素

<canvas id="myCanvas" width="150" height="150"></canvas>

<canvas>元素跟<img>元素很像,唯一的不同就是它並沒有 src 和 alt 屬性。實際上,<canvas> 標籤只有兩個可選的屬性—— widthheight

1.替換內容

<canvas><img>的不同之處在於,就像<video><audio><picture>元素一樣,很容易定義一些替代內容。由於某些較老的瀏覽器(尤其是IE9之前的IE瀏覽器)或者文本瀏覽器不支援HTML元素"canvas",在這些瀏覽器上你應該總是能展示替代內容。不支援<canvas>的瀏覽器將會忽略容器並在其中渲染後備內容。而支援<canvas>的瀏覽器將會忽略在容器中包含的內容,並且只是正常渲染canvas。

舉個例子,我們可以提供對canvas內容的文字描述或者是提供動態生成內容相對應的靜態圖片,如下所示:

<canvas id="stockGraph" width="150" height="150">    current stock price: $3.15 +0.15  </canvas>    <canvas id="clock" width="150" height="150">    <img src="images/clock.png" width="150" height="150" alt=""/>  </canvas>  

2.渲染上下文

<canvas>元素創造了一個固定大小的畫布,它公開了一個或多個渲染上下文,其可以用來繪製和處理要展示的內容。我們將會將注意力放在2D渲染上下文中。其他種類的上下文也許提供了不同種類的渲染方式。

canvas起初是空白的。為了展示,首先腳本需要找到渲染上下文,然後在它的上面繪製。

<canvas>元素有一個叫做getContext()的方法,這個方法是用來獲得渲染上下文和它的繪畫功能。getContext()只有一個參數,上下文的格式。

var canvas = document.getElementById('myCanvas');  var ctx = canvas.getContext('2d');

3.檢查支援性

替換內容適用於在不支援<canvas>標籤的瀏覽器中展示的。通過簡單測試getContext()方法的存在,腳本可以檢查編程支援性。

4.模板骨架

<html>    <head>      <title>Canvas tutorial</title>      <script type="text/javascript">        function draw(){          var canvas = document.getElementById('myCanvas');          if (canvas.getContext){            var ctx = canvas.getContext('2d');          }        }      </script>      <style type="text/css">        canvas { border: 1px solid black; }      </style>    </head>    <body onload="draw();">      <canvas id="myCanvas" width="150" height="150"></canvas>    </body>  </html>

上面的腳本中包含一個叫做draw()的函數,當頁面載入結束的時候就會執行這個函數。通過使用在文檔上載入事件來完成。只要頁面載入結束,這個函數,或者像是這個的,同樣可以使用 window.setTimeout()window.setInterval()或者其他任何事件處理程式來調用。

三、繪製圖形

1.繪製長方形

一開始,讓我們來看個簡單的例子,我們繪製了兩個有趣的長方形,其中的一個有著alpha透明度。

<!DOCTYPE html>  <!--[if lt IE 7]>      <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->  <!--[if IE 7]>         <html class="no-js lt-ie9 lt-ie8"> <![endif]-->  <!--[if IE 8]>         <html class="no-js lt-ie9"> <![endif]-->  <!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->      <head>          <meta charset="utf-8">            <title>繪製長方形</title>            <script type="text/javascript">              function draw() {                  var canvas = document.getElementById('myCanvas');                  if(canvas.getContext) {                      var ctx = canvas.getContext('2d');                        ctx.fillStyle = "rgba(200, 0 ,0 ,1)";                      ctx.fillRect(10, 10, 55, 50);                        ctx.fillStyle = "rgba(0, 0, 200, 0.5)"                      ctx.fillRect(30, 30, 55, 50);                  }              }          </script>      </head>      <body onload="draw()">          <!--[if lt IE 7]>              <p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="#">upgrade your browser</a> to improve your experience.</p>          <![endif]-->          <canvas id="myCanvas" width="150" height="150"></canvas>      </body>  </html>

fillRect() 是Canvas 2D API 繪製填充矩形的方法。矩形的起點在 (x, y) 位置,矩形的尺寸是 widthheightfillStyle 屬性決定矩形的樣式。

canvas提供了三種方法繪製矩形:

  • fillRect(x, y, width, height)

    繪製一個填充的矩形

  • strokeRect(x, y, width, height)

    繪製一個矩形的邊框

  • clearRect(x, y, width, height)

    清除指定矩形區域,讓清除部分完全透明。

    上面提供的方法之中每一個都包含了相同的參數。x與y指定了在canvas畫布上所繪製的矩形的左上角(相對於原點)的坐標。width和height設置矩形的尺寸。

2.柵格

在我們開始畫圖之前,我們需要了解一下畫布柵格(canvas grid)以及坐標空間。上文的HTML模板中有個寬150px, 高150px的canvas元素。如圖所示,canvas元素默認被網格所覆蓋。通常來說網格中的一個單元相當於canvas元素中的一像素。柵格的起點為左上角(坐標為(0,0))。所有元素的位置都相對於原點定位。所以圖中藍色方形左上角的坐標為距離左邊(X軸)x像素,距離上邊(Y軸)y像素(坐標為(x,y))。在課程的最後我們會平移原點到不同的坐標上,旋轉網格以及縮放。現在我們還是使用原來的設置。

現在使用三種方法繪製長方形:

<!DOCTYPE html>  <!--[if lt IE 7]>      <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->  <!--[if IE 7]>         <html class="no-js lt-ie9 lt-ie8"> <![endif]-->  <!--[if IE 8]>         <html class="no-js lt-ie9"> <![endif]-->  <!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->      <head>          <meta charset="utf-8">            <title>繪製長方形</title>            <script type="text/javascript">              function draw() {                  var canvas = document.getElementById('myCanvas');                  if(canvas.getContext) {                      var ctx = canvas.getContext('2d');                        ctx.fillStyle = "rgba(200, 0 ,0 ,1)";                      //繪製一個填充的矩形                      ctx.fillRect(10, 10, 55, 50);                        ctx.fillStyle = "rgba(0, 0, 200, 0.5)"                      ctx.fillRect(30, 30, 55, 50);                        ctx.fillStyle = "rgba(100, 210, 200, 1)";                      //繪製一個矩形的邊框                      ctx.strokeRect(40, 40, 55, 50);                        ctx.fillStyle = "rgba(20, 50, 120, 1)";                      //清除指定矩形區域,讓清除部分完全透明。                      ctx.clearRect(40, 40, 55, 50);                  }              }          </script>      </head>      <body onload="draw()">          <!--[if lt IE 7]>              <p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="#">upgrade your browser</a> to improve your experience.</p>          <![endif]-->          <canvas id="myCanvas" width="150" height="150"></canvas>      </body>  </html>

fillRect()函數繪製了一個邊長為100px的黑色正方形。clearRect()函數擦除了一個5550px的正方形,接著strokeRect()在清除區域內生成一個5550px的正方形邊框。

接下來我們能夠看到clearRect()的兩個可選方法,然後我們會知道如何改變渲染圖形的填充顏色及描邊顏色。

3.繪製路徑

圖形的基本元素是路徑。路徑是通過不同顏色和寬度的線段或曲線相連形成的不同形狀的點的集合。一個路徑,甚至一個子路徑,都是閉合的。使用路徑繪製圖形需要一些額外的步驟。

  1. 首先,你需要創建路徑起始點。
  2. 然後你使用畫圖命令去畫出路徑。
  3. 之後你把路徑封閉。
  4. 一旦路徑生成,你就能通過描邊或填充路徑區域來渲染圖形。

以下是所要用到的函數:

  • beginPath()

    新建一條路徑,生成之後,圖形繪製命令被指向到路徑上生成路徑。

  • closePath()

    閉合路徑之後圖形繪製命令又重新指向到上下文中。

  • stroke()

    通過線條來繪製圖形輪廓。

  • fill()

通過填充路徑的內容區域生成實心的圖形。

生成路徑的第一步叫做beginPath()。本質上,路徑是由很多子路徑構成,這些子路徑都是在一個列表中,所有的子路徑(線、弧形、等等)構成圖形。而每次這個方法調用之後,列表清空重置,然後我們就可以重新繪製新的圖形。

第二步就是調用函數指定繪製路徑。

第三,就是閉合路徑closePath(),不是必需的。這個方法會通過繪製一條從當前點到開始點的直線來閉合圖形。如果圖形是已經閉合了的,即當前點為開始點,該函數什麼也不做。

4.繪製三角形

<!DOCTYPE html>  <!--[if lt IE 7]>      <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->  <!--[if IE 7]>         <html class="no-js lt-ie9 lt-ie8"> <![endif]-->  <!--[if IE 8]>         <html class="no-js lt-ie9"> <![endif]-->  <!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->      <head>          <meta charset="utf-8">          <meta http-equiv="X-UA-Compatible" content="IE=edge">          <title>三角形</title>          <meta name="description" content="">          <meta name="viewport" content="width=device-width, initial-scale=1">          <link rel="stylesheet" href="">          <script type="text/javascript">              function draw() {                  var canvas = document.getElementById('myCanvas');                  if(canvas.getContext) {                      var ctx = canvas.getContext('2d');                        ctx.beginPath();                      ctx.moveTo(75, 50);                      ctx.lineTo(100, 75);                      ctx.lineTo(100, 25);                      ctx.fill();                  }              }          </script>      </head>      <body onload="draw();">          <!--[if lt IE 7]>              <p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="#">upgrade your browser</a> to improve your experience.</p>          <![endif]-->          <canvas id="myCanvas" width="150" height="150"></canvas>          <script src="" async defer></script>      </body>  </html>

5.移動筆觸

一個非常有用的函數,而這個函數實際上並不能畫出任何東西,也是上面所描述的路徑列表的一部分,這個函數就是moveTo()。或者你可以想像一下在紙上作業,一支鋼筆或者鉛筆的筆尖從一個點到另一個點的移動過程。

moveTo(x, y)

將筆觸移動到指定的坐標x以及y上。

當canvas初始化或者beginPath()調用後,你通常會使用moveTo()函數設置起點。我們也能夠使用moveTo()繪製一些不連續的路徑。看一下下面的笑臉例子。我將用到moveTo()方法(紅線處)的地方標記了。

6.繪製笑臉

<!DOCTYPE html>  <!--[if lt IE 7]>      <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->  <!--[if IE 7]>         <html class="no-js lt-ie9 lt-ie8"> <![endif]-->  <!--[if IE 8]>         <html class="no-js lt-ie9"> <![endif]-->  <!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->      <head>          <meta charset="utf-8">          <meta http-equiv="X-UA-Compatible" content="IE=edge">          <title></title>          <meta name="description" content="">          <meta name="viewport" content="width=device-width, initial-scale=1">          <link rel="stylesheet" href="">          <script type="text/javascript">              function draw() {                  var canvas = document.getElementById('myCanvas');                  if(canvas.getContext) {                      var ctx = canvas.getContext('2d');                        ctx.beginPath();                      //繪製                          ctx.arc(75,75,50,0,Math.PI*2,true);                      ctx.moveTo(110,75);                      //口(順時針)                      ctx.arc(75,75,35,0,Math.PI,false);                      ctx.moveTo(65,65);                      //左眼                      ctx.arc(60,65,5,0,Math.PI*2,true);                      ctx.moveTo(95,65);                      //右眼                      ctx.arc(90,65,5,0,Math.PI*2,true);                      ctx.stroke();                  }              }          </script>      </head>      <body onload="draw();">          <!--[if lt IE 7]>              <p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="#">upgrade your browser</a> to improve your experience.</p>          <![endif]-->          <canvas id="myCanvas" width="150" height="150"></canvas>          <script src="" async defer></script>      </body>  </html>

效果如下:

7.繪製直線

繪製直線,需要用到的方法時lineTo()方法。

lineTo(x,y)表示繪製一條從當前位置到指定x以及y位置的直線。該方法有兩個參數:x以及y ,代表坐標系中直線結束的點。開始點和之前的繪製路徑有關,之前路徑的結束點就是接下來的開始點,開始點也可以通過moveTo()函數改變。

下面的例子繪製兩個三角形,一個是填充的,另一個是描邊的。

<!DOCTYPE html>  <!--[if lt IE 7]>      <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->  <!--[if IE 7]>         <html class="no-js lt-ie9 lt-ie8"> <![endif]-->  <!--[if IE 8]>         <html class="no-js lt-ie9"> <![endif]-->  <!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->      <head>          <meta charset="utf-8">          <meta http-equiv="X-UA-Compatible" content="IE=edge">          <title></title>          <meta name="description" content="">          <meta name="viewport" content="width=device-width, initial-scale=1">          <link rel="stylesheet" href="">          <script type="text/javascript">              function draw() {                  var canvas = document.getElementById('myCanvas');                  if(canvas.getContext) {                      var ctx = canvas.getContext('2d');                        //填充三角形                      ctx.beginPath();                      ctx.moveTo(25,25);                      ctx.lineTo(105,25);                      ctx.lineTo(25,105);                      ctx.fill();                        //描邊三角形                      ctx.beginPath();                      ctx.moveTo(125,125);                      ctx.lineTo(125,45);                      ctx.lineTo(45,125);                      ctx.closePath();                      ctx.stroke();                  }              }          </script>      </head>      <body onload="draw();">          <!--[if lt IE 7]>              <p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="#">upgrade your browser</a> to improve your experience.</p>          <![endif]-->          <canvas id="myCanvas" width="150" height="150"></canvas>          <script src="" async defer></script>      </body>  </html>

效果如下:

8.圓弧

繪製圓弧或者圓,我們使用arc()方法。當然可以使用arcTo(),不過這個的實現並不是那麼的可靠。

  • arc(x, y, radius, startAngle, endAngle, anticlockwise)

    畫一個以(x,y)為圓心的以radius為半徑的圓弧(圓),從startAngle開始到endAngle結束,按照anticlockwise給定的方向(默認為順時針)來生成。

  • arcTo(x1, y1, x2, y2, radius)

    根據給定的控制點和半徑畫一段圓弧,再以直線連接兩個控制點。

這裡詳細介紹一下arc方法,該方法有六個參數:x,y為繪製圓弧所在圓上的圓心坐標。radius為半徑。startAngle以及endAngle參數用弧度定義了開始以及結束的弧度。這些都是以x軸為基準。參數anticlockwise為一個布爾值。為true時,是逆時針方向,否則順時針方向。

注意:arc()函數中表示角的單位是弧度,不是角度。角度與弧度的js表達式:

弧度=(Math.PI/180)*角度。

下面兩個for循環,生成圓弧的行列(x,y)坐標。每一段圓弧的開始都調用beginPath()。程式碼中,每個圓弧的參數都是可變的,實際編程中,我們並不需要這樣做。

x,y坐標是可變的。半徑(radius)和開始角度(startAngle)都是固定的。結束角度(endAngle)在第一列開始時是180度(半圓)然後每列增加90度。最後一列形成一個完整的圓。

clockwise語句作用於第一、三行是順時針的圓弧,anticlockwise作用於二、四行為逆時針圓弧。if語句讓一、二行描邊圓弧,下面兩行填充路徑。

<!DOCTYPE html>  <!--[if lt IE 7]>      <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->  <!--[if IE 7]>         <html class="no-js lt-ie9 lt-ie8"> <![endif]-->  <!--[if IE 8]>         <html class="no-js lt-ie9"> <![endif]-->  <!--[if gt IE 8]><!-->  <html class="no-js">  <!--<![endif]-->    <head>      <meta charset="utf-8">      <meta http-equiv="X-UA-Compatible" content="IE=edge">      <title></title>      <meta name="description" content="">      <meta name="viewport" content="width=device-width, initial-scale=1">      <link rel="stylesheet" href="">      <script type="text/javascript">          function draw() {              var canvas = document.getElementById('myCanvas');              if (canvas.getContext) {                  var ctx = canvas.getContext('2d');                      for (var i = 0; i < 4; i++) {                      for (var j = 0; j < 3; j++) {                          ctx.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; // 順時針或逆時針                          ctx.arc(x, y, radius, startAngle, endAngle, anticlockwise);                          if (i > 1) {                              ctx.fill();                          } else {                              ctx.stroke();                          }                      }                  }              }          }      </script>  </head>    <body onload="draw();">      <!--[if lt IE 7]>              <p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="#">upgrade your browser</a> to improve your experience.</p>          <![endif]-->      <canvas id="myCanvas" width="150" height="200"></canvas>      <script src="" async defer></script>  </body>    </html>

效果如下:

9.二次貝塞爾曲線及三次貝塞爾曲線

二次及三次貝塞爾曲線都十分有用,一般用來繪製複雜有規律的圖形。

  • quadraticCurveTo(cp1x, cp1y, x, y)

    繪製二次貝塞爾曲線,cp1x,cp1y為一個控制點,x,y為結束點。

  • bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)

    繪製三次貝塞爾曲線,cp1x,cp1y為控制點一,cp2x,cp2y為控制點二,x,y為結束點。

下面的圖能夠很好的描述兩者的關係,二次貝塞爾曲線有一個開始點(藍色)、一個結束點(藍色)以及一個控制點(紅色),而三次貝塞爾曲線有兩個控制點。

參數x、y在這兩個方法中都是結束點坐標。cp1x,cp1y為坐標中的第一個控制點,cp2x,cp2y為坐標中的第二個控制點。

使用二次以及三次貝塞爾曲線是有一定的難度的,因為不同於像Adobe Illustrators這樣的矢量軟體,我們所繪製的曲線沒有給我們提供直接的視覺回饋。這讓繪製複雜的圖形變得十分困難。在下面的例子中,我們會繪製一些簡單有規律的圖形,如果你有時間以及更多的耐心,很多複雜的圖形你也可以繪製出來。

9.1二次貝塞爾曲線

這個例子使用多個貝塞爾曲線來渲染對話氣泡。

function draw() {   var canvas = document.getElementById('canvas');   if (canvas.getContext) {   var ctx = canvas.getContext('2d');     // 二次貝塞爾曲線   ctx.beginPath();   ctx.moveTo(75,25);   ctx.quadraticCurveTo(25,25,25,62.5);   ctx.quadraticCurveTo(25,100,50,100);   ctx.quadraticCurveTo(50,120,30,125);   ctx.quadraticCurveTo(60,120,65,100);   ctx.quadraticCurveTo(125,100,125,62.5);   ctx.quadraticCurveTo(125,25,75,25);   ctx.stroke();    }  }

效果如下:

9.2三次貝塞爾曲線

這個例子使用貝塞爾曲線繪製心形。

//三次貝塞爾曲線                  ctx1.fillStyle="pink";                  ctx1.beginPath();                  ctx1.moveTo(75, 40);                  ctx1.bezierCurveTo(75, 37, 70, 25, 50, 25);                  ctx1.bezierCurveTo(20, 25, 20, 62.5, 20, 62.5);                  ctx1.bezierCurveTo(20, 80, 40, 102, 75, 120);                  ctx1.bezierCurveTo(110, 102, 130, 80, 130, 62.5);                  ctx1.bezierCurveTo(130, 62.5, 130, 25, 100, 25);                  ctx1.bezierCurveTo(85, 25, 75, 37, 75, 40);                  ctx1.fill();

效果如下: