本篇介紹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 }
設置圖形的透明度要用到 globalAlpha 屬性。globalAlpha 屬性的值是一個介於0 到1 之間的浮點數。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 }
效果如圖:學習
要爲圖形(文本)添加陰影須要用到 shadowColor,shadowBlur,shadowOffsetX 和shadowOffsetY屬性。動畫
shadowOffsetX 和 shadowOffsetY 用來設定陰影在X 和Y 軸的延伸距離,負值表示陰影會往上或左延伸,正值則表示會往下或右延伸,默認都是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"; |
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方法。
關於那個'米'字運行效果,就再也不作解釋了。
本篇介紹了透明、陰影、狀態的保存和恢復、組合、裁剪路徑、移動、縮放、旋轉及變換矩陣,東西略多,樓主也是蒐羅了好多地方纔整理好。
參考資料
canvas教程 (頁面左下角的文章列表)
若是有些說明錯誤的地方,請園友大牛指正。
後邊還有一些關於動畫的東西,可是和HTML5 APIs 沒什麼直接關係,都是使用現有js技術結合HTML5 APIs來實現的動態效果,有興趣的能夠本身搜索一下。
經過寫該系列博客,樓主真心感受到寫博的不容易,單是本篇,樓主用了整整一天時間來完成。
雖然本系列只是樓主的學習記錄,可是寫出來的時候,內心老是膽戰心驚,這裏沒弄明白寫錯了怎麼辦?讓園友大牛笑話怎麼辦?誤導其餘人怎麼辦?
尤爲是最近兩篇APIs的介紹,每一個參數是作什麼的、最後有什麼效果,樓主都不敢妄想,從各處搜索資料,一一實驗(雖然有些代碼並不是原創),而後將本身的想法描述出來。
儘管耗時耗力,可是終歸算是有了一個結束,樓主本人也很有收穫,也終於明白爲何前人強調要寫博客的重要性,就算是很細小很簡單的知識點,說不定就能解決別人在這個節骨眼上碰到麻煩。
好了,就說這些吧,歡迎拍磚(凡是打不死個人,終將讓我變得更加堅強,O(∩_∩)O哈哈~)