1. 程式人生 > >JavaScript的Canvas繪圖

JavaScript的Canvas繪圖

目錄

一、Canvas簡介

二、Canvas基本用法

三、填充和描邊

四、繪製矩形

五、繪製路徑

5.1、繪製線段

5.2、繪製三角形

5.3、繪製圓弧

5.4、繪製貝塞爾曲線

5.5、線條樣式

六、繪製文字

七、繪製圖像

八、模式

九、使用影象資料

十、陰影


一、Canvas簡介

    <canvas>元素是HTML5新增的,一個可以使用指令碼(通常為JavaScript)在其中繪製圖像的HTML元素。它可以用來製作照片集或者製作簡單的動畫,甚至可以進行實時視訊處理和渲染。

    <canvas>由幾組API構成,除了具備基本繪圖能力的2D上下文<canvas>還具備一個名為WebGL3D上下文

二、Canvas基本用法

    使用<canvas>元素之前,必須先設定其widthheight屬性,指定可以繪圖的區域大小。

    出現在開始和結束標籤中的內容是後備資訊,如果瀏覽器不支援<canvas>元素,就會顯示這些資訊。

<canvas id="drawing" width="200" height="200">A drawing of something.</canvas>

    要在這塊畫布上繪圖,需要使用getContext()方法傳入引數"2d"取得2D上下文物件

        var drawing = document.getElementById("drawing");

        //確定瀏覽器支援<canvas>元素
        if(drawing.getContext){
            var context = drawing.getContext("2d"); //取得2D上下文物件
            //更多程式碼
        }

    使用2D繪圖上下文提供的方法,可以繪製簡單的2D圖形,比如矩形、弧線和路徑。

    2D上下文的座標開始於<canvas>元素的左上角,原點座標是(0,0)。

    所有座標值都基於這個原點計算,x值越大表示越靠右,y值越大表示越靠下。

三、填充和描邊

    2D上下文的兩種基本繪圖操作是填充和描邊。

    填充就是用指定的樣式(顏色、漸變或影象)填充圖形,fillStyle屬性設定填充樣式。

    描邊就是隻在圖形的邊緣畫線,strokeStyle屬性設定描邊樣式。

    這兩個屬性的值可以是字串、漸變物件或模式物件,例如:

        var drawing = document.getElementById("drawing");

        //確定瀏覽器支援<canvas>元素
        if(drawing.getContext){
            
            var context = drawing.getContext("2d"); //取得2D上下文物件
            context.strokeStyle = "red";
            context.fillStyle = "#0000FF";
        }

四、繪製矩形

    與繪製矩形有關的方法包括:

  • fillRect()
  • strokeRect()
  • clearRect()

    這三個方法都能接收4個引數:矩形的x座標、矩形的y座標、矩形的寬度和矩形高度。引數單位都是畫素。

    示例1

        var drawing = document.getElementById("drawing");

        //確定瀏覽器支援<canvas>元素
        if(drawing.getContext){

            var context = drawing.getContext("2d"); //取得2D上下文物件

            //繪製紅色矩形
            context.fillStyle = "#F00";
            context.fillRect(10, 10, 50, 50);

            //繪製半透明的藍色矩形
            context.fillStyle = "rgba(0, 0, 255, 0.5)";
            context.fillRect(30, 30, 50, 50);
        }

    示例2

        var drawing = document.getElementById("drawing");

        //確定瀏覽器支援<canvas>元素
        if(drawing.getContext){

            var context = drawing.getContext("2d"); //取得2D上下文物件

            //繪製紅色描邊矩形
            context.strokeStyle = "#F00";
            context.strokeRect(10, 10, 50, 50);

            //繪製半透明的藍色描邊矩形
            context.strokeStyle = "rgba(0, 0, 255, 0.5)";
            context.strokeRect(30, 30, 50, 50);
        }

    示例3

        var drawing = document.getElementById("drawing");

        //確定瀏覽器支援<canvas>元素
        if(drawing.getContext){

            var context = drawing.getContext("2d"); //取得2D上下文物件

            //繪製紅色矩形
            context.fillStyle = "#F00";
            context.fillRect(10, 10, 50, 50);

            //繪製半透明的藍色描邊矩形
            context.fillStyle = "rgba(0, 0, 255, 0.5)";
            context.fillRect(30, 30, 50, 50);

            //在兩個矩形重疊的地方清除一個小矩形
            context.clearRect(40, 40, 10, 10);
        }

五、繪製路徑

    繪製路徑的方法:

  • beginPath()    ——    表示要開始繪製新路徑。
  • moveTo(x, y)    ——    將繪圖遊標移動到(x, y),不畫線。
  • lineTo(x, y)    ——    從上一點開始繪製一條直線,到(x, y)為止。
  • rect(x, y, width, height)    ——    從點(x,y)開始繪製一個矩形,寬度和高度分別由width和height指定。這個方法繪製的是矩形路徑,而不是strokeRect()和fillRect()所繪製的獨立的形狀。
  • arc(x, y, radius, startAngle, endAngle, counterclockwise)    ——   以(x,y)為圓心繪製一條弧線,弧線半徑為radius,起始和結束角度(用弧度表示)分別為startAngle和endAngle。最後一個引數表示startAngle和endAngle是否按逆時針方向計算,值為true表示按逆時針方向計算。
  • quadraticCurveTo(cx, cy, x, y)    ——    繪製二次貝塞爾曲線
  • bezierCurveTo(c1x, c1y, c2x, c2y, x, y)    ——    繪製三次貝塞爾曲線
  • closePath()    ——    繪製一條連線到路徑起點的線條。 
  • fill()    ——    使用fillStyle填充已經完成的路徑
  • stroke()    ——    使用strokeStyle為已經完成的路徑描邊

5.1、繪製線段

        var drawing = document.getElementById("drawing");

        //確定瀏覽器支援<canvas>元素
        if(drawing.getContext){

            var context = drawing.getContext("2d"); //取得2D上下文物件

            context.beginPath();        //開始繪製新路徑
            context.moveTo(50, 50);     //把畫筆移動到指定的座標
            context.lineTo(200, 50);    //繪製一條從當前位置到指定座標(200, 50)的直線
            context.stroke();           //繪製
        }

5.2、繪製三角形

    繪製三角形邊框:

        var drawing = document.getElementById("drawing");

        //確定瀏覽器支援<canvas>元素
        if(drawing.getContext){

            var context = drawing.getContext("2d"); //取得2D上下文物件

            context.beginPath();        //開始繪製新路徑
            context.moveTo(50, 50);     //把畫筆移動到指定的座標
            context.lineTo(200, 50);    //繪製一條從座標(50, 50)到座標(200, 50)的直線
            context.lineTo(200, 200);   //繪製一條從座標(200, 50)到座標(200, 200)的直線
            context.closePath();        //閉合路徑,繪製一條從路徑起點到路徑終點的直線
            context.stroke()            //為路徑描邊
        }

    填充三角形:

        var drawing = document.getElementById("drawing");

        //確定瀏覽器支援<canvas>元素
        if(drawing.getContext){

            var context = drawing.getContext("2d"); //取得2D上下文物件

            context.beginPath();        //開始繪製新路徑
            context.moveTo(50, 50);     //把畫筆移動到指定的座標
            context.lineTo(200, 50);    //繪製一條從座標(50, 50)到座標(200, 50)的直線
            context.lineTo(200, 200);   //繪製一條從座標(200, 50)到座標(200, 200)的直線
            context.closePath();        //閉合路徑,繪製一條從路徑起點到路徑終點的直線
            context.fill()              //填充三角形
        }

5.3、繪製圓弧

        var drawing = document.getElementById("drawing");

        //確定瀏覽器支援<canvas>元素
        if(drawing.getContext){

            var context = drawing.getContext("2d"); //取得2D上下文物件

            context.beginPath();        //開始繪製新路徑
            context.arc(100, 100, 99, 0, Math.PI, false);   
            context.stroke();
        }

5.4、繪製貝塞爾曲線

    二次貝塞爾曲線:

        var drawing = document.getElementById("drawing");

        //確定瀏覽器支援<canvas>元素
        if(drawing.getContext){

            var context = drawing.getContext("2d"); //取得2D上下文物件

            context.beginPath();
            context.moveTo(10, 200);    //起始點
            var x1 = 40, y1 = 100;      //控制點
            var x2 = 200, y2 = 200;     //結束點
            //繪製貝塞爾曲線
            context.quadraticCurveTo(x1, y1, x2, y2);
            context.stroke();

            context.beginPath();
            context.rect(10, 200, 10, 10);
            context.rect(x1, y1, 10, 10);
            context.rect(x2, y2, 10, 10);
            context.fill();

        }

    三次貝塞爾曲線:

        var drawing = document.getElementById("drawing");

        //確定瀏覽器支援<canvas>元素
        if(drawing.getContext){

            var context = drawing.getContext("2d"); //取得2D上下文物件

            context.beginPath();
            context.moveTo(40, 200);    //起始點
            var x1 = 20, y1 = 100;      //控制點1
            var x2 = 100, y2 = 120;     //控制點2
            var x3 = 200, y3 = 200;      //結束點
            //繪製三次貝塞爾曲線
            context.bezierCurveTo(x1, y1, x2, y2, x3, y3);
            context.stroke();

            context.beginPath();
            context.rect(40, 200, 10, 10);
            context.rect(x1, y1, 10, 10);
            context.rect(x2, y2, 10, 10);
            context.rect(x3, y3, 10, 10);
            context.fill();
        }

5.5、線條樣式

    lineWidth屬性用於設定線寬

        var drawing = document.getElementById("drawing");

        //確定瀏覽器支援<canvas>元素
        if(drawing.getContext){

            var context = drawing.getContext("2d"); //取得2D上下文物件

            context.beginPath();
            context.moveTo(10, 10);
            context.lineTo(100, 10);
            context.lineWidth = 10;
            context.stroke();

            context.beginPath();
            context.moveTo(110, 10);
            context.lineTo(160, 10);
            context.lineWidth = 20;
            context.stroke();
        }

    lineCap屬性用於設定線條末端樣式,可以設定為三個值:

  • butt    ——    線段末端為方形
  • round    ——    線段末端以圓形結束
  • square    ——    線段末端以方形結束,但是增加了一個寬度和線段相同,高度是線段厚度一半的矩形區域。
        var drawing = document.getElementById("drawing");

        //確定瀏覽器支援<canvas>元素
        if(drawing.getContext){

            var context = drawing.getContext("2d"); //取得2D上下文物件
            
            var lineCaps = ["butt", "round", "square"]
            for(var i = 0; i < 3; i++){
                context.beginPath();
                context.moveTo(20 + 30 * i, 30);
                context.lineTo(20 + 30 * i, 100);
                context.lineWidth = 20;
                context.lineCap = lineCaps[i];
                context.stroke();
            }

            context.beginPath();
            context.moveTo(0, 30);
            context.lineTo(300, 30);

            context.moveTo(0, 100);
            context.lineTo(300, 100);

            context.strokeStyle = "red";
            context.lineWidth = 1;
            context.stroke();
        }

    lineJoin屬性用於設定線條與線條間接合處的樣式,可以設定為三個值:

  • round    ——    通過填充一個額外的,圓心在相連部分末端的扇形,繪製拐角的形狀。圓角的半徑是線段的寬度。
  • bevel    ——    在相連部分的末端填充一個額外的以三角形為底的區域,每個部分都有各自獨立的矩形拐角。
  • miter(預設)    ——    通過延沈相連部分的外邊緣,使其相交於一點,形成一個額外的菱形區域。
        var drawing = document.getElementById("drawing");

        //確定瀏覽器支援<canvas>元素
        if(drawing.getContext){

            var context = drawing.getContext("2d"); //取得2D上下文物件
            
            var lineJoin = ['round', 'bevel', 'miter'];
            context.lineWidth = 20;

            for (var i = 0; i < lineJoin.length; i++){
                context.lineJoin = lineJoin[i];
                context.beginPath();
                context.moveTo(50, 50 + i * 50);
                context.lineTo(100, 100 + i * 50);
                context.lineTo(150, 50 + i * 50);
                context.lineTo(200, 100 + i * 50);
                context.lineTo(250, 50 + i * 50);
                context.stroke();
            }
        }

六、透明度

    globalAlpha屬性用於指定所有繪製的透明度,預設值為0。

    示例:

        var drawing = document.getElementById("drawing");

        //確定瀏覽器支援<canvas>元素
        if(drawing.getContext){

            var context = drawing.getContext("2d"); //取得2D上下文物件

            //繪製紅色矩形
            context.fillStyle = "#F00";
            context.fillRect(10, 10, 50, 50);

            //修改全域性透明度
            context.globalAlpha = 0.5;

            //繪製藍色矩形
            context.fillStyle = "rgba(0, 0, 255, 1)";
            context.fillRect(30, 30, 50, 50);

            //重置全域性透明度
            context.globalAlpha = 0;
  
        }

 

七、變換

7.1、translate()

    translate(x, y)用於將座標原點移動到(x, y),執行這個變換後,座標(0, 0)會變成之前由(x, y)表示的點。

    示例

        var drawing = document.getElementById("drawing");

        //確定瀏覽器支援<canvas>元素
        if(drawing.getContext){

            var context = drawing.getContext("2d"); //取得2D上下文物件

            //開始路徑
            context.beginPath();

            //繪製外圓
            context.arc(100, 100, 99, 0, 2 * Math.PI, false);

            //繪製內圓
            context.moveTo(194, 100);
            context.arc(100, 100, 94, 0, 2 * Math.PI, false);

            //變換原點
            context.translate(100, 100);

            //繪製分針
            context.moveTo(0, 0);
            context.lineTo(0, -85);

            //繪製時針
            context.moveTo(0, 0);
            context.lineTo(-65, 0);

            //描邊路徑
            context.stroke();
  
        }

7.2、rotate(angle)

    rotate(angle)方法能夠旋轉座標軸。

    示例

        var drawing = document.getElementById("drawing");

        //確定瀏覽器支援<canvas>元素
        if(drawing.getContext){

            var context = drawing.getContext("2d"); //取得2D上下文物件

            //開始路徑
            context.beginPath();

            //繪製外圓
            context.arc(100, 100, 99, 0, 2 * Math.PI, false);

            //繪製內圓
            context.moveTo(194, 100);
            context.arc(100, 100, 94, 0, 2 * Math.PI, false);

            //變換原點
            context.translate(100, 100);

            //旋轉錶針
            context.rotate(Math.PI / 6);

            //繪製分針
            context.moveTo(0, 0);
            context.lineTo(0, -85);

            //繪製時針
            context.moveTo(0, 0);
            context.lineTo(-65, 0);

            //描邊路徑
            context.stroke();
  
        }

7.3、scale(scaleX, scaleY)  

    scale(scaleX, scaleY)方法用於縮放影象,在x方向乘以scaleX,在y方向乘以scaleY

    scaleXscaleY的預設值都是1.0

    示例

        var drawing = document.getElementById("drawing");

        //確定瀏覽器支援<canvas>元素
        if(drawing.getContext){

            var context = drawing.getContext("2d"); //取得2D上下文物件

            //開始路徑
            context.beginPath();

            //繪製外圓
            context.arc(100, 100, 99, 0, 2 * Math.PI, false);

            //繪製內圓
            context.moveTo(194, 100);
            context.arc(100, 100, 94, 0, 2 * Math.PI, false);

            //變換原點
            context.translate(100, 100);

            //旋轉錶針
            context.rotate(Math.PI / 6);

            //繪製分針
            context.moveTo(0, 0);
            context.lineTo(0, -85);

            //繪製時針
            context.moveTo(0, 0);
            context.lineTo(-65, 0);

            //放大路徑
            context.scale(5, 5);

            //描邊路徑
            context.stroke();
  
        }

7.4、transform(a, b, c, d, e, f)

    直接修改變換矩陣,方式是乘以如下矩陣:

7.5、setTransform(a, b, c, d, e, f)

    將變換矩陣重置為預設狀態,然後再呼叫transform()

八、save()和restore()

    呼叫save()方法,當時所有的設定都會進入一個棧結構,得以妥善保管。

    然後可以對上下文進行其他修改,等想要回到之前儲存的設定時,可以呼叫restore()方法,在儲存設定的棧結構中向前返回一級,恢復之前的狀態。

    連續呼叫save()可以把更多設定儲存到棧結構中,之後再連續呼叫restore()則可以一級一級返回。

    示例:

        var drawing = document.getElementById("drawing");

        //確定瀏覽器支援<canvas>元素
        if(drawing.getContext){

            var context = drawing.getContext("2d"); //取得2D上下文物件

            context.fillStyle = "#F00";
            context.save();

            context.fillStyle = "#0F0";
            context.translate(100, 100);
            context.save();

            context.fillStyle = "#00F";
            //從點(100, 100)開始繪製藍色矩形
            context.fillRect(0, 0, 100, 200);   
            
            context.restore();
            //從點(110, 110)開始繪製綠色矩形
            context.fillRect(10, 10, 100, 200);

            context.restore();
            //從點(0, 0)開始繪製紅色矩形
            context.fillRect(0, 0, 100, 200);
        }

九、繪製文字

    繪製文字主要有兩個方法:

  • fillText()
  • strokeText()

    這兩個方法都可以接收4個引數:要繪製的文字字串、x座標、y座標和可選的最大畫素寬度。

    這兩個方法都以下列3個屬性為基礎:

  • font    ——    表示文字樣式、大小及字型,用CSS中指定字型的格式來指定,例如"10px Arial"
  • textAlign    ——    表示文字對齊方式。可能的值有"start"、"end"、"left"、"right"和"center"
  • textBaseline    ——    表示文字的基線,可能的值有"top"、"hanging"、"middle"、"alphabetic"、"ideographic"、"bottom"

    示例

        var drawing = document.getElementById("drawing");

        //確定瀏覽器支援<canvas>元素
        if(drawing.getContext){

            var context = drawing.getContext("2d"); //取得2D上下文物件
            
            //開始路徑
            context.beginPath();

            //繪製分針
            context.moveTo(100, 100);
            context.lineTo(100, 15);

            //描邊路徑
            context.stroke();

            context.font = "bold 14px Arial";
            context.textAlign = "center";   
            context.textBaseline = "middle";
            context.fillText("12", 100, 20);

            //起點對齊
            context.textAlign = "start";
            context.fillText("12", 100, 40);

            //終點對齊
            context.textAlign = "end";
            context.strokeText("12", 100, 60);

    fillText()strokeText()方法都可以接收第四個引數,也就是文字的最大畫素寬度。提供這個引數後,呼叫fillText()strokeText()時如果傳入的字串大於最大寬度,則繪製的文字字元的高度正確,但寬度會收縮以適應最大寬度。

十、繪製圖像

    2D繪圖上下文內建了對影象的支援,可以使用drawImage()方法。

    drawImage()有9個引數:要素繪製的影象、源影象的x座標、源影象的y座標、源影象的寬度、源影象的高度、目標影象的x座標、目標影象的y座標、目標影象的寬度、目標影象的高度。

    ①繪製圖像簡單示例

        var drawing = document.getElementById("drawing");

        var img = new Image();      //建立Image物件
        img.src = 'https://avatar.csdn.net/6/4/D/1_qq_35732147.jpg?1539065138'; //設定圖片源

        //確定瀏覽器支援<canvas>元素
        if(drawing.getContext){

            var context = drawing.getContext("2d"); //取得2D上下文物件

            img.addEventListener("load", function(){
                context.drawImage(img, 0, 0);       //影象載入完之後才會執行這條語句
            },false);
            
        }

    使用DOM0級的Image物件可以在客戶端預先載入影象,可以像使用<img>元素一樣使用Image物件,但無法將其新增到DOM樹中。

    考慮到圖片是從網路載入,所以應該保證影象載入完之後再呼叫drawImage()方法繪製圖像。

    ②縮放圖片簡單示例:

        var drawing = document.getElementById("drawing");

        var img = new Image();      //建立Image物件
        img.src = 'https://avatar.csdn.net/6/4/D/1_qq_35732147.jpg?1539065138'; //設定圖片源

        //確定瀏覽器支援<canvas>元素
        if(drawing.getContext){

            var context = drawing.getContext("2d"); //取得2D上下文物件

            img.addEventListener("load", function(){
                context.drawImage(img, 0, 0, 400, 400);       //影象載入完之後才會執行這條語句
            },false);
            
        }

    ③切片

    切片示例:

        var drawing = document.getElementById("drawing");

        //確定瀏覽器支援<canvas>元素
        if(drawing.getContext){

            var context = drawing.getContext("2d"); //取得2D上下文物件

            var img = new Image();
            img.src = "https://avatar.csdn.net/6/4/D/1_qq_35732147.jpg?1539071828";

            img.addEventListener("load", function(){
                context.drawImage(img, 50, 50, 200, 200, 0, 0, 200, 200);
            }, false);
  
        }

十一、模式

    模式其實就是重複的影象,可以用來填充或描邊圖形。

    要建立一個新模式,可以呼叫createPattern()方法並傳入兩個引數:

  • HTML<img>元素
  • 表示如何重複影象的字串,這個引數的值與CSS的background-repeat屬性值相同,包括"repeat"、"repeat-x"、"repeat-y"、"no-repeat"
        var drawing = document.getElementById("drawing");

        //確定瀏覽器支援<canvas>元素
        if(drawing.getContext){

            var context = drawing.getContext("2d"); //取得2D上下文物件

            var img = new Image();
            img.src = "https://avatar.csdn.net/E/9/8/3_weixin_42058532.jpg";

            img.addEventListener("load", function(){
                var pattern = context.createPattern(img, "repeat");

                //繪製矩形
                context.fillStyle = pattern;
                context.fillRect(10, 10, 200, 200);
            }, false);
  
        }

十二、使用影象資料

    canvas操作影象資料使用的幾個方法:

    ①getImageData()    ——    用於取得畫布上指定矩形的畫素資料。

    具體是這個方法會返回ImageData物件,ImageData物件中包含影象資料。

     getImageData()方法接收4個引數:

  • 要取得畫面區域左上角的x座標
  • 要取得畫面區域左上角的y座標
  • 要取得畫面區域的畫素寬度
  • 要取得畫面區域的畫素高度

    例如要取得左上角座標為(10,5)、大小為50*50畫素的區域的影象資料:

        var imageData = context.getImageData(10, 5, 50, 50);

    返回的物件是ImageData的例項,每個ImageData物件都有三個屬性:

  • width    ——    影象的寬度
  • height    ——    影象的高度
  • data    ——    儲存影象中每一個畫素的資料的陣列,每一個畫素用4個元素來儲存,分別表示紅、綠、藍和透明度值。

    ②putImageData()    ——    把影象資料(從指定的ImageData物件)放回畫布上。

    這個方法接收以下引數:

    

    下面的程式碼通過getImageData()取得畫布上指定矩形的畫素資料,然後通過putImageData()將影象資料放回畫布:

var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
ctx.fillStyle="green";
ctx.fillRect(10,10,50,50);

var imgData=ctx.getImageData(10,10,50,50);
ctx.putImageData(imgData,10,70);

    ③createImageData()    ——    建立新的、空白的ImageData物件

    這個方法接收的引數:

    以下程式碼建立100*100畫素的ImageData物件,其中每個畫素都是紅色的,然後把它放到畫布上:

var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
var imgData=ctx.createImageData(100,100);
for (var i=0;i<imgData.data.length;i+=4)
  {
  imgData.data[i+0]=255;
  imgData.data[i+1]=0;
  imgData.data[i+2]=0;
  imgData.data[i+3]=255;
  }
ctx.putImageData(imgData,10,10);

    效果:

    示例

    建立一個簡單的灰階過濾器:

        var drawing = document.getElementById("drawing");

        //確定瀏覽器支援<canvas>元素
        if(drawing.getContext){

            var context = drawing.getContext("2d"); //取得2D上下文物件

            var image = new Image();
            image.src = "liyangqiao.jpg";

            image.addEventListener("load", function(event){
                var red, green, blue, alpha, average;

                //繪製原始影象
                context.drawImage(image, 0, 0);

                //取得影象資料
                imageData = context.getImageData(0, 0, image.width, image.height);
                data = imageData.data;
                
                for(var i = 0, len = data.length; i < len; i += 4){
                    red = data[i];
                    green = data[i + 1];
                    blue = data[i + 2];
                    alpha = data[i + 3];
                    
                    //求得rgb平均值
                    average = Math.floor((red + green + blue) / 3);

                    //設定顏色值,透明度不變
                    data[i] = average;
                    data[i + 1] = average;
                    data[i + 2] = average;
                }

                //回寫影象資料並顯示結果
                imageData.data = data;
                context.putImageData(imageData, 0, 0);
            }, false);
  
        }

    注意只有在畫布“乾淨”的情況下(即影象並非來自其他域),才可以取得影象資料。如果畫布”不乾淨“,那麼訪問影象資料時會導致JavaScript錯誤。

十三、陰影

    2D上下文會根據以下幾個屬性的值,自動為形狀或路徑繪製出陰影。

  • shadowColor    ——    用CSS顏色格式表示的陰影顏色,預設為黑色。
  • shadowOffsetX    ——    形狀或路徑x軸方向的陰影偏移量,預設為0。
  • shadowOffsetY    ——    形狀或路徑y軸方向的陰影偏移量,預設為0。
  • shadowBlur    ——    模糊的畫素數,預設0,即不模糊。
        var drawing = document.getElementById("drawing");

        //確定瀏覽器支援<canvas>元素
        if(drawing.getContext){

            var context = drawing.getContext("2d"); //取得2D上下文物件

            //設定陰影
            context.shadowOffsetX = 5;
            context.shadowOffsetY = 5;
            context.shadowBlur = 5;
            context.shadowColor = "rgba(0, 0, 0, 0.5)";

            //繪製紅色矩形
            context.fillStyle = "#F00";
            context.fillRect(10, 10, 50, 50);

            //繪製藍色矩形
            context.fillStyle = "rgba(0, 0, 255, 1)";
            context.fillRect(30, 30, 50, 50);
            
        }

十四、漸變

    漸變由CanvasGradient例項表示,很容易通過2D上下文來建立和修改。

    要建立一個新的線性漸變物件,可以呼叫createLinearGradient()方法。

    這個方法接受4個引數:起點的x座標、起點的y座標、終點的x座標、終點的y座標。

    建立了漸變物件後,下一步就是使用addColorStop()方法來指定色標。

    這個方法接收兩個引數:色標位置和CSS顏色值。色標位置是一個0(開始的顏色)到1(結束的顏色)之間的數字。

    示例:

        var drawing = document.getElementById("drawing");

        //確定瀏覽器支援<canvas>元素
        if(drawing.getContext){

            var context = drawing.getContext("2d"); //取得2D上下文物件

            var gradient = context.createLinearGradient(30, 30, 70, 70);

            gradient.addColorStop(0, "white");
            gradient.addColorStop(1, "black");

            //繪製紅色矩形
            context.fillStyle = "#ff0000";
            context.fillRect(10, 10, 50, 50);

            //繪製漸變矩形
            context.fillStyle = gradient;
            context.fillRect(30, 30, 50, 50);
  
        }

    要建立徑向漸變(或放射性漸變),可以使用createRadialGradient()方法。

    這個方法接收6個引數,對應著兩個圓的圓心和半徑。前三個引數指定的是起點圓的圓心和半徑,後三個引數指定的是終點圓的圓心和半徑。

    示例

        var drawing = document.getElementById("drawing");

        //確定瀏覽器支援<canvas>元素
        if(drawing.getContext){

            var context = drawing.getContext("2d"); //取得2D上下文物件

            var gradient = context.createRadialGradient(55, 55, 10, 55, 55, 30);

            gradient.addColorStop(0, "white");
            gradient.addColorStop(1, "black");

            //繪製紅色矩形
            context.fillStyle = "#ff0000";
            context.fillRect(10, 10, 50, 50);

            //繪製漸變矩形
            context.fillStyle = gradient;
            context.fillRect(30, 30, 50, 50);
  
        }

十五、合成

    globalCompositionOperation屬性表示後繪製的圖形怎樣與先繪製的圖形結合。

    這個屬性的值是字串,可能的值如下:

  • source-over(預設值)    ——    後繪製的圖形位於先繪製的圖形上方
  • source-in    ——    後繪製的圖形與先繪製的圖形重疊的部分可見,兩者其他部分完全透明
  • source-out    ——    後繪製的圖形與先繪製的圖形不重疊的部分可見,先繪製的圖形完全透明
  • source-atop    ——    後繪製的圖形與先繪製的圖形重疊部分可見,先繪製圖形不受影響
  • destination-over    ——    後繪製的圖形位於先繪製的圖形下方,只有之前透明畫素下的部分才可見
  • destination-in    ——    後繪製的圖形位於先繪製的圖形下方,兩者不重疊的部分完全透明
  • destination-out    ——    後繪製的圖形擦除與先繪製的圖形重疊的部分
  • destination-atop    ——    後繪製的圖形位於先繪製的圖形下方,在兩者不重疊的地方,先繪製的圖形會變透明
  • lighter    ——    後繪製的圖形與先繪製的圖形重疊部分的值相加,使該部分變亮
  • copy    ——    後繪製的圖形完全替代與之重疊的先繪製圖形
  • xor    ——    後繪製的圖形與先繪製的圖形重疊的部分執行“異或”操作

    ①source-over(預設值)

        var drawing = document.getElementById("drawing");

        //確定瀏覽器支援<canvas>元素
        if(drawing.getContext){

            var context = drawing.getContext("2d"); //取得2D上下文物件

            context.globalCompositeOperation = "source-over";

            //繪製紅色矩形
            context.fillStyle = "#F00";
            context.fillRect(10, 10, 50, 50);

            //繪製藍色矩形
            context.fillStyle = "rgba(0, 0, 255, 1)";
            context.fillRect(30, 30, 50, 50);
        }

    ②source-in

        var drawing = document.getElementById("drawing");

        //確定瀏覽器支援<canvas>元素
        if(drawing.getContext){

            var context = drawing.getContext("2d"); //取得2D上下文物件

            //繪製紅色矩形
            context.fillStyle = "#F00";
            context.fillRect(10, 10, 50, 50);

            context.globalCompositeOperation = "source-in";

            //繪製藍色矩形
            context.fillStyle = "rgba(0, 0, 255, 1)";
            context.fillRect(30, 30, 50, 50);
        }

    ③source-out

        var drawing = document.getElementById("drawing");

        //確定瀏覽器支援<canvas>元素
        if(drawing.getContext){

            var context = drawing.getContext("2d"); //取得2D上下文物件

            //繪製紅色矩形
            context.fillStyle = "#F00";
            context.fillRect(10, 10, 50, 50);

            context.globalCompositeOperation = "source-out";

            //繪製藍色矩形
            context.fillStyle = "rgba(0, 0, 255, 1)";
            context.fillRect(30, 30, 50, 50);
        }

    ④source-atop

        var drawing = document.getElementById("drawing");

        //確定瀏覽器支援<canvas>元素
        if(drawing.getContext){

            var context = drawing.getContext("2d"); //取得2D上下文物件

            //繪製紅色矩形
            context.fillStyle = "#F00";
            context.fillRect(10, 10, 50, 50);

            context.globalCompositeOperation = "source-atop";

            //繪製藍色矩形
            context.fillStyle = "rgba(0, 0, 255, 1)";
            context.fillRect(30, 30, 50, 50);
        }

    ⑤destination-over

        var drawing = document.getElementById("drawing");

        //確定瀏覽器支援<canvas>元素
        if(drawing.getContext){

            var context = drawing.getContext("2d"); //取得2D上下文物件

            //繪製紅色矩形
            context.fillStyle = "#F00";
            context.fillRect(10, 10, 50, 50);

            context.globalCompositeOperation = "destination-over";

            //繪製藍色矩形
            context.fillStyle = "rgba(0, 0, 255, 1)";
            context.fillRect(30, 30, 50, 50);
        }

    ⑥destination-in

        var drawing = document.getElementById("drawing");

        //確定瀏覽器支援<canvas>元素
        if(drawing.getContext){

            var context = drawing.getContext("2d"); //取得2D上下文物件

            //繪製紅色矩形
            context.fillStyle = "#F00";
            context.fillRect(10, 10, 50, 50);

            context.globalCompositeOperation = "destination-in";

            //繪製藍色矩形
            context.fillStyle = "rgba(0, 0, 255, 1)";
            context.fillRect(30, 30, 50, 50);
        }

    ⑦destination-out

        var drawing = document.getElementById("drawing");

        //確定瀏覽器支援<canvas>元素
        if(drawing.getContext){

            var context = drawing.getContext("2d"); //取得2D上下文物件

            //繪製紅色矩形
            context.fillStyle = "#F00";
            context.fillRect(10, 10, 50, 50);

            context.globalCompositeOperation = "destination-out";

            //繪製藍色矩形
            context.fillStyle = "rgba(0, 0, 255, 1)";
            context.fillRect(30, 30, 50, 50);
        }

    ⑧destination-atop

        var drawing = document.getElementById("drawing");

        //確定瀏覽器支援<canvas>元素
        if(drawing.getContext){

            var context = drawing.getContext("2d"); //取得2D上下文物件

            //繪製紅色矩形
            context.fillStyle = "#F00";
            context.fillRect(10, 10, 50, 50);

            context.globalCompositeOperation = "destination-atop";

            //繪製藍色矩形
            context.fillStyle = "rgba(0, 0, 255, 1)";
            context.fillRect(30, 30, 50, 50);
        }

    ⑨lighter

        var drawing = document.getElementById("drawing");

        //確定瀏覽器支援<canvas>元素
        if(drawing.getContext){

            var context = drawing.getContext("2d"); //取得2D上下文物件

            //繪製紅色矩形
            context.fillStyle = "#F00";
            context.fillRect(10, 10, 50, 50);

            context.globalCompositeOperation = "lighter";

            //繪製藍色矩形
            context.fillStyle = "rgba(0, 0, 255, 1)";
            context.fillRect(30, 30, 50, 50);
        }

    ⑩copy

        var drawing = document.getElementById("drawing");

        //確定瀏覽器支援<canvas>元素
        if(drawing.getContext){

            var context = drawing.getContext("2d"); //取得2D上下文物件

            //繪製紅色矩形
            context.fillStyle = "#F00";
            context.fillRect(10, 10, 50, 50);

            context.globalCompositeOperation = "copy";

            //繪製藍色矩形
            context.fillStyle = "rgba(0, 0, 255, 1)";
            context.fillRect(30, 30, 50, 50);
        }

    ⑾xor

        var drawing = document.getElementById("drawing");

        //確定瀏覽器支援<canvas>元素
        if(drawing.getContext){

            var context = drawing.getContext("2d"); //取得2D上下文物件

            //繪製紅色矩形
            context.fillStyle = "#F00";
            context.fillRect(10, 10, 50, 50);

            context.globalCompositeOperation = "xor";

            //繪製藍色矩形
            context.fillStyle = "rgba(0, 0, 255, 1)";
            context.fillRect(30, 30, 50, 50);
        }

十六、基本動畫

16.1、動畫的基本步驟

    ①清空canvas

    除非要畫的內容會完全充滿canvas(例如背景圖),否則需要清空所有。

    最簡單的做法就是用clearRect()方法。

    ②儲存canvas狀態

    如果要改變一些會改變canvas狀態的設定(樣式,變形之類的),又要在每畫一幀之時都是原始狀態的話,需要先儲存一下。

    ③繪製動畫圖形

    這一步才是重繪動畫幀

    ④恢復canvas狀態

    如果已經儲存了canvas的狀態,可以先恢復它,然後重繪下一幀。

16.2、操縱動畫

    在canvas上繪製內容是用canvas提供的或者自定義的方法,而通常,我們僅僅在指令碼執行結束後才能看見結果,比如說,在for迴圈裡面做完成動畫是不太可能的,因為for迴圈一直高速執行,動畫都來不及繪製。

    因此,為了實現動畫,我們需要一些可以定時執行重繪的方法。

    首先,可以用window.setInterval()window.setTimeout(),和window.requestAnimationFrame()來設定定期執行一個指定函式:

  • setInterval(function, delay)    ——    當設定好間隔時間後,function會定期執行
  • setTimeout(function, delay)    ——    在設定好的時間之後執行函式
  • requestAnimationFrame(callback)    ——    告訴瀏覽器希望執行一個動畫,並在重繪之前,請求瀏覽器執行一個特定的函式來更新動畫

    如果並不需要與使用者互動,可以使用setInterval()方法,它就可以定期執行指定程式碼。

    如果需要做一個遊戲,我們可以使用鍵盤或者滑鼠事件配合上setTimeout()方法來實現。

    通過設定事件監聽,可以捕捉使用者的互動,並執行相應的動作。

16.3、太陽系的動畫

    下面的例子,採用window.requestAnimationFrame()實現動畫效果。

    這個方法提供了更加平緩並更加有效率的方式來執行動畫,當系統準備好了重繪條件的時候,才呼叫繪製動畫幀。

    一般每秒鐘回撥函式執行60次,也有可能會被降低。

    一個小型的太陽系模擬動畫:

        var sun = new Image();
        var moon = new Image();
        var earth = new Image();
        function init(){
            sun.src = 'https://mdn.mozillademos.org/files/1456/Canvas_sun.png';
            moon.src = 'https://mdn.mozillademos.org/files/1443/Canvas_moon.png';
            earth.src = 'https://mdn.mozillademos.org/files/1429/Canvas_earth.png';
            window.requestAnimationFrame(draw);
        }
        
        function draw(){
            var ctx = document.getElementById("drawing").getContext('2d');
            
            //後繪製的圖形位於先繪製的圖形下方,只有之前透明畫素下的部分才可見
            ctx.globalCompositeOperation = 'destination-over';
            ctx.clearRect(0, 0, 300, 300);    //clear canvas

            ctx.fillStyle = 'rgba(0, 0, 0, 0.4)';
            ctx.strokeStyle = 'rgba(0, 153, 255, 0.4)';
            ctx.save();
            //移動座標原點
            ctx.translate(150, 150);

            // Earth
            var time = new Date();
            ctx.rotate( ((2 * Math.PI) / 60) * time.getSeconds() + ((2 * Math.PI) / 60000) * time.getMilliseconds() );
            ctx.translate(105, 0);
            ctx.fillRect(0, -12, 50, 24);   //Shadow
            ctx.drawImage(earth, -12, -12);
            
            // Moon
            ctx.save();
            ctx.rotate( ((2 * Math.PI) / 60) * time.getSeconds() + ((2 * Math.PI) / 60000) * time.getMilliseconds() );
            ctx.translate(0, 28.5);
            ctx.drawImage(moon, -3.5, -3.5);
            ctx.restore();
            
            ctx.restore();

            ctx.beginPath();
            ctx.arc(150, 150, 105, 0, Math.PI * 2, false);  // Earth orbit
            ctx.stroke();

            ctx.drawImage(sun, 0, 0, 300, 300);

            window.requestAnimationFrame(draw);

        }

        init();