《JavaScript高級程序設計》筆記:使用Canvas繪圖(15)

基本用法

要使用<canvas>元素,必須先設置其width和height屬性,指定能夠繪圖的區域大小。出如今開始和結束標籤中的內容是後備信息,若是瀏覽器不支持<canvas>元素,就會顯示這些信息。以下例子:web

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

與其它元素同樣,<canvas>元素對應的DOM元素對象也有width和height屬性,能夠隨意修改。並且,也能夠經過CSS爲該元素添加樣式,若是不添加任何樣式或者不繪製任何圖形,在頁面中是看不到該元素的。canvas

要在這塊畫布(canvas)上繪圖,須要取得繪圖上下文。以下代碼:數組

var drawing = document.getElementById('drawing');
//肯定瀏覽器支持<canvas>元素
if(drawing.getContext){
    var context = drawing.getContext("2d");
    //更多代碼
}

使用toDataURL()方法,能夠導出在<canvas>元素上繪製的圖像。這個方法接收一個參數,即圖像的MIME類型格式,並且適合用於建立圖像的任何上下文。好比,要取得畫布中的一副PNG格式的圖像,以下代碼:瀏覽器

var drawing = document.getElementById('drawing');
//肯定瀏覽器支持<canvas>元素
if(drawing.getContext){
    //取得圖像數據的URI 
    var imgURI = drawing.toDataURL("image/png");
    
    //顯示圖像
    var image = document.createElement("img");
    image.src = imgURI;
    document.body.appendChild(image);    
}

2D上下文

使用2D繪圖上下文提供的方法,能夠繪製簡單的2D圖形,好比矩形、弧線和路徑。2D上下文的座標開始於<canvas>元素的左上角,原點座標是(0,0)。全部座標值都基於這個原點計算。x值越大越靠右,y值越大越靠下。app

填充和描邊

2D上下文的兩種基本繪圖操做是填充和描邊。填充,就是用指定的樣式(顏色、漸變或圖像)填充圖形;描邊,就是隻在圖形的邊緣畫線。大多數2D上下文操做都會細分爲填充和描邊兩個操做,而操做的結果取決於兩個屬性:fillStylestrokeStyleide

這兩個屬性的值能夠是字符串、漸變對象或模式對象。並且他們的默認值都是「#000000」。若是爲他們指定表示顏色的字符串值,可使用CSS中指定顏色值的任何格式,包括顏色名、十六進制碼、rgb、rgba、hsl或hsla,以下例子:函數

var drawing = document.getElementById('drawing');
//肯定瀏覽器支持<canvas>元素
if(drawing.getContext){
    var context = drawing.getContext("2d");
    context.strokeStyle = "red";
    context.fillStyle = "#0000ff";
}

繪製矩形

矩形是惟一一種能夠直接在2D上下文中繪製的形狀。與矩形有關的方法包括fillRect()strokeRect()clearRect()。這三個方法都能接收4個參數:矩形的x座標、矩形的y座標、矩形的寬度和高度。這些參數的單位都是像素。性能

首先fillRect()方法在畫布上繪製的矩形會填充指定的顏色。填充的顏色經過fillStyle屬性指定,好比:字體

var drawing = document.getElementById('drawing');
//肯定瀏覽器支持<canvas>元素
if(drawing.getContext){
    var context = drawing.getContext("2d");
    
    //繪製紅色矩形
    context.fillStyle = "#ff0000";
    context.fillRect(10,10,50,50);
    
    //繪製半透明的藍色矩形
    context.fillStyle = "rgba(0,0,255,0.5)";
    context.fillRect(30,30,50,50);
}

strokeRect()方法在畫布上繪製的矩形會使用指定的顏色描邊。描邊顏色經過strokeStyle屬性指定。好比:webgl

var drawing = document.getElementById('drawing');
//肯定瀏覽器支持<canvas>元素
if(drawing.getContext){
    var context = drawing.getContext("2d");
    
    //繪製紅色描邊矩形
    context.strokeStyle = "#ff0000";
    context.strokeRect(10,10,50,50);
    
    //繪製半透明的藍色描邊矩形
    context.strokeStyle = "rgba(0,0,255,0.5)";
    context.strokeRect(30,30,50,50);
}

以上代碼繪製了兩個重疊的矩形。不過,這兩個矩形都只有框線,內部並無填充顏色。

最後,clearRect()方法用於清除畫布上的矩形區域。本質上,這個方法能夠把繪製上下文中的某一矩形區域變透明。經過繪製形狀而後再清除指定區域,就能夠生成有意思的效果,例如把某個形狀切掉一塊。以下例子:

var drawing = document.getElementById('drawing');
//肯定瀏覽器支持<canvas>元素
if(drawing.getContext){
    var context = drawing.getContext("2d");
    
    //繪製紅色矩形
    context.fillStyle = "#ff0000";
    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);
}

繪製路徑

2D繪製上下文支持不少在畫布上繪製路徑的方法。經過路徑能夠創造出複雜的形狀和線條。要繪製路徑,首先必須調用beginPath()方法,表示要開始繪製新路徑。而後,在經過下列方法來實際的繪製路徑。

  • arc(x,y,radius,startAngle,endAngle,counterclockwise):以(x,y)爲圓心繪製一條弧線,弧線半徑爲radius,起始和結束角度(用弧度表示)分別爲startAngle和endAngle。最後一個參數表示startAngle和endAngle是否按逆時針方向計算,值爲false表示按照順時針方法計算。
  • arcTo(x1,y1,x2,y2,radius):從上一點開始繪製一條弧線,到(x2,y2)爲止,而且以給定的半徑radius穿過(x1,y1)。
  • bezierCurveTo(c1x,c1y,c2x,c2y,x,y):從上一點開始繪製一條曲線,到(x,y)爲止,而且以(c1x,c1y)和(c2x,c2y)爲控制點。
  • lineTo(x,y):從上一點開始繪製一條直線,到(x,y)爲止。
  • moveTo(x,y):將繪圖遊標移動到(x,y),不畫線。
  • quadraticCurveTo(cx,cy,x,y):從上一點開始繪製一條二次曲線,到(x,y)爲止,而且以(cx,cy)爲控制點。
  • rect(x,y,width,height):以點(x,y)開始繪製一個矩形,寬度和高度分別爲width和height指定。這個方法繪製的是矩形路徑,而不是strokeRect()和fillRect()所繪製的獨立的形狀。

建立了路徑後,接下來有幾種可能的選擇。若是想繪製一條想鏈接到路徑起點的線條,能夠調用closePath()。若是路徑已經完成,你想用fillStyle填充它,能夠調用fill()方法。另外,還能夠調用stroke()方法對路徑描邊,描邊使用的是strokeStyle。最後還能夠調用clip(),這個方法能夠在路徑上建立一個剪切區域。

以下例子,繪製一個不帶數字的時鐘錶盤。

var drawing = document.getElementById('drawing');
//肯定瀏覽器支持<canvas>元素
if(drawing.getContext){
    var context = drawing.getContext("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.moveTo(100,100);
    context.lineTo(100,15);
    
    //繪製時針
    context.moveTo(100,100);
    context.lineTo(35,100);
    
    //繪製描邊
    context.strokeStyle = "#ff0000";
    context.stroke();
}

因爲路徑的使用很頻繁,因此就有了一個名爲isPointInPath()的方法。這個方法接收x和y座標做爲參數,用於在路徑被關閉以前肯定畫布上的某一點是否位於路徑上,例如:

if(context.isPointInPath(100,100)){
        console.log("Point(100,100) is in the path")
    }

繪製文本

繪製文本主要有兩個方法:fillText()strokeText()。這兩個方法均可以接收4個參數:要繪製的文本字符串、x座標、y座標和可選的最大像素寬度。並且,這兩個方法都如下列3個屬性爲基礎。

  • font:表示文本樣式、大小及字體,用CSS指定字體的格式來指定,如「10px Arial」。
  • textAlign:表示文本對齊方式。可能的值有「start」、「end」、「left」、「right」和「center」。建議使用「start」和「end」,不要使用「left」和「right」,由於前二者的意思更穩妥,能同時適合從左到右和從右到左顯示(閱讀)的語言。
  • textBaseline:表示文本的基線。可能的值有「top」、「hanging」、「middle」、「alphabetic」、「ideographic」和「bottom」。

這幾個屬性都有默認值,所以不必每次使用它們都從新設置一遍值。fillText()方法使用fillStyle屬性繪製文本,而strokeText()方法使用strokeStyle屬性爲文本描邊。相對來講,仍是使用fillText()的時候更多,由於該方法模仿了在網頁中正常顯示文本。例以下面代碼在前面建立的錶盤上方繪製了數字12:

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

上面座標(100,20)表示的是文本水平和垂直中點的座標。

//正常    
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.fillText("12",100,60);

因爲繪製文本比較複雜,特別是須要把文本控制在某一區域中的時候,2D上下文提供了輔助肯定文本大小的方法measureText()。這個方法接收一個參數,即要繪製的文本;返回一個TextMetrics對象。返回的對象目前只有一個width屬性,但未來還會增長更多度量屬性。

measureText()方法利用font、textAlign和textBaseline的當前值計算指定文本的大小。好比,假設你想在一個140像素寬的矩形區域中繪製文本 Hello world!,下面的代碼從100像素的字體大小開始遞減,最終會找到合適的字體大小。

var fontSize = 100;
context.font = fontSize + "px Arial";
while(context.measureText("Hello world!").width > 140){
    fontSize--;
    context.font = fontSize + "px Arial";
}
context.fillText("Hello world!",10,100);
context.fillText("Font size is" + fontSize + "px", 10,150);

變換

經過上下文的變換,能夠把處理後的圖像繪製到畫布上。2D繪製上下文支持各類基本的繪製變換。建立繪製上下文時,會以默認值初始化變換矩陣,在默認的變換矩陣下,全部處理都按描述直接繪製。爲繪製上下文應用變換,會致使不一樣的變換矩陣應用處理,從而產生不一樣的結果。

能夠經過以下方法來修改變換矩陣。

  • rotate(angle):圍繞原點旋轉圖像angle弧度。
  • scale(scaleX,scaleY):縮放圖像,在x方向乘以scaleX,在y方向乘以scaleY。scaleX和scaleY的默認值都是1.0。
  • translate(x,y):將座標原點移動到(x,y)。執行這個變換後,座標(0,0)會變成以前由(x,y)表示的點。
  • transform(m1_1,m1_2,m2_1,m2_2,dx,dy):直接修改變換矩陣,方式是乘以以下矩陣。

m1_1    m1_2   dx

m2_1    m2_2   dy

0           0         1

  • setTransform(m1_1,m1_2,m2_1,m2_2,dx,dy):將變換矩陣重置爲默認狀態,而後再調用transform()。

變換有可能很簡單,也有可能很複雜,這都要視狀況而定。好比,就那前面的繪製錶針來講,若是把原點變換到變盤的中心,而後在繪製錶針就容易多了,以下例子:

var drawing = document.getElementById('drawing');
//肯定瀏覽器支持<canvas>元素
if(drawing.getContext){
    var context = drawing.getContext("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.strokeStyle = "#ff0000";
    context.stroke();
}

把原點變換到時鐘錶盤的中心點(100,100)後,在同一方向上繪製線條就變成了簡單的數學問題了。全部數學計算都基於(0,0),而不是(100,100)。還能夠更進一步,像下面這樣使用rotate()方法旋轉時針的錶針。

var drawing = document.getElementById('drawing');
//肯定瀏覽器支持<canvas>元素
if(drawing.getContext){
    var context = drawing.getContext("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(1);
    
    //繪製分針
    context.moveTo(0,0);
    context.lineTo(0,-85);
    
    //繪製時針
    context.moveTo(0,0);
    context.lineTo(-65,0);
    
    //繪製描邊
    context.strokeStyle = "#ff0000";
    context.stroke();
}

雖然沒有什麼辦法把上下文中的一切都重置回默認值,但有兩個方法能夠跟蹤上下文的狀態變化。若是你知道未來還要返回某組屬性與變換的組合,能夠調用save()方法。調用這個方法後,當時的全部設置都會進入一個棧結構,得以妥善保管。而後能夠對上下文進行其它修改。等想要回到以前保存的設置時,能夠調用restore()方法,在保存設置的棧結構中向前返回一級,恢復以前的狀態。連續調用save()能夠把更多設置保存到棧結構中,以後在連續調用restore()則能夠一級一級返回,以下例子:

var drawing = document.getElementById('drawing');
//肯定瀏覽器支持<canvas>元素
if(drawing.getContext){
    var context = drawing.getContext("2d");
    
    context.fillStyle = "#ff0000";
    context.save();
    
    context.fillStyle = "#00ff00";
    context.translate(100,100);
    context.save();
    
    context.fillStyle = "#0000ff";
    context.fillRect(0,0,100,200); //從點(100,100)開始繪製藍色矩形
    
    context.restore();
    context.fillRect(10,10,100,200); //從點(110,110)開始繪製綠色矩形
    
    context.restore();
    context.fillRect(0,0,100,200); //從點(0,0)開始繪製紅色矩形
}

須要注意的是,save()方法保存的只是對繪圖上下文的設置和變換。不會保存繪圖上下文的內容。

繪製圖像

使用drawImage()方法能夠把圖像繪製到畫布上。調用這個方法時,可使用三種不一樣的參數組合。最簡單的調用方式是傳入一個HTML<img>元素,以及繪製該圖像的起點的x和y座標,以下:

window.onload = function(){
    var drawing = document.getElementById('drawing');
    //肯定瀏覽器支持<canvas>元素
    if(drawing.getContext){
        var context = drawing.getContext("2d");
        var img = document.images[0];
        context.fillStyle = "#000000";
        context.fillRect(0,0,200,200);
        context.drawImage(img,10,10);
    }
}

若是你想改變繪製後的圖像大小,能夠多傳兩個參數,分別表示目標寬度和目標高度。以下:

window.onload = function(){
    var drawing = document.getElementById('drawing');
    //肯定瀏覽器支持<canvas>元素
    if(drawing.getContext){
        var context = drawing.getContext("2d");
        var img = document.images[0];
        context.fillStyle = "#000000";
        context.fillRect(0,0,200,200);
        context.drawImage(img,10,10,100,200);
    }
}

執行後圖像的大小變成了100*200。

除了上述兩種方式,還能夠選擇把圖像中的某個區域繪製到上下文中。drawImage()方法的這種調用方式總共須要傳入9個參數:要繪製的圖像、源圖像的x座標、源圖像的y座標、源圖像的寬度、源圖像的高度、目標圖像的x座標、目標圖像的y座標、目標圖像的寬度、目標圖像的高度。以下:

context.drawImage(img,0,10,50,50,0,100,40,60);

這行代碼只把原始圖像的一部分繪製到畫布上。原始圖像的這一部分起點爲(0,10),寬和高都是50像素。最終繪製到上下文中的圖像的起點爲(0,100),而大小變成了40*60像素。

陰影

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

  • shadowColor:用CSS顏色格式表示的陰影顏色,默認爲黑色。
  • shadowOffsetX:形狀或路徑x軸方向的陰影偏移量,默認爲0。
  • shadowOffsetY:形狀或路徑y軸方向的陰影偏移量,默認爲0。
  • shadowBlur:模糊的像素數,默認0,即不模糊。

這些屬性均可以經過context對象來修改。

var drawing = document.getElementById('drawing');
//肯定瀏覽器支持<canvas>元素
if(drawing.getContext){
    var context = drawing.getContext("2d");
    
    //設置陰影
    context.shadowOffsetX = 5;
    context.shadowOffsetY = 5;
    context.shadowBlur = 4;
    context.shadowColor = "rgba(0,0,0,0.5)";
    
    //繪製紅色矩形
    context.fillStyle = "#ff0000";
    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座標。調用這個方法後,它就會建立一個指定大小的漸變,並返回CanvasGradient對象的實例。

建立了漸變對象後,下一步就是使用addColorStop()方法來指定色標。這個方法接收2個參數:色標位置和CSS顏色值。色標位置是一個0(開始的顏色)到1(結束的顏色)之間的數字。以下:

var drawing = document.getElementById('drawing');
//肯定瀏覽器支持<canvas>元素
if(drawing.getContext){
    var context = drawing.getContext("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);
}

確保漸變也形狀對齊,以下代碼:

function createRectLinearGradient(context,x,y,width,height){
    return context.createLinearGradient(x,y,x+width,y+height);
}

這個函數基於起點的x和y座標以及寬度和高度值來建立漸變對象,從而讓咱們能夠在fillRect()中使用相同的值,以下:

function createRectLinearGradient(context,x,y,width,height){
    return context.createLinearGradient(x,y,x+width,y+height);
}

var drawing = document.getElementById('drawing');
//肯定瀏覽器支持<canvas>元素
if(drawing.getContext){
    var context = drawing.getContext("2d");
    
    var gradient = createRectLinearGradient(context,30,30,50,50);
    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個參數,對應這兩個圓的圓心和半徑。前三個參數指定的是起點圓的原心(x和y)以及半徑,後三個參數指定的是終點圓的原心(x和y)以及半徑。

若是想從某個形狀的中心點開始建立一個向外擴散的徑向漸變效果,就要將兩個圓定義爲同心圓。好比,拿前面建立的矩形來講,徑向漸變的兩個圓的圓心都應該在(55,55),由於矩形的區域是從(30,30)到(80,80),看下面代碼:

var drawing = document.getElementById('drawing');
//肯定瀏覽器支持<canvas>元素
if(drawing.getContext){
    var context = drawing.getContext("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);
}

效果以下所示:

模式

模式其實就是重複的圖像,能夠用來填充或描邊圖形。要建立一新模式,能夠調用createPattern()方法並傳入兩個參數:一個THML<img>元素和一個 表示如何重複圖像的字符串。其中,第二個參數的值與CSS的background-repeat的屬性值相同,包括「repeat」、「repeat-x」、「repeat-y」和「no-repeat」。以下例子:

window.onload = function(){
    var drawing = document.getElementById('drawing');
    //肯定瀏覽器支持<canvas>元素
    if(drawing.getContext){
        var context = drawing.getContext("2d");
        
        var image = document.images[0],
            pattern = context.createPattern(image,"repeat");
        
        //繪製矩形
        context.fillStyle =pattern;
        context.fillRect(10,10,150,150);
    }
}

效果如圖:

createPattern()方法的第一個參數也能夠是一個<video>元素,或者另外一個<canvas>元素。

使用圖像數據

 2D上下文的一個明顯長處就是,能夠經過getImageData()取得原生圖像數據。這個方法接收4個參數:要取得其數據的畫面區域的x和y座標以及該區域的像素寬度和高度。例如要取得左上角座標爲(10,5)、大小爲50*50像素區域的圖像數據,以下代碼:

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

這裏返回的對象是imageData的實例。每一個imageData對象都有三個屬性:width、height和data。其中data屬性是一個數組,保存着圖像中每個像素的數據。在data數組中,每個像素用4個元素來保存,分別表示紅、綠、藍和透明度值。所以,第一個像素的數據就保存在數組的第0到第3個元素中,例如:

var data = imageData.data,
        red = data[0],
        green = data[1],
        blue = data[2],
        alpha = data[3];

數組中每一個元素的值都介於0到255之間(包括0和255)。可以直接訪問到原始圖像數據,就可以以各類方式來操做這些數據。例如,經過修改圖像數據,能夠像下面這樣建立一個簡單的灰階過濾器。

window.onload = function(){
    var drawing = document.getElementById('drawing');
    //肯定瀏覽器支持<canvas>元素
    if(drawing.getContext){
        var context = drawing.getContext("2d"),
            image = document.images[0],
            imageData,data,
            i,len,average,
            red,green,blue,alpha;
            
        //繪製原始圖像
        context.drawImage(image,0,0);
        
        //取得圖像數據
        imageData = context.getImageData(0,0,image.width,image.height);
        data = imageData.data;
        
        for( 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);
    }        
}

 如上代碼,在把data數組回寫到imageData對象後,調用putImageData()方法把圖像數據繪製到畫布上。最終獲得了圖像的黑白版。

效果以下:

合成

 還有兩個會應用到2D上下文中全部繪製操做的屬性:globalAlpha和globalCompositionOperation。其中globalAlpha是一個介於0到1之間的值(包括0和1),用於指定全部繪製的透明度。默認值爲0。若是全部後續操做都要基於相同的透明度,就能夠先把globalAlpha設置爲適當的值,而後繪製,最後再把它設置爲默認值0。以下例子:

var drawing = document.getElementById('drawing');
//肯定瀏覽器支持<canvas>元素
if(drawing.getContext){
    var context = drawing.getContext("2d");
    
    //繪製紅色矩形
    context.fillStyle = "#ff0000";
    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;    
}

在上面例子中,咱們把藍色矩形繪製到了紅色矩形上面,由於在繪製藍色矩形前,globalAlpha設置爲了0.5,因此藍色矩形會呈現半透明效果,透過它能夠看到下面的紅色矩形。

第二個屬性globalCompositionOperation表示後繪製的圖形怎樣與先繪製的圖形結合。這個屬性的值是字符串,可能的值以下:

  • source-over(默認值):後繪製的圖形位於先繪製的圖形上方。
  • source-in:後繪製的圖形與先繪製的圖形重疊的部分可見,二者其它部分徹底透明。
  • source-out:後繪製的圖形與先繪製的圖形不重疊的部分可見,先繪製的圖形徹底透明。
  • source-atop:後繪製的圖形與先繪製的圖形重疊的部分可見,先繪製圖形不受影響。
  • destination-over:後繪製的圖形位於先繪製的圖形下方,只有以前透明像素下的部分纔可見。
  • destination-in:後繪製的圖形位於先繪製的圖形下方,二者不重疊的部分徹底透明。
  • destination-out:後繪製的圖形擦除與先繪製的圖形重疊的部分。
  • destination-atop:後繪製的圖形位於先繪製的圖形下方,在二者不重疊的地方,先繪製的圖形會變透明。
  • lighter:後繪製的圖形與先繪製的圖形重疊部分的值相加,使該部分變亮。
  • copy:後繪製的圖形徹底替代與之重疊的先繪製圖形。
  • xor:後繪製的圖形與先繪製的圖形重疊的部分執行「異或」操做。
var drawing = document.getElementById('drawing');
//肯定瀏覽器支持<canvas>元素
if(drawing.getContext){
    var context = drawing.getContext("2d");
    
    //繪製紅色矩形
    context.fillStyle = "#ff0000";
    context.fillRect(10,10,50,50);
    
    //設置合成操做
    context.globalCompositeOperation = "xor";
    
    //繪製藍色矩形
    context.fillStyle = "rgba(0,0,255,1)";
    context.fillRect(30,30,50,50);
}

webGL

webGL是針對Canvas的3D上下文。與其它web技術不一樣,webGL並非W3C指定的標準。而是由Khronos Group制定的。Khronos Group也設計了其餘圖像處理API,好比openGL ES2.0。瀏覽器使用的webGL就是基於openGL ES2.0制定的。

類型化數組

webGL涉及的複雜計算須要提早知道數值的精度,而標準的JavaScript數值沒法知足須要。爲此,webGL引入了一個概念,叫類型化數組(typed arrays)。類型化數組也是數組,只不過其元素被設置爲特定類型的值。

類型化數組的核心就是一個名爲ArrayBuffer的類型。每一個ArrayBuffer對象表示的只是內存中指定的字節數,但不會指定這些字節用於保存什麼類型的數據。經過ArrayBuffer所能作的,就是爲了未來使用而分配必定數量的字節。例如,下面這行代碼會在內存中分配20B。

var buffer = new ArrayBuffer(20);

建立了ArrayBuffer對象後,可以經過該對象得到的信息只有它包含的字節數,方法是訪問其byteLength屬性:

var bytes = buffer.byteLength;

雖然ArrayBuffer對象自己沒有什麼可說的,但對webGL而言,使用它是極其重要的。並且,在涉及視圖的時候,你纔會發現它原來仍是頗有意思的。

1.視圖

使用ArrayBuffer(數組緩衝器類型) 的一種特別的方式就是用它來建立數組緩衝器視圖。其中,最多見的視圖是DataView,經過它能夠選擇ArrayBuffer的一小段字節。爲此,能夠在建立DataView實例的時候傳入一個ArrayBuffer、一個可選的字節偏移量(從該字節開始選擇)和一個可選的要選擇的字節數。例如:

var buffer = new ArrayBuffer(20);

//基於整個緩衝器建立一個新視圖
var view = new DataView(buffer);

//建立一個開始於字節9的新視圖
var view = new DataView(buffer,9);

//建立一個從字節9開始到字節18的新視圖
var view = new DataView(buffer,9,10);

實例化後,DataView對象會把字節偏移量以及字節長度信息分別保存在byteOffset和byteLength屬性中。

console.log(view.byteOffset); //9
console.log(view.byteLength); //10

讀取和寫入DataView的時候,要根據實際操做的數據類型,選擇相應的getter和setter方法。下表列出了DataView支持的數據類型以及相應的讀寫方法。

數據類型 getter setter
有符號8位整數 getInt8(byteOffset) setInt8(byteOffset,value)
無符號8位整數 getUint8(byteOffset) setUint8(byteOffset,value)
有符號16位整數 getInt16(byteOffset,littleEndian) setInt16(byteOffset,value,littleEndian)
無符號16位整數 getUint16(byteOffset,littleEndian) setUint16(byteOffset,value,littleEndian)
有符號32位整數 getInt32(byteOffset,littleEndian) setInt32(byteOffset,value,littleEndian)
無符號32位整數 getUint32(byteOffset,littleEndian) setUint32(byteOffset,value,littleEndian)
32位浮點數 getFloat32(byteOffset,littleEndian) setFloat32(byteOffset,value,littleEndian)
64位浮點數 getFloat64(byteOffset,littleEndian) setFloat64(byteOffset,value,littleEndian)

 littleEndian:可選。若是爲 false 或未定義,則應寫入 big-endian 值;不然應寫入 little-endian 值。詳細瞭解《MSDN:DataView對象

全部這些方法的第一個參數都是一個字節偏移量,表示要從哪一個字節開始讀取或者寫入。不要忘了,要保存有些數據類型的數據,可能須要不止1B。好比,無符號8位整數要用1B,而32爲浮點數則要用4B。使用DataView,就須要你本身來管理這些細節,即要明確本身的數據須要多少字節,並選擇正確的讀寫方法。例如:

var buffer = new ArrayBuffer(20),
    view = new DataView(buffer),
    value;
    
view.setUint16(0,25);
view.setUint16(2,50); //不能從字節1開始,由於16位整數要用2B
value = view.getUint16(0);

 以上代碼把兩個無符號16位整數保存到了數組緩衝器中。由於每一個16位整數要用2B,因此保存第一個數的字節偏移量爲0,而保存第二個數的字節偏離量爲2。

能夠經過幾種不一樣的方式來訪問同一字節。例如:

var buffer = new ArrayBuffer(20),
    view = new DataView(buffer),
    value;
    
view.setUint16(0,25);
value = view.getInt8(0);
console.log(value); //0

在這個例子中,數值25以16位無符號整數的形式被寫入,字節偏移量爲0。而後,再以8位有符號整數的方式讀取該數據,獲得的結果爲0。這是由於25的二進制形式的前8位(第一個字節)全是0,如圖所示:

可見,雖然DataView能讓咱們在字節級別上讀寫數組緩衝器中的數據,但咱們必須本身記住要將數據保存到哪裏,須要佔用多少字節。這樣一來,就會帶來不少工做量,所以,類型化視圖也就應運而生。

2.類型化視圖

類型化視圖通常也被稱爲類型化數組,由於他們除了元素必須是某種特定的數據類型外,與常規的數組無異。類型化視圖也分爲幾種,並且它們都繼承了DataView。

  • Int8Array:表示8位二補整數。
  • Uint8Array:表示8位無符號整數。
  • Int16Array:表示16位二補整數。
  • Uint16Array:表示16位無符號整數。
  • Int32Array:表示32位二補整數。
  • Uint32Array:表示32位無符號整數。
  • Float32Array:表示32位IEEE浮點值。
  • Float64Array:表示64位IEEE浮點值。

每種視圖類型都以不一樣的方式表示數據,而同一數據視選擇的類型不一樣有可能佔用一或多字節。例如,20B的ArrayBuffer能夠保存20個Int8Array或Uint8Array,或者10個Int16Array或Uint16Array,或者5個Int32Array、Uint32Array或Float32Array,或者2個Float64Array。

因爲這些視圖都繼承自DataView,於是可使用相同的構造函數參數來實例化。第一個參數是要使用ArrayBuffer對象,第二個參數是做爲起點的字節偏移量(默認爲0),第三個參數是要包含的字節數。三個參數只有第一個是必須的。以下例子:

var buffer = new ArrayBuffer(200);
//建立一個新數組,使用整個緩衝器
var int8s = new Int8Array(buffer);

//只使用從字節10開始的緩衝器
var int16s = new Int16Array(buffer,10);

//只使用從字節10到字節19的的緩衝器
var uint16s = new Uint16Array(buffer,10,10);

 可以指定緩衝器中可用的字節段,意味着能在同一個緩衝器中保存不一樣類型的數值。好比,下面的代碼就是在緩衝器的開頭保存8位整數,而在其它字節中保存16位整數。

var buffer = new ArrayBuffer(32);

//使用緩衝器的一部分保存8位整數另外一部分保存在16位整數
var int8s = new Int8Array(buffer,0,11);
var uint16s = new Uint16Array(buffer,12,10);

 每一個視圖構造函數都有一個名爲BYTES_PER_ELEMENT的屬性,表示類型化數組的每一個元素須要多少字節。所以,Uint8Array.BYTES_PER_ELEMENT就是1,而Float32Array.BYTES_PER_ELEMENT則爲4。能夠利用這個屬性來輔助初始化。

var buffer = new ArrayBuffer(32);

//須要11個元素空間
var int8s = new Int8Array(buffer,0,11*Int8Array.BYTES_PER_ELEMENT);

//須要5個元素空間
var uint16s = new Uint16Array(buffer,int8s.byteOffset + int8s.byteLength + 1,5*Uint16Array.BYTES_PER_ELEMENT);

 類型化視圖的目的在於簡化對二進制數據的操做。除了前面看到的優勢以外,建立類型化視圖還能夠不用首先建立ArrayBuffer對象。只要傳入但願數組保存的元素數,相應的構造函數就能夠自動建立一個包含足夠字節數的ArrayBuffer對象,例如:

//建立一個數組保存10個8位整數(10字節)
var int8s = new Int8Array(10);

//建立一個數組保存10個16位整數(20字節)
var int16s = new Int16Array(10);

另外,也能夠把常規數組轉換爲類型化視圖,只要把常規數組傳入到類型化視圖的構造函數便可:

//建立一個數組保存5個8位整數(10字節)
var int8s = new Int8Array([10,20,30,40,50]);

這是用默認值來初始化類型化視圖的最佳方式,也是webGL項目中最經常使用的方式。

使用類型化視圖時,能夠經過length屬性肯定數組中有多少元素,以下:

//建立一個數組保存5個8位整數(10字節)
var int8s = new Int8Array([10,20,30,40,50]);
for(var i = 0,len = int8s.length; i < len; i++){
    console.log("value at position" + i + " is " + int8s[i]);
}

 固然也可使用方括號語法爲類型化視圖的元素賦值。若是爲相應的元素指定字節數放不下相應的值,則實際保存的值是最大可能值的模。例如,無符號16位整數所能表示的最大數值是65535,若是你想保存65536,那實際保存的值是0;若是你想保存65537,那實際保存的值是1,依次類推。

var uint16s = new Uint16Array(10);
uint16s[0] = 65537;
console.log(uint16s[0]);  //1

類型化視圖還有一個方法subarray(),使用這個方法能夠基於底層數組緩衝器的子集建立一個新視圖。這個方法接收兩個參數:開始元素的索引和可選的結束元素的索引。返回的類型與與調用該方法的視圖類型相同。例如:

var uint16s = new Uint16Array(10),
    sub = uint16s.subarray(2,5);
console.log(sub);  //[0, 0, 0]

在以上代碼中,sub也是Uint16Array的一個實例,並且底層與uint16s都基於同一個ArrayBuffer。

webGL上下文

目前,在支持的瀏覽器中,webGL的名字叫「experimental-webgl」,這是由於webGL的規範仍然未制定完成。制定完成後,這個名字就會變成簡單的「webgl」。若是瀏覽器不支持webGL,取得上下文時會返回null。在使用webGL時,務必先檢測一下返回值:

var drawing = document.getElementById("drawing");
//肯定瀏覽器支持<canvas>元素
if(drawing.getContext){
    var gl = drawing.getContext("experimental-webgl");
    if(gl){
        //使用webGL 
    }
}

 經過給getContext()傳遞第二個參數,能夠爲webGL上下文設置一些選項。這個參數自己是一個對象,能夠包含下列屬性。

  • alpha:值爲true,表示爲上下文建立一個Alpha通道緩衝區;默認值爲true。
  • depth:值爲true,表示可使用16位深緩衝區;默認值爲true。
  • stencil:值爲true,表示可使用8位模板緩衝區;默認值爲false。
  • antialias:值爲true,表示將使用默認機制執行抗鋸齒操做;默認值爲true。
  • premultipliedAlpha:值爲true,表示繪圖緩衝區有預乘Alpha值;默認值爲true。
  • preserveDrawingBuffer:值爲true,表示在繪圖完成後保留繪圖緩衝區;默認值爲false。建議確實有必要的狀況下再開啓這個值,由於可能影響性能。

傳遞這個選項對象的方式以下:

var drawing = document.getElementById("drawing");
//肯定瀏覽器支持<canvas>元素
if(drawing.getContext){
    var gl = drawing.getContext("experimental-webgl",{alpha:false});
    if(gl){
        //使用webGL 
    }
}

大多數上下文選項只在高級技巧中使用。不少時候,各個選項的默認值就能知足咱們的需求。

若是getContext()沒法建立webGL上下文,最好把調用封裝在try-catch塊中,以下:

var drawing = document.getElementById("drawing"),
    gl;
//肯定瀏覽器支持<canvas>元素
if(drawing.getContext){
    try{
        gl = drawing.getContext("experimental-webgl");
    }catch(ex){
        //什麼也不作
    }

    if(gl){
        //使用webGL 
    }else{
        alert("webGL context could not be created");
    }
}

 1.常量

若是你屬性openGL,那確定會對各類操做中使用很是多的常量印象深入。這些常量在openGL中都帶前綴GL_。在webGL中,保存在上下文對象中的這些常量都沒有GL_前綴。好比,GL_COLOR_BUFFER_BIT常量在webGL上下文就是gl.COLOR_BUFFER_BIT。webGL以這種方式支持大多數openGL常量(有一部分常量是不支持的)。

2.方法命名

openGL(以及webGL)中的不少方法都視圖經過名字傳達有關數據類型的信息。若是某種方法能夠接收不一樣類型以及不一樣數量的參數,看方法名的後綴就能夠知道。方法名的後綴會包含參數個數(1到4)和接收的數據類型(f表示浮點數,i表示整數)。例如,gl.uniform4f()意味着要接收4個浮點數,而gl.uniform3i()則表示要接收3個整數。

也有不少方法接收數組參數而非一個個單獨的參數。這樣的方法其名字中會包含字母v(即vector,矢量)。所以,gl.uniform3iv()能夠接收一個包含3個值的整數數組。請你們記住以上命名約定,這樣對理解後面關於webGL的討論頗有幫助。

3.準備繪圖

在實際操做webGL上下文以前,通常都要使用某種實色清除<canvas>,爲繪圖作好準備。爲此,首先必須使用clearColor()方法來指定要使用的顏色值,該方法接收4個參數:紅、綠、藍和透明度。每一個參數必須是一個0到1之間的數值,表示每種份量在最終顏色中的強度,以下代碼:

gl.clearColor(0,0,0,1); //black
gl.clear(gl_COLOR_BUFFER_BIT);

以上代碼把清理顏色緩衝區的值設置爲黑色,而後調用clear()方法,這個方法與openGL中的glClear()等價。傳入的參數gl_COLOR_BUFFER_BIT告訴webGL使用以前定義的顏色來填充相應區域。通常來講,都要先清理緩衝區,而後再執行其餘繪圖操做。

4.視口與座標

開始繪圖以前,一般要先定義webGL的視口(viewport)。默認狀況下視圖可使用整個<canvas>區域。要改變視口大小,能夠調用viewport()方法並傳入4個參數:(視口相對於<canvas>元素的)x座標、y座標、寬度和高度。例如,下面的調用就使用了<canvas>元素:

gl.viewport(0,0,drawing.width,drawing.height);

視口座標與咱們熟悉的網頁座標不同。視口座標的原點(0,0)在<canvas>元素的左下角,x軸和y軸的正方向分別是向右和向上,能夠定義爲(width-1,height-1),以下圖所示:

知道怎麼定義視口大小,就能夠只在<canvas>元素的部分區域中繪圖。以下例子:

//視口是<canvas>左下角的四分之一區域
gl.viewport(0,0,drawing.width/2,drawing.height/2);

//視口是<canvas>左上角的四分之一區域
gl.viewport(0,drawing.height/2,drawing.width/2,drawing.height/2);

//視口是<canvas>右下角的四分之一區域
gl.viewport(drawing.width/2,0,drawing.width/2,drawing.height/2);

另外,視圖內部的座標系與定義視口的座標系也不同。在視口內部,座標原點(0,0)是視口的中心點,所以,視口左下角座標爲(-1,-1),而右上角座標爲(1,1)。以下圖所示:

若是在視口內部繪圖時使用視口外部的座標,結果可能被視口剪切。好比,要繪製的形狀有一個頂點在(1,2),那麼該形狀在視口右側的部分會被剪切掉。

5.緩衝區

建立緩衝區:gl.createBuffer(),使用gl.bindBuffer()綁定到webGL上下文,gl.deleteBuffer()釋放內存。

6.錯誤

webGL操做通常不會拋出錯誤,手工調用gl.getError()方法。

7.着色器

webGL有兩種着色器:頂點着色器和片斷(或像素)着色器。

相關文章
相關標籤/搜索