HTML5簡單入門系列(八)

前言

本篇介紹HTML5中相對複雜的一些APIs,其中的數學知識比較多。雖然如此,可是其實API使用起來仍是比較方便的。html

這裏說明一下,只寫出API相關的JS代碼,由於他們都是基於一個canvas標籤進行的操做。有特殊狀況,我會單獨列出。canvas

下邊是公用的canvas標籤:數組

<canvas height="600" width="800" id="myCanvas"></canvas>

調用方式,也基本一致,以下:瀏覽器

1 window.onload = function () {
2             shadow();
3             //transparent();
4         }

 

HTML5 APIs 續

透明

設置圖形的透明度要用到 globalAlpha 屬性。globalAlpha 屬性的值是一個介於之間的浮點數。0表示徹底透明,而1表示徹底不透明。dom

示例代碼以下(注意畫圖時從新調用beginPath,不然後邊的fill方法會連同以前的圖形區域一塊兒從新填充顏色): ide

 1 function transparent() {
 2             var canvas = document.getElementById("myCanvas");
 3             var ctx = canvas.getContext("2d");
 4             ctx.globalAlpha = 0.7;
 5             ctx.beginPath();
 6             ctx.fillStyle = "red";
 7             ctx.rect(0, 0, 100, 100);
 8             ctx.fill();
 9 
10             ctx.globalAlpha = 0.4;
11             ctx.beginPath();
12             ctx.fillStyle = "green";
13             ctx.rect(50, 50, 150, 150);
14             ctx.fill();
15 
16             ctx.globalAlpha = 0.2;
17             ctx.fillStyle = "blue";
18             ctx.fillText("Sample String", 40, 60);
19         }

 效果如圖:學習

 陰影

要爲圖形(文本)添加陰影須要用到 shadowColorshadowBlurshadowOffsetX shadowOffsetY屬性。動畫

shadowOffsetX 和 shadowOffsetY 用來設定陰影在軸的延伸距離,負值表示陰影會往上或左延伸,正值則表示會往下或右延伸,默認都是0(像素)ui

shadowBlur 用於設定陰影的模糊程度,其數值並不跟像素數量掛鉤,也不受變換矩陣(後邊會說到,變換矩陣會影響縮放、旋轉和移動)影響,默認爲0(w3school定義是陰影的模糊級數,默認值#000000)this

shadowColor 用於設定陰影效果的延伸,值能夠是標準的CSS 顏色值,默認是全透明的黑色。

示例代碼以下: 

 1 function shadow() {
 2             var canvas = document.getElementById("myCanvas");
 3             var ctx = canvas.getContext("2d");
 4 
 5             ctx.shadowOffsetX = 5;//指示陰影位於形狀(文本) left 位置右側的 5 像素處
 6             ctx.shadowOffsetY = 5;
 7             ctx.shadowBlur = 2;
 8             ctx.shadowColor = "rgba(0, 0, 0, 0.5)";
 9 
10             ctx.font = "40px Times New Roman";
11             ctx.fillStyle = "Black";
12             ctx.fillText("Sample String", 5, 60);
13         }

 效果以下:

狀態的保存和恢復

save restore 方法是用來保存和恢復canvas 狀態的,都沒有參數。Canvas 的狀態就是當前畫面應用的全部樣式和變形的一個快照。

Canvas 狀態是以棧(stack)的方式保存的,每一次調用save 方法,當前的狀態就會被放入棧中保存起來。你能夠調用任意屢次save 方法。每一次調用restore 方法,上一個保存的狀態就從棧中彈出,全部設定都恢復。

看下邊的示例: 

 1 var i, context, interval;
 2         window.onload = function () {
 3             //draw();//可在瀏覽器端 單步調試效果更佳。
 4 
 5             i = 1;
 6             context = document.getElementById('myCanvas').getContext('2d');
 7             interval = setInterval(dystate, 500);
 8         }
 9         function draw() {
10             var ctx = document.getElementById('myCanvas').getContext('2d');
11 
12             ctx.fillRect(0, 0, 150, 150);   // state 1
13             ctx.save();
14 
15             ctx.fillStyle = '#09F'
16             ctx.fillRect(15, 15, 120, 120);  // state 2
17             ctx.save();
18 
19             ctx.fillStyle = '#FFF'
20             ctx.globalAlpha = 0.5;
21             ctx.fillRect(30, 30, 90, 90);   //state 3 沒有保存
22 
23             ctx.restore();               //恢復到 state 2
24             ctx.fillRect(45, 45, 60, 60);
25 
26             ctx.restore();               // 恢復到state 1
27             ctx.fillRect(60, 60, 30, 30);
28         }
29 
30         function dystate() {
31             var r1 = Math.floor((Math.random() * 2 + 1) * 255 % 255);
32             var r2 = Math.floor((Math.random() * 3 + 1) * 255 % 255);
33             var r3 = Math.floor((Math.random() * 7 + 1) * 255 % 255);
34             context.fillStyle = "rgb(" + r1 + "," + r2 + "," + r3 + ")";
35 
36             if (i <= 5) {
37                 context.fillRect(i * 10, i * 10, 150 - i * 15, 150 - i * 15);
38                 context.save();
39             }
40             else {
41                 context.restore();
42                 context.fillRect(i * 10, i * 10, 150 - i * 15, 150 - i * 15);
43             }
44             i = i + 1;
45             if (i > 10) {
46                 clearInterval(interval);
47             }
48         }

 效果以下:

該示例中,LZ使用了兩個示例,第一個示例單步調試效果和第二個示例效果基本同樣。

第二個示例是隨機顏色值,每次執行結果不一樣,請注意!

代碼中標出的紅色部分rgb方法就是獲取顏色值,rgba() 方法與 rgb() 方法相似,就多了一個用於設置色彩透明度的參數。它的有效範圍是從 0.0(徹底透明)到 1.0(徹底不透明)。

示例以下:

1 // 這些 fillStyle 的值均爲 '橙色'
2 ctx.fillStyle = "orange";
3 ctx.fillStyle = "#FFA500";
4 ctx.fillStyle = "rgb(255,165,0)";
5 ctx.fillStyle = "rgba(255,165,0,1)";

組合

以前的例子裏面,咱們老是將一個圖形畫在另外一個之上,大多數狀況下,這樣是不夠的。好比說,它這樣受制於圖形的繪製順序(透明度那個示例可說明,後繪畫的會覆蓋以前的繪畫)。不過,咱們能夠利用 globalCompositeOperation 屬性來改變這些作法。 

globalCompositeOperation 屬性設置或返回如何將一個源(新的)圖像繪製到目標(已有)的圖像上。

源圖像 = 您打算放置到畫布上的繪圖。 

目標圖像 = 您已經放置在畫布上的繪圖。 

默認值: source-over
JavaScript 語法: context.globalCompositeOperation="source-in";

 摘自Mozilla開發社區

source-over (default)

這是默認設置,新圖形會覆蓋在原有內容之上。

destination-over
會在原有內容之下繪製新圖形。

source-in

新圖形會僅僅出現與原有內容重疊的部分。其它區域都變成透明的。

destination-in

原有內容中與新圖形重疊的部分會被保留,其它區域都變成透明的。

source-out

結果是隻有新圖形中與原有內容不重疊的部分會被繪製出來。

destination-out

原有內容中與新圖形不重疊的部分會被保留。

source-atop

新圖形中與原有內容重疊的部分會被繪製,並覆蓋於原有內容之上。

destination-atop

原有內容中與新內容重疊的部分會被保留,並會在原有內容之下繪製新圖形

lighter

兩圖形中重疊部分做加色處理。

darker

兩圖形中重疊的部分做減色處理。

xor

重疊的部分會變成透明。

copy

只有新圖形會被保留,其它都被清除掉。

示例代碼:

這裏須要從新定義兩個canvas

1 <canvas id="myCanvas" width="578" height="430"></canvas>
2     <!-- 下面這個canvas就是用做內存中繪圖的,樣式被設爲不可見 -->
3     <canvas id="tempCanvas" width="578" height="430" style="display: none;"></canvas>

下邊的代碼將會繪製如上表中的圖形原型(代碼來源

 1 window.onload = function () {
 2             var canvas = document.getElementById("myCanvas");
 3             var context = canvas.getContext("2d");
 4             // 注意這裏建立了一個臨時canvas,能夠理解爲內存中繪圖所用,用於在正式將圖形畫到頁面以前先把完整的圖形在這個臨時canvas中畫完,而後再一會兒拷貝到真正用於顯示的 myCanvas上,而在頁面中的這個臨時canvas是不可見的
 5             var tempCanvas = document.getElementById("tempCanvas");
 6             var tempContext = tempCanvas.getContext("2d");
 7 
 8             var squareWidth = 55;
 9             var circleRadius = 35;
10             var startX = 10;
11             var startY = 30;
12             var rectCircleDistX = 50;
13             var rectCircleDistY = 50;
14             var exampleDistX = 150;
15             var exampleDistY = 140;
16             var arr = new Array();
17             arr.push("source-atop");
18             arr.push("source-in");
19             arr.push("source-out");
20             arr.push("source-over");
21             arr.push("destination-atop");
22             arr.push("destination-in");
23             arr.push("destination-out");
24             arr.push("destination-over");
25             arr.push("lighter");
26             arr.push("darker");
27             arr.push("xor");
28             arr.push("copy");
29             // 畫出十二種操做模式
30             for (var n = 0; n < arr.length; n++) {
31                 var thisX; var thisY;
32                 var thisOperation = arr[n];
33                 // 第一行     
34                 if (n < 4) {
35                     thisX = startX + (n * exampleDistX);//橫座標移到下個位置
36                     thisY = startY;
37                 }
38                     // 第二行          
39                 else if (n < 8) {
40                     thisX = startX + ((n - 4) * exampleDistX);//橫座標回到第二行起始位置,並後移
41                     thisY = startY + exampleDistY;//縱座標移到第二行
42                 }
43                     // 第三行        
44                 else {
45                     thisX = startX + ((n - 8) * exampleDistX);
46                     thisY = startY + (exampleDistY * 2);
47                 }
48 
49                 tempContext.clearRect(0, 0, canvas.width, canvas.height);//整個canvas被清空
50                 // 畫矩形
51                 tempContext.beginPath();
52                 tempContext.rect(thisX, thisY, squareWidth, squareWidth);
53                 tempContext.fillStyle = "blue";
54                 tempContext.fill();
55 
56                 // 設置全局組合模式
57                 tempContext.globalCompositeOperation = thisOperation;
58                 // 畫圓
59                 tempContext.beginPath();
60                 tempContext.arc(thisX + rectCircleDistX, thisY + rectCircleDistY, circleRadius, 0, 2 * Math.PI, false);
61                 tempContext.fillStyle = "red";
62                 tempContext.fill();
63                 // 恢復成默認狀態
64                 tempContext.globalCompositeOperation = "source-over";
65                 tempContext.font = "10pt Verdana";
66                 tempContext.fillStyle = "black";
67                 tempContext.fillText(thisOperation, thisX, thisY + squareWidth + 45);
68                 // 將圖像從 tempCanvas 拷貝到 myCanvas 
69                 context.drawImage(tempCanvas, 0, 0);
70             }
71         }

關於爲何須要一個臨時的canvas,這就和globalCompositeOperation 屬性相關了,LZ單步調試了,後邊的繪圖(部分)會將以前的內容清空,好比最後一個copy值,會清空已有的全部圖形而單獨顯示它本身,這是copy的本質,其餘幾個屬性值,也有相似行爲,所以須要一個臨時canvas,一個一個拷貝到展現的canvas上。

效果圖以下:

裁切路徑

clip() 方法從原始畫布中剪切任意形狀和尺寸。

一旦剪切了某個區域,則全部以後的繪圖都會被限制在被剪切的區域內(不能訪問畫布上的其餘區域)。

若是和上面介紹的 globalCompositeOperation 屬性做一比較,它能夠實現與source-in source-atop 差很少的效果。最重要的區別是裁切路徑不會在canvas 上繪製東西,並且它不受新圖形的影響。

默認狀況下,canvas 有一個與它自身同樣大的裁切路徑(也就是繪圖只能在canvas範圍內)。

咱們使用兩個canvas對比使用clip和不使用clip時,對繪圖效果的影響。

 1 <!DOCTYPE html>
 2 <html>
 3 <body>
 4     <p>不使用 clip():</p>
 5     <canvas id="myCanvas" width="300" height="150" style="border: 1px solid red;">Your browser does not support the HTML5 canvas tag.
 6     </canvas>
 7 
 8     <script>
 9         var c = document.getElementById("myCanvas");
10         var ctx = c.getContext("2d");
11         
12         ctx.rect(50, 20, 200, 120);
13         ctx.strokeStyle = "blue";
14         ctx.stroke();
15         
16         ctx.fillStyle = "green";
17         ctx.fillRect(0, 0, 150, 100);
18     </script>
19 
20     <br />
21 
22     <p>使用 clip():</p>
23     <canvas id="myCanvas2" width="300" height="150" style="border: 1px solid red;">Your browser does not support the HTML5 canvas tag.
24     </canvas>
25 
26     <script>
27         var c = document.getElementById("myCanvas2");
28         var ctx = c.getContext("2d");
29         // Clip a rectangular area
30         ctx.rect(50, 20, 200, 120);
31         ctx.strokeStyle = "blue";
32         ctx.stroke();
33         ctx.clip();
34         
35         ctx.fillStyle = "green";
36         ctx.fillRect(0, 0, 150, 100);
37     </script>
38 </body>
39 </html>

效果以下(紅色是canvas整個區域,藍色是裁剪區,綠色是填充效果):

移動

translate(x, y) 它用來移動 canvas 和它的原點到一個不一樣的位置。

默認初始時,canvas的原點座標是(0,0),咱們能夠把canvas看作一個原點座標是(0,0)的座標系。使用該方法後,整個canvas位移到(x,y)爲原點的座標系。以後的畫圖操做數值都是相對於移動以後的座標系的,如:

位移以後,某座標位置是(100,100),則相對於移動以前的座標系,它是(100+x,100+y),原座標系的(x,y)是當前座標系的(0,0)原點。

看下邊的示例: 

 1 function randcircle() {
 2             var c = document.getElementById("myCanvas");
 3             var ctx = c.getContext("2d");
 4             for (var x = 5; x < 10; x++) {
 5                 for (var y = 5; y < 10; y++) {
 6                     ctx.save();
 7                     //circle(ctx, x * 20, y * 20, 10);
 8                     ctx.translate(x * 20, y * 20);
 9                     circle(ctx, 0, 0, 10);
10                     ctx.restore();
11                 }
12             }
13         }
14         function circle(ctx, x, y, R) {
15             var r1 = Math.floor((Math.random() * 2 + 1) * 255 % 255);
16             var r2 = Math.floor((Math.random() * 3 + 1) * 255 % 255);
17             var r3 = Math.floor((Math.random() * 7 + 1) * 255 % 255);
18             ctx.beginPath();
19             ctx.fillStyle = "rgb(" + r1 + "," + r2 + "," + r3 + ")";
20 
21             //var s = Math.floor((Math.random() * 10 + 5) * 10 % 10);
22 //ctx.scale(s, s); 23 24 ctx.arc(x, y, R, 0, 2 * Math.PI, false); 25 ctx.fill(); 26 }

 看代碼7-9行

7 //circle(ctx, x * 20, y * 20, 10);
8 ctx.translate(x * 20, y * 20);
9 circle(ctx, 0, 0, 10);

註釋的這一句能夠當作下邊兩句來使用,但這並沒能將translate的優點展現出來。

咱們能夠想象,circle方法內若是是定點畫圓呢?也就是說circle若是不接收位置參數,那麼先移動座標原點再調用方法則能夠在隨意位置畫圓了。

 效果圖以下(顏色是隨機的):

值得注意的是,代碼中21-22行註釋掉的縮放代碼,對圓心的定位影響很大,LZ還須要再研究一下。先看一下效果圖吧。

一、將21-22代碼取消註釋,其餘不變,效果以下(效果同LZ所想):

二、將21-22代碼取消註釋,修改代碼以下

7 circle(ctx, x * 20, y * 20, 10);
8 //ctx.translate(x * 20, y * 20);
9 //circle(ctx, 0, 0, 10);

效果如圖(LZ略暈,由於圓心位置變了):

縮放

scale 方法接受兩個參數。x,y 分別是橫軸和縱軸的縮放因子,它們都必須是正值。值比1.0 小表示縮小,比1.0 大則表示放大,值爲1.0 時什麼效果都沒有。

若是您對繪圖進行縮放,全部以後的繪圖也會被縮放。定位也會被縮放,若是您 scale(2,2),那麼繪圖將定位於距離畫布左上角兩倍遠的位置。

看到標紅的文字,LZ釋然了,上邊的例子中的疑惑也就澄清了。讓咱們來看個示例再對比一下效果吧。 

 1 function scale() {
 2             var c = document.getElementById("myCanvas");
 3             var ctx = c.getContext("2d");
 4             ctx.strokeRect(5, 5, 25, 15);
 5             ctx.scale(2, 2);
 6             ctx.strokeRect(5, 5, 25, 15);
 7             ctx.scale(2, 2);
 8             ctx.strokeRect(5, 5, 25, 15);
 9             ctx.scale(2, 2);
10             ctx.strokeRect(5, 5, 25, 15);
11         }

 咱們畫了是個矩形,每次橫向縱向放大2倍,效果以下:

 

雖然每次都是從(5,5)到(25,15)的矩形,可是縮放以後,不止長寬變了,起點座標也跟着一塊兒變化了

而上邊translate例子中,移動原點位置以後再縮放,則隻影響長度不影響定位

旋轉

rotate(angle)這個方法只接受一個參數:旋轉的弧度(angle),它是順時針方向的,以弧度爲單位的值。若是是角度,則可使用 degrees*Math.PI/180轉換成弧度。

旋轉的中心點始終是 canvas 的原點,若是要改變它,須要用到 translate 方法。該方法比較簡單,看下邊的示例:

 

 1 function rotate() {
 2             var canvas = document.getElementById("myCanvas");
 3             var context = canvas.getContext("2d");
 4             context.translate(400, 300);
 5             context.arc(0, 0, 10, 0, Math.PI * 2, false);//中心一個圓
 6             context.fill();
 7             for (var i = 1; i < 8; i++) {//層數
 8                 var x = i * 20;//x座標
 9                 var n = i * 6;//該層圓圈數量
10                 var s = 2 * Math.PI * x;//當前x座標圓周長,用於計算小圓的半徑
11                 var r = Math.floor(s / n / 2);//半徑
12                 var angle = 2 * Math.PI / n;//旋轉角度
13                 for (var j = 0; j < n; j++) {//每層是6的倍數遞增
14                     context.save();
15                     context.beginPath();
16                     context.rotate(angle * j);
17                     context.arc(x, 0, r, 0, 2 * Math.PI, false);
18                     context.fillStyle = 'rgb(' + (30 * i) + ',' + (255 - 30 * i) + ',255)';
19                     context.fill();
20                     context.restore();
21                 }
22             }
23         }

 

首先咱們先移動原點到(400,300),而後畫了一個小圓。

咱們設定圓圈展現層數、及每層數量,而後根據當前層所在座標及每層數量(6倍遞增)計算當前層小圓的半徑和旋轉角度(弧度)

最後在同一個位置畫圓,再將小圓旋轉到正確位置(記得每次save和restore,不然旋轉角度沒必要angle * j,而是繼承上次旋轉角度;還有每次繪圖以前要用beginPath哦!)

效果以下圖:

 

變換矩陣

 

transform(a,b,c,d,e,f) 方法以用戶自定義的變換矩陣對圖像座標進行變換操做

setTransform(a,b,c,d,e,f) 方法重置當前的變形矩陣爲單位矩陣,而後以相同的參數調用 transform 方法。(反正LZ是有點暈~~~)

這個方法須要6個參數組成一個 3 x 3 的轉換矩陣,座標由 (x, y) 到 (x', y') 的轉換公式以下所示:

 這個。。計算。。呵呵。。記住下邊的參數說明就好了。。 

參數說明以下:

 

參數

描述

a

水平縮放繪圖

b

水平傾斜繪圖

c

垂直傾斜繪圖

d

垂直縮放繪圖

e

水平移動繪圖

f

垂直移動繪圖

咱們仍是看下邊的例子吧。

 1 function transform() {
 2             var canvas = document.getElementById("myCanvas");
 3             var ctx = canvas.getContext("2d");
 4 
 5             var sin = Math.sin(Math.PI / 6);
 6             var cos = Math.cos(Math.PI / 6);
 7             ctx.translate(200, 200);
 8             var c = 0;
 9             for (var i = 0; i <= 12; i++) {
10                 c = Math.floor(255 / 12 * i);
11                 ctx.fillStyle = "rgb(" + c + "," + c + "," + c + ")";
12                 ctx.fillRect(0, 0, 100, 10);
13                 ctx.transform(cos, sin, -sin, cos, 0, 0);
14             }
15 
16             ctx.setTransform(-1, 0, 0, 1, 200, 200);
17             ctx.fillStyle = "rgba(255, 128, 255, 0.5)";
18             ctx.fillRect(0, 50, 100, 100);
19         }

先上效果圖再解釋:

看到效果,各位園友以爲這個矩形是怎麼畫出來的呢?此時的座標原點在哪裏呢?

好吧,LZ得認可最開始這個旋轉的橫條效果,也沒有搞的很明白,雖然單步調試看到了效果。。。對於這個矩形呢,LZ也沒有找到正確的座標原點。

因此,我添加以下代碼

ctx.strokeStyle = "green";
ctx.arc(0, 0, 50, 0, Math.PI, false);
ctx.stroke();

如今的效果圖以下:

如今找到了座標原點了吧,對,它就在綠色弧線中心。

這裏LZ要強調一下,setTransform方法會重置當前的變形矩陣爲單位矩陣,即原始沒有旋轉、沒有縮放、沒有移動的原始座標系。

因此setTransform第5/6兩個參數將做爲移動(translate)參數進行原點移動,就到了上圖中綠色弧線的中心了。

那麼接下來,根據繪製矩形的座標設置,咱們想一想它是怎樣一個效果。

ctx.fillRect(0, 50, 100, 100);

起點(0,50)沒問題,那麼左下角(100,100)的橫座標怎麼偏向左邊去了呢?

這就是咱們在調用setTransform是第一個參數的效果,水平縮放-1,即長度不變,方向相反。第四個參數垂直縮放1,保持原來的效果。

咱們再修改代碼,看看第二個、第三個參數的效果。修改以下:

ctx.setTransform(-1, 0.4, 0, 1, 200, 200);

效果以下:

咱們修改了第二參數,水平傾斜0.4(傾斜和旋轉都是順時針的),可是效果圖是逆時針傾斜的,這和第一個參數-1有關,咱們將第一個參數改爲1,效果以下,符合咱們的預期:

那麼到這裏,應該這幾個參數都弄明白了吧。

咱們介紹的只是setTransform方法,它和transform的使用是同樣的。

區別在於,setTransform會先將座標系重置即所謂的單位矩陣,而後在參數基礎上執行transform方法。

關於那個'米'字運行效果,就再也不作解釋了。

小結

本篇介紹了透明、陰影、狀態的保存和恢復、組合、裁剪路徑、移動、縮放、旋轉及變換矩陣,東西略多,樓主也是蒐羅了好多地方纔整理好。

參考資料

w3school

Mozilla開發者社區

canvas教程 (頁面左下角的文章列表)

若是有些說明錯誤的地方,請園友大牛指正。

後邊還有一些關於動畫的東西,可是和HTML5 APIs 沒什麼直接關係,都是使用現有js技術結合HTML5 APIs來實現的動態效果,有興趣的能夠本身搜索一下。


 寫博小感

經過寫該系列博客,樓主真心感受到寫博的不容易,單是本篇,樓主用了整整一天時間來完成。

雖然本系列只是樓主的學習記錄,可是寫出來的時候,內心老是膽戰心驚,這裏沒弄明白寫錯了怎麼辦?讓園友大牛笑話怎麼辦?誤導其餘人怎麼辦?

尤爲是最近兩篇APIs的介紹,每一個參數是作什麼的、最後有什麼效果,樓主都不敢妄想,從各處搜索資料,一一實驗(雖然有些代碼並不是原創),而後將本身的想法描述出來。

儘管耗時耗力,可是終歸算是有了一個結束,樓主本人也很有收穫,也終於明白爲何前人強調要寫博客的重要性,就算是很細小很簡單的知識點,說不定就能解決別人在這個節骨眼上碰到麻煩。

好了,就說這些吧,歡迎拍磚(凡是打不死個人,終將讓我變得更加堅強,O(∩_∩)O哈哈~)

相關文章
相關標籤/搜索