一、Canvas基本使用 Canvas是HTML5的畫布,Canvas算是“不務正業”的面向對象大總結,將面向對象玩極致。 演算法為王!就是說canvas你不會,但是演算法好,不怕寫業務,不怕代碼量,只要稍微學學API就能出活。 Canvas這裡是HTML5新標簽,直接要了flash的命。 1.1 Ca ...
一、Canvas基本使用
Canvas是HTML5的畫布,Canvas算是“不務正業”的面向對象大總結,將面向對象玩極致。
演算法為王!就是說canvas你不會,但是演算法好,不怕寫業務,不怕代碼量,只要稍微學學API就能出活。
Canvas這裡是HTML5新標簽,直接要了flash的命。
1.1 Canvas簡介
MDN的Canvas線上手冊:
https://developer.mozilla.org/zh-CN/docs/Web/API/Canvas_API
瞭解:
<canvas>是一個可以使用腳本(通常為JavaScript)來繪製圖形的 HTML 元素.它可以用於繪製圖表、製作圖片構圖或者製作簡單的(以及不那麼簡單的)動畫. 右邊的圖片展示了一些 <canvas> 的實現示例。
歷史:
<canvas> 最早由Apple引入WebKit,用於Mac OS X 的 Dashboard,隨後被各個瀏覽器實現。如今,所有主流的瀏覽器都支持它。
Mozilla 程式從 Gecko 1.8 (Firefox 1.5) 開始支持 <canvas>。它首先是由 Apple 引入的,用於 OS X Dashboard 和 Safari。Internet Explorer 從IE9開始支持<canvas> ,更舊版本的IE可以引入 Google 的 Explorer Canvas 項目中的腳本來獲得<canvas>支持。Chrome和Opera 9+ 也支持 <canvas>。
Canvas相容到IE9。
1.2 Canvas入門
canvas是HTML5中比較特殊的雙標簽,可以在body中放:
<html> <head> <meta charset="UTF-8" /> <title>Document</title> <style type="text/css"> canvas{border:1px solid #000;} </style> </head> <body> <canvas width="600" height="400"></canvas> </body> </html>
不能將width、height在CSS中設置,否則畫布的像素的會被縮放,畫面質量粗糙了。
<canvas>元素可以像任何一個普通的圖像一樣(有margin,border,background等等屬性)被設計。然而,這些樣式不會影響在canvas中的實際圖像。
畫布沒什麼用,所有操作都要在“上下文”中進行,這裡的上下文是環境的意思,不是面向對象中的this。
<script type="text/javascript"> //得到畫布標簽 var canvas = document.querySelector("canvas"); //使用上下文,得到一個2D的畫布 var ctx = canvas.getContext("2d"); //畫畫 ctx.fillRect(100, 100, 300, 100); </script>
Canvas的本質就是用js來畫畫,所有的繪畫函數,都是ctx的方法。
canvas馬上開始面對一堆API:
<script type="text/javascript"> //得到畫布標簽 var canvas = document.querySelector("canvas"); //使用上下文,得到一個2D的畫布 var ctx = canvas.getContext("2d"); //繪製矩形 ctx.fillStyle = "orange"; //先提供一個顏色的筆 ctx.fillRect(100, 100, 300, 100); //在根據以上顏色填充 ctx.fillStyle = "green"; //先提供一個顏色的筆 ctx.fillRect(100, 200, 300, 200); //在根據以上顏色填充 </script>
Canvas的坐標系和絕對定位的坐標系是一樣的。
二、Canvas繪製形狀
2.1繪製形狀路徑
在Canvas中有兩種東西:
l stroke路徑【筆觸】,也叫描邊,就是形狀的輪廓
l fill填充,就是裡面的顏色
//得到畫布標簽 var canvas = document.querySelector('canvas'); //使用上下文,得到一個2D畫布 var ctx = canvas.getContext("2d"); //畫畫 ctx.beginPath(); //聲明要開始繪製路徑 ctx.moveTo(100,100); //移動到繪製點,將“畫筆”移動到100,100的位置 ctx.lineTo(250,250); //劃線 ctx.lineTo(500,250); //劃線 ctx.lineWidth = 10; //線的粗細 ctx.strokeStyle = "red"; //線的顏色 ctx.fillStyle = "blue"; //準備填充的顏色 ctx.closePath(); //閉合路徑(自動補全) ctx.stroke(); //顯示線(繪製線),可以繪製的路徑顯示出來 ctx.fill(); //填充顏色
只有矩形有快捷方法,比如想繪製多邊形,都要用以上這些組合。
2.2繪製矩形
ctx.fillRect(x,y,w,h); //繪製填充矩形 ctx.strokeRect(x,y,w,h); //繪製路徑矩形
繪製調色板:
var canvas = document.querySelector('canvas'); var ctx = canvas.getContext("2d"); for (var i = 0;i < 6;i++){ for (var j = 0;j < 6;j++){ ctx.fillStyle = 'rgba('+ Math.floor(255-42.5 * i) +','+ Math.floor(255-42.5 * j) +', 200)'; ctx.fillRect(i * 25, j * 25, 25, 25); } }
記住一句話:Canvas是不可逆,繪製的元素一旦上了屏幕,是無法針對它再次操作。
2.3繪製弧度
ctx.arc(圓心x, 圓心y, 半徑, 開始的弧度, 結束的弧度, 是否逆時針); ctx.beginPath(); //開始繪製路徑 // ctx.arc(100, 100, 60, 0, 6.28, false); ctx.arc(100, 100, 60, 0, Math.PI * 2, false); ctx.stroke(); //顯示路徑線
切一個圓,讓切下來的弧邊長等於圓的半徑,此時弧對應的角度是57.3度左右,此時角度是固定的。
正方向是正右方
在canvas中所有涉及角度的坐標系有兩點註意的:
l 0弧度的方向是正右方向。
弧度的順時針和逆時針:
ctx.arc(100,100,60, 0, 3, false); //繪製圓弧
ctx.arc(100,100,60, 0, 1, true); //繪製圓弧
繪製圓形:
ctx.arc(100,100,60, 0, Math.PI * 2, false); ctx.arc(100,100,60, 0, 6.28, false); ctx.arc(100, 100, 60, 0, -6.28, true);
註意:x和y坐標是到圓心的位置,而且圓的大小是半徑,後面繪製的形狀會覆蓋前面的形狀。
繪製笑臉
<script type="text/javascript"> var canvas = document.querySelector("canvas"); var ctx = canvas.getContext("2d"); //繪製大臉 ctx.beginPath(); //開始繪製路徑 ctx.arc(300,200, 160, 0, 6.28, false); //繪製圓弧 ctx.stroke(); //顯示路徑線 ctx.fillStyle = "brown"; ctx.fill(); //繪製左眼睛 ctx.beginPath(); ctx.arc(230,150, 30, 0, 6.28, false); ctx.stroke(); ctx.fillStyle = "orange"; ctx.fill(); //繪製右眼睛 ctx.beginPath(); ctx.arc(370,150, 30, 0, 6.28, false); ctx.stroke(); ctx.fillStyle = "blue"; ctx.fill(); //繪製嘴巴 ctx.beginPath(); ctx.arc(300,160, 120, 0.5, 2.6, false); ctx.lineWidth = 10; ctx.stroke(); </script>繪製笑臉
三、使用圖片
3.1圖片基本使用
canvas中不可能所有形狀都自己畫,一定是設計師給我們素材,然後使用。
canvas中使用圖片的方法:註意,必須等img完全載入後才能呈遞圖片。
ctx.drawImage(); var canvas = document.querySelector('canvas'); var ctx = canvas.getContext("2d"); //創建一個img標簽 var image = new Image() //設置圖片的路徑 image.src = "images/baby1.jpg"; //當圖片成功載入,就畫圖(上屏幕) image.onload = function(){ //顯示圖片的API ctx.drawImage(image, 100, 100); //表示x和y坐標 }
3.2使用切片
如果2個數字參數,此時表示左上角位置的x和y坐標:
ctx.drawImage(img,100,100);
ctx.drawImage(img圖片對象,畫布X,畫布Y);
如果4個數字參數,此時表示x、y、w、h:
ctx.drawImage(img圖片對象, 畫布X,畫布Y,圖片W,圖片H);
如果8個數字參數,此時表示:
ctx.drawImage(img,切片X,切片Y,切片W,切片H,畫布X,畫布Y,圖片W,圖片H);
//創建一個img標簽 var image = new Image() //設置圖片的路徑 image.src = "images/baby1.jpg"; //當圖片成功載入,就畫圖(上屏幕) image.onload = function(){ //顯示圖片的API // ctx.drawImage(image, 100, 100); //表示x和y坐標 // ctx.drawImage(image, 100, 100, 150, 150); //表示x和y坐標 // ctx.drawImage(img,切片X,切片Y,切片W,切片H,畫布X,畫布Y,圖片W,圖片H); ctx.drawImage(image, 108, 200, 145, 120, 100, 100, 145, 120); }
圖片API:https://developer.mozilla.org/zh-CN/docs/Web/API/Canvas_API/Tutorial/Using_images
3.3簡易的圖片載入器
var canvas = document.querySelector('canvas'); var ctx = canvas.getContext("2d"); var R = { "0":"images/d1.jpg", "1":"images/d2.jpg", "2":"images/d3.jpg" } var arr = []; for(var k in R){ arr[k] = new Image(); //創建img對象 arr[k].src = R[k]; //設置圖片地址 // 當圖片成功載入,就畫圖(上屏幕) arr[k].onload = function(){ ctx.drawImage(arr[k], 50, 50) } }
3.4顯示GIF動態圖
HTML代碼:
<img id="testImg" src="xxx.gif" width="224" height="126"> <p><input type="button" id="testBtn" value="停止"></p>
if ('getContext' in document.createElement('canvas')) { HTMLImageElement.prototype.play = function() { if (this.storeCanvas) { // 移除存儲的canvas this.storeCanvas.parentElement.removeChild(this.storeCanvas); this.storeCanvas = null; // 透明度還原 image.style.opacity = ''; } if (this.storeUrl) { this.src = this.storeUrl; } }; HTMLImageElement.prototype.stop = function() { var canvas = document.createElement('canvas'); // 尺寸 var width = this.width, height = this.height; if (width && height) { // 存儲之前的地址 if (!this.storeUrl) { this.storeUrl = this.src; } // canvas大小 canvas.width = width; canvas.height = height; // 繪製圖片幀(第一幀) canvas.getContext('2d').drawImage(this, 0, 0, width, height); // 重置當前圖片 try { this.src = canvas.toDataURL("image/gif"); } catch(e) { // 跨域 this.removeAttribute('src'); // 載入canvas元素 canvas.style.position = 'absolute'; // 前面插入圖片 this.parentElement.insertBefore(canvas, this); // 隱藏原圖 this.style.opacity = '0'; // 存儲canvas this.storeCanvas = canvas; } } }; } var image = document.getElementById("testImg"), button = document.getElementById("testBtn"); if (image && button) { button.onclick = function() { if (this.value == '停止') { image.stop(); this.value = '播放'; } else { image.play(); this.value = '停止'; } }; }JavaScript代碼
3.5游戲圖片資源載入器
//得到畫布 var canvas = document.querySelector("canvas"); // 使用上下文,得到一個2D的畫布 var ctx = canvas.getContext("2d"); //資源文件 var R = { "d1" : "images/d1.jpg", "d2" : "images/d2.jpg", "d3" : "images/d3.jpg" } //遍歷這個對象,將他們的地址變為真實圖片地址 var count = 0; //已成功載入的圖片個數 var length = Object.keys(R).length; //所有圖片的總數 for(var k in R){ //創建image對象 var image = new Image(); //設置src圖片路徑 image.src = R[k]; //將R裡面的資源文件,變為真正的圖片對象 R[k] = image; //當image載入成功後,顯示圖片在畫布上 image.onload = function(){ count++; //當某張圖片載入成功,給計數器+1 ctx.clearRect(0,0,600,600) //繪製文本,提升用戶體驗,提示載入的進度 //填充文字API ctx.textAlign = "center"; ctx.font = "30px 微軟雅黑"; ctx.fillText("正在載入圖片:" + count + "/" + length, canvas.width / 2,50) //當載入完畢,開始游戲 if(count == length){ //開始游戲的回調函數 ctx.clearRect(0,0,600,600) start(); } } } // 開始游戲的函數 function start(){ ctx.drawImage(R["d1"],100,100); ctx.drawImage(R["d2"],0,100); ctx.drawImage(R["d3"],300,200); }游戲圖片載入器
四、畫布的變形
4.1 translate移動變形
translate()移動畫布,rotate()旋轉畫布。
canvas中不能只移動某一個對象,移動的都是整個畫布。
canvas中不能只旋轉某一個對象,旋轉的都是整個畫布。
但是可以用save()、restore()來巧妙設置,實現讓某一個元素進行移動和旋轉。
var canvas = document.querySelector('canvas'); var ctx = canvas.getContext("2d"); ctx.translate(100, 100); //將畫布移動,坐標系就發生變化了 ctx.fillRect(100, 100, 100, 100); //相對於移動後的坐標系開始畫畫
移動變形、移動的是整個畫布、而不是某個元素,在ctx.translate()之後繪製的語句都將被影響。
var canvas = document.querySelector('canvas'); var ctx = canvas.getContext("2d"); ctx.translate(100, 100); //將畫布移動,坐標系就發生變化了 ctx.fillRect(100, 100, 100, 100); //相對於移動後的坐標系開始畫畫 ctx.beginPath(); ctx.arc(100,100, 100, 0, 6.28, false); ctx.fillStyle = 'skyblue'; ctx.fill();
4.2 save()保存和restore()恢復
ctx.save()表示保存上下文的物理性質,ctx.restore()表示恢復最近一次的保存。
save表示保存sava函數之前的狀態,restore表示獲取save保存的狀態。
移動了的元素,會影響不需要移動圓點坐標的元素,所以可以使用以上兩個方法保存起來,可以解決讓某一個元素移動變形不受影響。
var canvas = document.querySelector('canvas'); var ctx = canvas.getContext("2d"); ctx.save(); ctx.translate(100, 100); //將畫布移動,坐標系就發生變化了 ctx.fillRect(100, 100, 100, 100); //相對於移動後的坐標系開始畫畫 ctx.restore(); ctx.beginPath(); ctx.arc(100,100, 100, 0, 6.28, false); ctx.fillStyle = 'skyblue'; ctx.fill();
4.3 rotate()旋轉變形
旋轉的是整個坐標系,坐標系以0,0點為中心點進行旋轉。
rotate(1)的參數,是弧度,旋轉的也不是矩形,而是畫布。
var canvas = document.querySelector("canvas"); var ctx = canvas.getContext("2d"); ctx.rotate(1); //1表示57.3度(1弧度) ctx.fillRect(100, 100, 100, 100); //相對於旋轉後的坐標系開始畫畫
如果想旋轉某一個元素,必須將坐標軸原點,放到要旋轉的元素身上,然後再旋轉。
ctx.save(); ctx.translate(150,150) ctx.rotate(1); //1表示57.3度(1弧度) ctx.fillRect(-50, -50, 100, 100); //相對於旋轉後的坐標系開始畫畫 ctx.restore();
坐標系移動到物體的中心點,物體以負半寬、半高、為x,y繪製。
function Box(){ this.x = 150; this.y = 150; this.w = 100; this.h = 100; this.deg = 0; } Box.prototype.render = function(){ ctx.save() ctx.translate(this.x,this.y) ctx.rotate(this.deg); ctx.fillRect(-this.w / 2,-this.h / 2,this.w,this.h); ctx.restore() } Box.prototype.update = function(){ this.deg += 0.2; } var b = new Box(); b.render(); setInterval(function(){ ctx.clearRect(0,0,600,400) b.update(); b.render(); },20);
globalCompositeOperation
用來設置新圖像和老圖形如何“融合”、“裁剪”。
值有以下這些:
新圖形是:source,老圖形是destination
ctx.globalCompositeOperation="destination-over";
五、FlappyBird游戲
5.1游戲結構
游戲採用中介者模式開發,Game類統領全局,負責讀取資源、設置定時器、維護各種演員的實例,也就是說所有的演員都是Game類new出來,當做一個子屬性。
也就是,游戲項目外部就一條語句:
var game = new Game();
其他的所有語句都寫在Game類裡面。
需要的類:
Game類: 中介者,讀取資源、設置定時器、維護各種演員的實例
Bird類: 小鳥類,這個類是單例的,實例化一次
Pipe類: 管子類
Land類: 大地類
background類: 背景類
<body> <canvas width="414" height="650"></canvas> </body> <script type="text/javascript" src="js/lib/underscore-min.js"></script> <script type="text/javascript" src="js/Game.js"></script> <script type="text/javascript" src="js/Bird.js"></script> <script type="text/javascript" src="js/Land.js"></script> <script type="text/javascript" src="js/Pipe.js"></script> <script type="text/javascript" src="js/Background.js"></script>
5.2創建Game類:開始界面、載入資源
(function(){ window.Game = function() { this.f = 0; //幀編號 this.init();//初始化DOM } Game.prototype.init = function() { this.canvas = document.getElementById("canvas"); this.ctx = this.canvas.getContext("2d"); //R對象表示資源文件,圖片總數 this.R = { "bg_day": "images/bg_day.png", "land": "images/land.png", "pipe_down": "images/pipe_down.png", "pipe_up": "images/pipe_up.png", "bird0_0": "images/bird0_0.png", "bird0_1": "images/bird0_1.png", "bird0_2": "images/bird0_2.png", } var self = this; //遍歷對象用for in語句 //遍歷這個對象,將它們變為真的圖片地址 var count = 0; //計算載入好的圖片總數(成功載入一張就+1) var length = Object.keys(this.R).length; //得到圖片的總數 for (var k in this.R) { //創建一個img標簽,發出圖片的請求,目前img對象是孤兒節點 var img = new Image(); //將這個R[k]對象賦值給src設置圖片的路徑 img.src = this.R[k]; //將R裡面的資源文件,改為img真的圖片對象 this.R[k] = img; //當圖片載入完畢,就畫圖上畫布(圖片必須load才能上畫布) img.onload = function () { count++; //當某張圖片載入完畢,給計數器+1 //清屏 self.clear() //繪製文本,提升用戶載入到什麼程度了 //save和restore方法配合使用,防止污染其他樣式 self.ctx.save(); //保存狀態 self.ctx.textAlign = "center"; self.ctx.font = "18px 微軟雅黑"; self.ctx.fillStyle = "blue"; //填充文字 self.ctx.fillText(`載入中 ${count} / ${length}`, self.canvas.width / 2, 100); self.ctx.restore(); //恢復保存的狀態 //當載入完畢的圖片總數==圖片總數時,此時就開始載入圖片並開始游戲 if (count == length) { self.start(); //開始游戲的回調函數 } } } } //清屏 Game.prototype.clear = function() { this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height) } // 游戲主迴圈 Game.prototype.start = function () { var self = this; this.timer = setInterval(function(){ self.f++; // 清屏 self.clear(); //顯示幀率 self.ctx.font = "16px 微軟雅黑"; self.ctx.fillText(self.f,10,20