第156天:canvas(三)
一、變形
1.1 translate
translate(x, y)
? 用來移動 canvas
的原點到指定的位置
? translate
方法接受兩個參數。x
是左右偏移量,y
是上下偏移量,如右圖所示。
在做變形之前先保存狀態是一個良好的習慣。大多數情況下,調用 restore
方法比手動恢復原先的狀態要簡單得多。又如果你是在一個循環中做位移但沒有保存和恢復canvas
的狀態,很可能到最後會發現怎麽有些東西不見了,那是因為它很可能已經超出 canvas
範圍以外了。
? 註意:translate
移動的是canvas
的坐標原點。(坐標變換)
1 var ctx; 2 functiondraw(){ 3 var canvas = document.getElementById(‘tutorial1‘); 4 if (!canvas.getContext) return; 5 var ctx = canvas.getContext("2d"); 6 ctx.save(); //保存坐原點平移之前的狀態 7 ctx.translate(100, 100); 8 ctx.strokeRect(0, 0, 100, 100) 9 ctx.restore(); //恢復到最初狀態 10 ctx.translate(220, 220); 11ctx.fillRect(0, 0, 100, 100) 12 } 13 draw();
1.2 rotate
rotate(angle)
? 旋轉坐標軸。
? 這個方法只接受一個參數:旋轉的角度(angle),它是順時針方向的,以弧度為單位的值。
? 旋轉的中心是坐標原點。
1 var ctx; 2 function draw(){ 3 var canvas = document.getElementById(‘tutorial1‘); 4 if (!canvas.getContext) return; 5 var ctx = canvas.getContext("2d");6 7 ctx.fillStyle = "red"; 8 ctx.save(); 9 10 ctx.translate(100, 100); 11 ctx.rotate(Math.PI / 180 * 45); 12 ctx.fillStyle = "blue"; 13 ctx.fillRect(0, 0, 100, 100); 14 ctx.restore(); 15 16 ctx.save(); 17 ctx.translate(0, 0); 18 ctx.fillRect(0, 0, 50, 50) 19 ctx.restore(); 20 } 21 draw();
1.3 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 倍。
1.4 transform(變形矩陣)
transform(a, b, c, d, e, f)
a (m11)
? Horizontal scaling.
b (m12)
? Horizontal skewing.
c (m21)
? Vertical skewing.
d (m22)
? Vertical scaling.
e (dx)
? Horizontal moving.
f (dy)
? Vertical moving.
1 var ctx; 2 function draw(){ 3 var canvas = document.getElementById(‘tutorial1‘); 4 if (!canvas.getContext) return; 5 var ctx = canvas.getContext("2d"); 6 ctx.transform(1, 1, 0, 1, 0, 0); 7 ctx.fillRect(0, 0, 100, 100); 8 } 9 draw();
二、合成
? 在前面的所有例子中、,我們總是將一個圖形畫在另一個之上,對於其他更多的情況,僅僅這樣是遠遠不夠的。比如,對合成的圖形來說,繪制順序會有限制。不過,我們可以利用 globalCompositeOperation
屬性來改變這種狀況。
globalCompositeOperation = type
1 var ctx; 2 function draw(){ 3 var canvas = document.getElementById(‘tutorial1‘); 4 if (!canvas.getContext) return; 5 var ctx = canvas.getContext("2d"); 6 7 ctx.fillStyle = "blue"; 8 ctx.fillRect(0, 0, 200, 200); 9 10 ctx.globalCompositeOperation = "source-over"; //全局合成操作 11 ctx.fillStyle = "red"; 12 ctx.fillRect(100, 100, 200, 200); 13 } 14 draw(); 15 16 </script>
註:下面的展示中,藍色是原有的,紅色是新的。
type `是下面 13 種字符串值之一:
1. source-over(default)
這是默認設置,新圖像會覆蓋在原有圖像。
2. source-in
僅僅會出現新圖像與原來圖像重疊的部分,其他區域都變成透明的。(包括其他的老圖像區域也會透明)
3. source-out
僅僅顯示新圖像與老圖像沒有重疊的部分,其余部分全部透明。(老圖像也不顯示)
4. source-atop
新圖像僅僅顯示與老圖像重疊區域。老圖像仍然可以顯示。
5. destination-over
新圖像會在老圖像的下面。
6. destination-in
僅僅新老圖像重疊部分的老圖像被顯示,其他區域全部透明。
7. destination-out
僅僅老圖像與新圖像沒有重疊的部分。 註意顯示的是老圖像的部分區域。
8. destination-atop
老圖像僅僅僅僅顯示重疊部分,新圖像會顯示在老圖像的下面。
9. lighter
新老圖像都顯示,但是重疊區域的顏色做加處理
10. darken
保留重疊部分最黑的像素。(每個顏色位進行比較,得到最小的)
blue: #0000ff
red: #ff0000
所以重疊部分的顏色:#000000
11. lighten
保證重疊部分最量的像素。(每個顏色位進行比較,得到最大的)
blue: #0000ff
red: #ff0000
所以重疊部分的顏色:#ff00ff
12. xor
重疊部分會變成透明
13. copy
只有新圖像會被保留,其余的全部被清除(邊透明)
三、裁剪路徑
clip()
? 把已經創建的路徑轉換成裁剪路徑。
? 裁剪路徑的作用是遮罩。只顯示裁剪路徑內的區域,裁剪路徑外的區域會被隱藏。
? 註意:clip()
只能遮罩在這個方法調用之後繪制的圖像,如果是clip()
方法調用之前繪制的圖像,則無法實現遮罩。
1 var ctx; 2 function draw(){ 3 var canvas = document.getElementById(‘tutorial1‘); 4 if (!canvas.getContext) return; 5 var ctx = canvas.getContext("2d"); 6 7 ctx.beginPath(); 8 ctx.arc(20,20, 100, 0, Math.PI * 2); 9 ctx.clip(); 10 11 ctx.fillStyle = "pink"; 12 ctx.fillRect(20, 20, 100,100); 13 } 14 draw();
四、動畫
動畫的基本步驟
-
清空
canvas
再繪制每一幀動畫之前,需要清空所有。清空所有最簡單的做法就是
clearRect()
方法 -
保存
canvas
狀態如果在繪制的過程中會更改
canvas
的狀態(顏色、移動了坐標原點等),又在繪制每一幀時都是原始狀態的話,則最好保存下canvas
的狀態 -
繪制動畫圖形
這一步才是真正的繪制動畫幀
-
恢復
canvas
狀態如果你前面保存了
canvas
狀態,則應該在繪制完成一幀之後恢復canvas
狀態。
控制動畫
我們可用通過canvas
的方法或者自定義的方法把圖像會知道到canvas
上。正常情況,我們能看到繪制的結果是在腳本執行結束之後。例如,我們不可能在一個 for
循環內部完成動畫。
也就是,為了執行動畫,我們需要一些可以定時執行重繪的方法。
一般用到下面三個方法:
setInterval()
setTimeout()
requestAnimationFrame()
案例1:太陽系
1 let sun; 2 let earth; 3 let moon; 4 let ctx; 5 function init(){ 6 sun = new Image(); 7 earth = new Image(); 8 moon = new Image(); 9 sun.src = "sun.png"; 10 earth.src = "earth.png"; 11 moon.src = "moon.png"; 12 13 let canvas = document.querySelector("#solar"); 14 ctx = canvas.getContext("2d"); 15 16 sun.onload = function (){ 17 draw() 18 } 19 20 } 21 init(); 22 function draw(){ 23 ctx.clearRect(0, 0, 300, 300); //清空所有的內容 24 /*繪制 太陽*/ 25 ctx.drawImage(sun, 0, 0, 300, 300); 26 27 ctx.save(); 28 ctx.translate(150, 150); 29 30 //繪制earth軌道 31 ctx.beginPath(); 32 ctx.strokeStyle = "rgba(255,255,0,0.5)"; 33 ctx.arc(0, 0, 100, 0, 2 * Math.PI) 34 ctx.stroke() 35 36 let time = new Date(); 37 //繪制地球 38 ctx.rotate(2 * Math.PI / 60 * time.getSeconds() + 2 * Math.PI / 60000 * time.getMilliseconds()) 39 ctx.translate(100, 0); 40 ctx.drawImage(earth, -12, -12) 41 42 //繪制月球軌道 43 ctx.beginPath(); 44 ctx.strokeStyle = "rgba(255,255,255,.3)"; 45 ctx.arc(0, 0, 40, 0, 2 * Math.PI); 46 ctx.stroke(); 47 48 //繪制月球 49 ctx.rotate(2 * Math.PI / 6 * time.getSeconds() + 2 * Math.PI / 6000 * time.getMilliseconds()); 50 ctx.translate(40, 0); 51 ctx.drawImage(moon, -3.5, -3.5); 52 ctx.restore(); 53 54 requestAnimationFrame(draw); 55 }
案例2:模擬時鐘
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 <style> 7 body { 8 padding: 0; 9 margin: 0; 10 background-color: rgba(0, 0, 0, 0.1) 11 } 12 13 canvas { 14 display: block; 15 margin: 200px auto; 16 } 17 </style> 18 </head> 19 <body> 20 <canvas id="solar" width="300" height="300"></canvas> 21 <script> 22 init(); 23 24 function init(){ 25 let canvas = document.querySelector("#solar"); 26 let ctx = canvas.getContext("2d"); 27 draw(ctx); 28 } 29 30 function draw(ctx){ 31 requestAnimationFrame(function step(){ 32 drawDial(ctx); //繪制表盤 33 drawAllHands(ctx); //繪制時分秒針 34 requestAnimationFrame(step); 35 }); 36 } 37 /*繪制時分秒針*/ 38 function drawAllHands(ctx){ 39 let time = new Date(); 40 41 let s = time.getSeconds(); 42 let m = time.getMinutes(); 43 let h = time.getHours(); 44 45 let pi = Math.PI; 46 let secondAngle = pi / 180 * 6 * s; //計算出來s針的弧度 47 let minuteAngle = pi / 180 * 6 * m + secondAngle / 60; //計算出來分針的弧度 48 let hourAngle = pi / 180 * 30 * h + minuteAngle / 12; //計算出來時針的弧度 49 50 drawHand(hourAngle, 60, 6, "red", ctx); //繪制時針 51 drawHand(minuteAngle, 106, 4, "green", ctx); //繪制分針 52 drawHand(secondAngle, 129, 2, "blue", ctx); //繪制秒針 53 } 54 /*繪制時針、或分針、或秒針 55 * 參數1:要繪制的針的角度 56 * 參數2:要繪制的針的長度 57 * 參數3:要繪制的針的寬度 58 * 參數4:要繪制的針的顏色 59 * 參數4:ctx 60 * */ 61 function drawHand(angle, len, width, color, ctx){ 62 ctx.save(); 63 ctx.translate(150, 150); //把坐標軸的遠點平移到原來的中心 64 ctx.rotate(-Math.PI / 2 + angle); //旋轉坐標軸。 x軸就是針的角度 65 ctx.beginPath(); 66 ctx.moveTo(-4, 0); 67 ctx.lineTo(len, 0); // 沿著x軸繪制針 68 ctx.lineWidth = width; 69 ctx.strokeStyle = color; 70 ctx.lineCap = "round"; 71 ctx.stroke(); 72 ctx.closePath(); 73 ctx.restore(); 74 } 75 76 /*繪制表盤*/ 77 function drawDial(ctx){ 78 let pi = Math.PI; 79 80 ctx.clearRect(0, 0, 300, 300); //清除所有內容 81 ctx.save(); 82 83 ctx.translate(150, 150); //一定坐標原點到原來的中心 84 ctx.beginPath(); 85 ctx.arc(0, 0, 148, 0, 2 * pi); //繪制圓周 86 ctx.stroke(); 87 ctx.closePath(); 88 89 for (let i = 0; i < 60; i++){//繪制刻度。 90 ctx.save(); 91 ctx.rotate(-pi / 2 + i * pi / 30); //旋轉坐標軸。坐標軸x的正方形從 向上開始算起 92 ctx.beginPath(); 93 ctx.moveTo(110, 0); 94 ctx.lineTo(140, 0); 95 ctx.lineWidth = i % 5 ? 2 : 4; 96 ctx.strokeStyle = i % 5 ? "blue" : "red"; 97 ctx.stroke(); 98 ctx.closePath(); 99 ctx.restore(); 100 } 101 ctx.restore(); 102 } 103 </script> 104 </body> 105 </html>
第156天:canvas(三)