Canvas知識點補充

  • 2020 年 7 月 30 日
  • 筆記

Canvas筆記

複習

初識canvas


  • <canvas>HTML5 新增的,一個可以使用腳本(通常為 JavaScript) 在其中繪製影像的 HTML 元素。它可以用來製作照片集或者製作簡單(也不是那麼簡單)的動畫,甚至可以進行實時影片處理和渲染。
  • 示例<canvas id="canvas1" width="500" height="500"></canvas>
    • <canvas> 看起來和 <img> 標籤一樣,只是 <canvas> 只有兩個可選的屬性 width、heigth 屬性,而沒有 src、alt 屬性。
    • 如果不給 <canvas> 設置 widht、height 屬性時,則默認 width為300、height 為 150,單位都是 px。也可以使用 css 屬性來設置寬高,但是如寬高屬性和初始比例不一致,他會出現扭曲。所以,建議永遠不要使用 css 屬性來設置 <canvas> 的寬高。
  • 替換內容
    • 支援 <canvas> 的瀏覽器會只渲染 <canvas> 標籤,而忽略其中的替代內容。不支援 <canvas> 的瀏覽器則 會直接渲染替代內容。<canvas id="canvas1" width="500" height="500">您的破瀏覽器不支援canvas</canvas>
    • 替換的內容還可以使用img標籤

基本使用


  • <canvas> 會創建一個固定大小的畫布,會公開一個或多個渲染上下文(畫筆),使用渲染上下文來繪製和處理要展示的內容。獲取繪圖上下文2Dvar pen1 = canvas1.getContext("2d");

繪製形狀


柵格和坐標空間

  • canvas 元素默認被網格所覆蓋。通常來說網格中的一個單元相當於 canvas 元素中的一像素。柵格的起點為左上角,坐標為 (0,0) (X向右Y向下增大)。所有元素的位置都相對於原點來定位。

繪製矩形

  • <canvas> 只支援一種原生的圖形繪製:矩形。所有其他圖形都至少需要生成一種路徑 (path)。不過,我們擁有眾多路徑生成的方法讓複雜圖形的繪製成為了可能。

  • 三種繪製矩形的方法

    • 1、fillRect(x, y, width, height):繪製一個填充的矩形。
    • 2、strokeRect(x, y, width, height):繪製一個矩形的邊框。
    • 3、clearRect(x, y, widh, height):清除指定的矩形區域,然後這塊區域會變的完全透明。

    說明:這 3 個方法具有相同的參數。

    • x, y:指的是矩形的左上角的坐標。(相對於canvas的坐標原點)
    • width, height:指的是繪製的矩形的寬和高。

繪製路徑

  • 路徑是通過不同顏色和寬度的線段或曲線相連形成的不同形狀的點的集合。

  • 一個路徑,甚至一個子路徑,都是閉合的。

  • 繪製路徑的步驟

    • 創建路徑起始點
    • 調用繪製方法去繪製出路徑
    • 把路徑封閉
    • 一旦路徑生成,通過描邊或填充路徑區域來渲染圖形。
  • 繪製路徑對應的方法

    • beginPath()

      新建一條路徑,路徑一旦創建成功,圖形繪製命令被指向到路徑上生成路徑

    • moveTo(x, y)

      把畫筆移動到指定的坐標(x, y)。相當於設置路徑的起始點坐標。

    • lineTo(x, y)

      繪製一條從指定位置到當前位置的路徑

    • closePath()

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

    • stroke()

      通過線條來繪製圖形輪廓

    • fill()

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

  • 示例

        <script>
            var canvas1 = document.getElementById('canvas1');
    
            var pen1 = canvas1.getContext("2d");
    
            pen1.beginPath();
            pen1.moveTo(200,200);
            pen1.lineTo(300,200);
            pen1.lineTo(300,400);
            pen1.closePath(); // 閉合路徑
            pen1.stroke(); // 描邊
        </script>
    
  • pen1.fill();會填充閉合區域,若該區域沒有closePath則會自動閉合

繪製圓弧

  • arc(x, y, r, startAngle, endAngle, anticlockwise): 以(x, y) 為圓心,以r 為半徑,從 startAngle 弧度開始到endAngle弧度結束。anticlosewise 是布爾值,true 表示逆時針,false 表示順時針(默認是順時針)。
    • 這裡的度數都是弧度。
    • 0 弧度是指的 x 軸正方向。radians=(Math.PI/180)*degrees //角度轉換成弧度
  • arcTo(x1, y1, x2, y2, radius): 根據給定的控制點和半徑畫一段圓弧,最後再以直線連接兩個控制點。
    • arcTo 方法的說明:
      • 這個方法可以這樣理解。繪製的弧形是由兩條切線所決定。
      • 第 1 條切線:起始點和控制點1決定的直線。
      • 第 2 條切線:控制點1 和控制點2決定的直線。
      • 其實繪製的圓弧就是與這兩條直線相切的圓弧。

繪製貝塞爾曲線

  • 二次貝塞爾曲線 quadraticCurveTo(cp1x, cp1y, x, y)

    • 參數 1 和 2:控制點坐標

    • 參數 3 和 4:結束點坐標

    • 示例

          <script>
              var canvas1 = document.getElementById('canvas1');
      
              var pen1 = canvas1.getContext("2d");
      
              pen1.beginPath();
              pen1.moveTo(100,100); //起始點
              var cp1x = 150, cp1y = 200; // 控制點
              var x = 500, y = 300; // 結束點
              pen1.quadraticCurveTo(cp1x, cp1y, x, y);
              pen1.stroke();
          </script>
      
  • 三次貝塞爾曲線 bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)

    • 參數 1 和 2:控制點 1 的坐標

    • 參數 3 和 4:控制點 2 的坐標

    • 參數 5 和 6:結束點的坐標

    • 示例

          <script>
              var canvas1 = document.getElementById('canvas1');
      
              var pen1 = canvas1.getContext("2d");
      
              pen1.beginPath();
              pen1.moveTo(100,100); // 起始點
              var cp1x = 150, cp1y = 200; // 控制點1
              var cp2x = 250, cp2y = 220; // 控制點2
              var x = 400, y = 300; // 結束點
              pen1.quadraticCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
              pen1.stroke();
          </script>
      

添加樣式和顏色


  • 如果想要給圖形上色,有兩個重要的屬性可以做到。
    1. fillStyle = color 設置圖形的填充顏色
    2. strokeStyle = color 設置圖形輪廓的顏色
  • 備註:
    • color 可以是表示 css 顏色值的字元串、漸變對象或者圖案對象。(rgb或者rgba都可以)
    • 默認情況下,線條和填充顏色都是黑色。
    • 一旦您設置了 strokeStyle 或者 fillStyle 的值,那麼這個新值就會成為新繪製的圖形的默認值。如果你要給每個圖形上不同的顏色,你需要重新設置 fillStyle 或 strokeStyle 的值。

fillStyle

function draw(){
  var canvas = document.getElementById('tutorial');
  if (!canvas.getContext) return;
  var ctx = canvas.getContext("2d");
  for (var i = 0; i < 6; i++){
    for (var j = 0; j < 6; j++){
      ctx.fillStyle = 'rgb(' + Math.floor(255 - 42.5 * i) + ',' +
        Math.floor(255 - 42.5 * j) + ',0)';
      ctx.fillRect(j * 50, i * 50, 50, 50);
    }
  }
}
draw();

strokeStyle

function draw(){
    var canvas = document.getElementById('tutorial');
    if (!canvas.getContext) return;
    var ctx = canvas.getContext("2d");
    for (var i = 0; i < 6; i++){
        for (var j = 0; j < 6; j++){
            ctx.strokeStyle = `rgb(${randomInt(0, 255)},${randomInt(0, 255)},${randomInt(0, 255)})`;
            ctx.strokeRect(j * 50, i * 50, 40, 40);
        }
    }
}
draw();
/**
 返回隨機的 [from, to] 之間的整數(包括from,也包括to)
 */
function randomInt(from, to){
    return parseInt(Math.random() * (to - from + 1) + from);
}

transparency

  • globalAlpha = transparencyValue: 這個屬性影響到 canvas 里所有圖形的透明度,有效的值範圍是 0.0 (完全透明)到 1.0(完全不透明),默認是 1.0。
  • globalAlpha 屬性在需要繪製大量擁有相同透明度的圖形時候相當高效。不過,我認為使用rgba()設置透明度更加好一些。

lineStyle

  • 線寬。只能是正值。默認是 1.0。
  • 起始點和終點的連線為中心,上下各佔線寬的一半
  • pen1.lineWidth = 10;

lineCap

  • 線條末端樣式。
  • 共有 3 個值:
    1. butt:線段末端以方形結束
    2. round:線段末端以圓形結束
    3. square:線段末端以方形結束,但是增加了一個寬度和線段相同,高度是線段厚度一半的矩形區域
  • pen1.lineCap = 'round';

lineJoin

  • 同一個 path 內,設定線條與線條間接合處的樣式。

  • 共有 3 個值 round, bevelmiter

    1. round 通過填充一個額外的,圓心在相連部分末端的扇形,繪製拐角的形狀。 圓角的半徑是線段的寬度。
    2. bevel 在相連部分的末端填充一個額外的以三角形為底的區域, 每個部分都有各自獨立的矩形拐角。
    3. miter(默認) 通過延伸相連部分的外邊緣,使其相交於一點,形成一個額外的菱形區域。
  • pen1.lineJoin = 'round';

虛線

  • setLineDash 方法和 lineDashOffset 屬性來制定虛線樣式。 setLineDash 方法接受一個數組,來指定線段與間隙的交替;lineDashOffset屬性設置起始偏移量。

  • 示例

        <script>
            var canvas1 = document.getElementById('canvas1');
    
            var pen1 = canvas1.getContext("2d");
            pen1.strokeStyle = 'rgba(121,222,34,0.5)';
            pen1.lineWidth = 2;
            pen1.lineCap = 'round';
            pen1.lineJoin = 'round';
    
            pen1.beginPath();ji
            pen1.setLineDash([20,5]); // 數組內部的參數為實線長度,間隙長度
            pen1.lineDashOffset = -0;
            pen1.strokeRect(100,100,200,200);
        </script>
    
  • 備註: getLineDash() 返回一個包含當前虛線樣式,長度為非負偶數的數組。

繪製文本


  • canvas 提供了兩種方法來渲染文本:

    1. fillText(text, x, y [, maxWidth]) 在指定的 (x,y) 位置填充指定的文本,繪製的最大寬度是可選的。
    2. strokeText(text, x, y [, maxWidth]) 在指定的 (x,y) 位置繪製文本邊框,繪製的最大寬度是可選的。
  • 設置文本樣式

    • font = value 當前我們用來繪製文本的樣式。這個字元串使用和 CSS font 屬性相同的語法。 默認的字體是 10px sans-serif
    • textAlign = value 文本對齊選項。 可選的值包括:start, end, left, right or center。 默認值是 start
    • textBaseline = value 基準線對齊選項,可選的值包括:top, hanging, middle, alphabetic, ideographic, bottom。默認值是 alphabetic。
    • direction = value 文本方向。可能的值包括:ltr, rtl, inherit。默認值是 inherit

繪製圖片


  • 示例

        <script>
            var canvas1 = document.getElementById('canvas1');
            var img = new Image();
            img.src = '//www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png'
    
            var pen1 = canvas1.getContext("2d");
    
            img.onload = function()
            {
                pen1.drawImage(img, 50,50);
            }
    
        </script>
    
  • 注意:考慮到圖片是從網路載入,如果 drawImage 的時候圖片還沒有完全載入完成,則什麼都不做,個別瀏覽器會拋異常。所以我們應該保證在 img 繪製完成之後再 drawImage

  • img 可以 new 也可以來源於我們頁面的 <img>標籤。

  • 圖片的縮放

    • drawImage(image, x, y, width, height)
    • 這個方法多了 2 個參數:widthheight,這兩個參數用來控制 當像 canvas 畫入時應該縮放的大小。
  • 切片

  • drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)

  • 第一個參數和其它的是相同的,都是一個影像或者另一個 canvas 的引用。

  • 其他 8 個參數:

    • 前 4 個是定義影像源的切片位置和大小,後 4 個則是定義切片的目標顯示位置和大小。

狀態的保存和恢復


  • Saving and restoring state 是繪製複雜圖形時必不可少的操作。

  • save() 和 `restore()“

  • “saverestore方法是用來保存和恢復canvas` 狀態的,都沒有參數。

  • Canvas 的狀態就是當前畫面應用的所有樣式和變形的一個快照。

  • 關於 save() :Canvas狀態存儲在棧中,每當save()方法被調用後,當前的狀態就被推送到棧中保存。

  • 一個繪畫狀態包括:

    • 當前應用的變形(即移動,旋轉和縮放)
    • strokeStyle, fillStyle, globalAlpha, lineWidth, lineCap, lineJoin, miterLimit, shadowOffsetX, shadowOffsetY, shadowBlur, shadowColor, globalCompositeOperation 的值
    • 當前的裁切路徑(clipping path),可以調用任意多次 save方法(類似數組的 push())。
  • 2、關於restore():每一次調用 restore 方法,上一個保存的狀態就從棧中彈出,所有設定都恢復(類似數組的 pop())。

  • 示例

    var ctx;
    function draw(){
        var canvas = document.getElementById('tutorial');
        if (!canvas.getContext) return;
        var ctx = canvas.getContext("2d");
     
        ctx.fillRect(0, 0, 150, 150);   // 使用默認設置繪製一個矩形
        ctx.save();                  // 保存默認狀態
     
        ctx.fillStyle = 'red'       // 在原有配置基礎上對顏色做改變
        ctx.fillRect(15, 15, 120, 120); // 使用新的設置繪製一個矩形
     
        ctx.save();                  // 保存當前狀態
        ctx.fillStyle = '#FFF'       // 再次改變顏色配置
        ctx.fillRect(30, 30, 90, 90);   // 使用新的配置繪製一個矩形
     
        ctx.restore();               // 重新載入之前的顏色狀態
        ctx.fillRect(45, 45, 60, 60);   // 使用上一次的配置繪製一個矩形
     
        ctx.restore();               // 載入默認顏色配置
        ctx.fillRect(60, 60, 30, 30);   // 使用載入的配置繪製一個矩形
    }
    draw();
    

變形


tanslate

  • translate(x, y)用來移動 canvas原點到指定的位置(移動的是整個畫布)

  • translate 方法接受兩個參數。x 是左右偏移量,y 是上下偏移量

  • 在做變形之前先保存狀態是一個良好的習慣。大多數情況下,調用 restore 方法比手動恢復原先的狀態要簡單得多。又如果你是在一個循環中做位移但沒有保存和恢復 canvas 的狀態,很可能到最後會發現怎麼有些東西不見了,那是因為它很可能已經超出 canvas 範圍以外了。

  • 注意:translate 移動的是 canvas 的坐標原點(坐標變換)。

  • 示例

        <script>
            var canvas1 = document.getElementById('canvas1');
    
            var pen1 = canvas1.getContext('2d');
    
            pen1.save();
            pen1.translate(200,200);
            pen1.fillRect(0,0,100,100);
    
            pen1.restore();
            pen1.fillRect(0,0,100,100);
        </script>
    

rotate

  • 旋轉坐標軸。rotate(angle)

  • 這個方法只接受一個參數:旋轉的角度(angle),它是順時針方向的,以弧度為單位的值。旋轉的中心是坐標原點。

  • 示例

            var pen1 = canvas1.getContext('2d');
    
            pen1.save();
            pen1.rotate((Math.PI/180)*45);;
            pen1.fillRect(100,100,50,50);
    
            pen1.restore();
            pen1.fillRect(100,100,50,50);
    

scale

  • scale(x, y) 我們用它來增減圖形在 canvas 中的像素數目,對形狀,點陣圖進行縮小或者放大。
  • scale方法接受兩個參數。x,y 分別是橫軸和縱軸的縮放因子,它們都必須是正值。值比 1.0 小表示縮 小,比 1.0 大則表示放大,值為 1.0 時什麼效果都沒有。
  • 默認情況下,canvas 的 1 單位就是 1 個像素。舉例說,如果我們設置縮放因子是 0.5,1 個單位就變成對應 0.5 個像素,這樣繪製出來的形狀就會是原先的一半。同理,設置為 2.0 時,1 個單位就對應變成了 2 像素,繪製的結果就是圖形放大了 2 倍。

合成


  • 對合成的圖形來說,繪製順序會有限制。不過,我們可以利用 globalCompositeOperation 屬性來改變這種狀況。globalCompositeOperation = type
  • 對應的屬性值有:
    • 默認設置,新影像會覆蓋在原有影像。
    • source-in 僅僅會出現新影像與原來影像重疊的部分,其他區域都變成透明的。(包括其他的老影像區域也會透明)
    • source-out 僅僅顯示新影像與老影像沒有重疊的部分,其餘部分全部透明。(老影像也不顯示)
    • source-atop 新影像僅僅顯示與老影像重疊區域。老影像仍然可以顯示。
    • destination-over 新影像會在老影像的下面。
    • destination-in 僅僅新老影像重疊部分的老影像被顯示,其他區域全部透明。
    • destination-out 僅僅老影像與新影像沒有重疊的部分。 注意顯示的是老影像的部分區域。
    • destination-atop 老影像僅僅僅僅顯示重疊部分,新影像會顯示在老影像的下面。
    • lighter 新老影像都顯示,但是重疊區域的顏色做加處理。
    • darken 保留重疊部分最黑的像素。(每個顏色位進行比較,得到最小的)
    • lighten 保證重疊部分最量的像素。(每個顏色位進行比較,得到最大的)
    • xor 重疊部分會變成透明。
    • copy 只有新影像會被保留,其餘的全部被清除(邊透明)。

裁剪路徑


  • clip()

  • 把已經創建的路徑轉換成裁剪路徑。裁剪路徑的作用是遮罩。只顯示裁剪路徑內的區域,裁剪路徑外的區域會被隱藏。

  • 注意:clip() 只能遮罩在這個方法調用之後繪製的影像,如果是 clip() 方法調用之前繪製的影像,則無法實現遮罩。

  • 示例

            var canvas1 = document.getElementById('canvas1');
    
            var pen1 = canvas1.getContext('2d');
    
            pen1.beginPath();
            pen1.arc(100,100,100,0,Math.PI*2);
            pen1.clip();
    
            pen1.fillStyle = 'red';
            pen1.fillRect(100,100,200,200);
    

漸變


  • 線性漸變

    • 示例
            var canvas1 = document.getElementById('canvas1');
    
            var pen1 = canvas1.getContext('2d');
    
            var grd = pen1.createLinearGradient(0,0,100,100); // 參數:起始X,Y和結束X,Y
            grd.addColorStop(0,'blue'); // 設置漸變顏色
            grd.addColorStop(1,'green');
            pen1.fillStyle = grd;其實  	
            pen1.fillRect(0,0,100,100);
    
  • 徑向漸變

    • 示例
            var canvas1 = document.getElementById('canvas1');
    
            var pen1 = canvas1.getContext('2d');
    
            var grd = pen1.createRadialGradient(100,100,50,100,100,100); // 參數:前三個參數為第一個漸變圓圓心XYR,後三個參數為第二個圓XYR
            grd.addColorStop(0,'blue');
            grd.addColorStop(1,'green');
            pen1.fillStyle = grd;
            pen1.fillRect(0,0,200,200);
    

陰影


shadowColor 設置或返回用於陰影的顏色
shadowBlur 設置或返回用於陰影的模糊級別
shadowOffsetX 設置或返回陰影距形狀的水平距離
shadowOffsetY 設置或返回陰影距形狀的垂直距離
  • 示例
        var canvas1 = document.getElementById('canvas1');

        var pen1 = canvas1.getContext('2d');

        pen1.shadowBlur = 30; 
        pen1.shadowColor = 'red'; 
        pen1.fillStyle = 'blue'; 
        pen1.shadowOffsetX = 10;
        pen1.shadowOffsetY = 10;
        pen1.fillRect(100,100,100,100);

像素的操作


  • 示例1

        <script>
            var canvas1 = document.getElementById('canvas1');
    
            var pen1 = canvas1.getContext('2d');
    
            pen1.save();
            pen1.fillStyle = 'gold';
            pen1.fillRect(0,0,100,100);
    
            // 讀取指定矩形區域內的像素資訊
            var imgData1 = pen1.getImageData(0,0,100,100);
            //imgData1屬性:width height data,其中data數組中的每四個元素表示一個像素內的顏色資訊,分別是rgba
    
            // 遍歷修改每個像素的顏色值
            for(let i=0; i<imgData1.data.length; i+=4)
            {
                imgData1.data[i] += 10; 
                imgData1.data[i+1] += 20; 
                imgData1.data[i+2] += 30; 
                imgData1.data[i+4] += 40; 
            }
    
            // 將修改完成的像素資訊寫入到指定位置
            pen1.putImageData(imgData1,200,200);
        </script>
    
  • 示例2

        <script>
            var canvas1 = document.getElementById('canvas1');
    
            var pen1 = canvas1.getContext('2d');
    
            // 設置像素點尺寸
            var imgData2 = pen1.createImageData(100,100);;
            // 設置像素點顏色資訊
            for(let i=0;i<imgData2.data.length;i+=4)
            {
                imgData2.data[i+0]=255;
                imgData2.data[i+1]=155;
                imgData2.data[i+2]=222;
                imgData2.data[i+3]=100;
            }
    
            pen1.putImageData(imgData2,0,0);
        </script>