requestAnimationFrame是瀏覽器用於定時循環操作的一個介面,類似於setTimeout,主要用途是按幀對網頁進行重繪。
設定這個API的目的是為了讓各種網頁動畫效果(DOM動畫、Canvas動畫、SVG動畫、WebGL動畫)能夠有一個統一的刷新機制,從而節省系統資源,提高系統性能,改善視覺效果。程式碼中使用這個API,就是告訴瀏覽器希望執行一個動畫,讓瀏覽器在下一個動畫影格安排一次網頁重繪。
requestAnimationFrame的優勢,在於充分利用顯示器的刷新機制,比較節省系統資源。顯示器有固定的刷新頻率(60Hz或75Hz),也就是說,每秒最多只能重繪60次或75次,requestAnimationFrame的基本想法就是與這個刷新頻率保持同步,利用這個刷新頻率進行頁面重繪。此外,使用這個API,一旦頁面不處於瀏覽器的目前標籤,就會自動停止刷新。這就節省了CPU、GPU和電力。
不過有一點要注意,requestAnimationFrame是在主執行緒上完成。這意味著,如果主線程非常繁忙,requestAnimationFrame的動畫效果會大打折扣。
requestAnimationFrame使用一個回呼函數作為參數。這個回呼函數會在瀏覽器重繪之前呼叫。
requestID = window.requestAnimationFrame(callback);
目前,高版本瀏覽器Firefox 23 / IE 10 / Chrome / Safari)都支援這個方法。可以用下面的方法,檢查瀏覽器是否支援這個API。如果不支持,則自行模擬部署該方法。
window.requestAnimFrame = (function(){ return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function( callback ){ window.setTimeout(callback, 1000 / 60); }; })();
上面的程式碼依照1秒鐘60次(大約每16.7毫秒一次),來模擬requestAnimationFrame。
使用requestAnimationFrame的時候,只需重複呼叫它即可。
function repeatOften() { // Do whatever requestAnimationFrame(repeatOften); } requestAnimationFrame(repeatOften);
取消重繪可以用 cancelAnimationFrame。
window.cancelAnimationFrame(requestID);
它的參數是requestAnimationFrame傳回的一個代表任務ID的整數值。
判斷瀏覽器是否支援canvas,並將寬高設定為瀏覽器視窗大小。
var canvas = document.getElementById("myCanvas"); if (!canvas.getContext) { return; } canvas.width = window.innerWidth; canvas.height = window.innerHeight;var ctx = canvas.getContext("2d");
煙火效果可以簡單地認為是圍繞一個點,爆炸產生很多小球向邊上擴散。因此需要一個煙火對象,這個對象主要是記錄煙火綻放的位置和周圍小球的資訊。所以我們的煙火物件定義如下。
function FireWork() { this.x = -1; this.y = -1; this.balls = []; }
那這個物件應該有哪些方法呢?
首先,要建立爆炸產生的小球。
createBalls: function () { for (var i = 0; i < 300; i++) { var angle = Math.random() * Math.PI * 2, radius = getRandom(50, 200); this.balls.push(new Ball(fwx, fwy, fwx + Math.cos(angle) * radius, fwy + Math.sin(angle) * radius)); } }
註:這裡fwx為煙火位置X軸座標,fwy為煙火位置Y軸座標,下同。
這裡小球的運行長度為 50 到200 的隨機值,小球運行軌跡起點為煙火位置,終點在一個圓上隨機的一點。
然後,要對煙火進行初始化,主要是確定位置,產生小球。
init: function () { this.x = getRandom(200, width - 200); this.y = getRandom(200, height - 200); fwx = this.x; fwy = this.y; this.createBalls(); drawCount = 0; currBallIndex = 0; }
註:這裡drawCount為繪製次數,currBallIndex為目前繪製的小球索引。
整個FireWork定義如下。
function FireWork() { this.x = -1; this.y = -1; this.balls = []; } FireWork.prototype = { init: function () { this.x = getRandom(200, width - 200); this.y = getRandom(200, height - 200); fwx = this.x; fwy = this.y; this.createBalls(); drawCount = 0; currBallIndex = 0; }, run: function () { this.init(); }, createBalls: function () { for (var i = 0; i < 300; i++) { var angle = Math.random() * Math.PI * 2, radius = getRandom(50, 200); this.balls.push(new Ball(fwx, fwy, fwx + Math.cos(angle) * radius, fwy + Math.sin(angle) * radius)); } } }
#小球需要知道自己的起點和終點的位置,所以定義如下。
function Ball(bx, by, ex, ey) { this.bx = bx;//起点X轴坐标 this.by = by;//起点Y轴坐标 this.ex = ex;//终点X轴坐标 this.ey = ey;//终点Y轴坐标}
小球還要能根據目前繪製的次數和總繪製次數計算得到當前座標和下一次繪製座標,這兩個座標連接起來的直線就是本次要繪製的內容,所以定義如下。
Ball.prototype = { getSpan: function () { var xSpan = (this.ex - this.bx) / allDrawCount, ySpan = (this.ey - this.by) / allDrawCount; return { x: xSpan, y: ySpan }; }, currPosition: function () { var span = this.getSpan(), currX = -1, currY = -1; if (drawCount < allDrawCount) { currX = this.bx + span.x * (drawCount - 1); currY = this.by + span.y * (drawCount - 1); return { x: currX, y: currY }; } return null; }, nextPosition: function () { var span = this.getSpan(), currX = -1, currY = -1; if (drawCount < allDrawCount) { currX = this.bx + span.x * drawCount; currY = this.by + span.y * drawCount; return { x: currX, y: currY }; } return null; } }
var fwx = -1, //烟花位置X轴坐标 fwy = -1, //烟花位置Y轴坐标 currFW = null, //烟花实例 currBallIndex = -1, //当前正在绘制的小球索引 drawCount = 0, //绘制次数 allDrawCount = 40, //总共需要的绘制次数 width = canvas.width, //画布宽度 height = canvas.height; //画布高度
然後還要幾個工具方法。
function componentToHex(c) { var hex = c.toString(16); return hex.length == 1 ? "0" + hex : hex; }function rgbToHex(r, g, b) { return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b); }function getRandom(minNum, maxNum) { var iChoices = maxNum - minNum + 1; return Math.floor(Math.random() * iChoices + minNum); }
#最後剩下一個供 requestAnimationFrame 呼叫的繪製方法。這個繪製方法就是根據目前的繪製次數,拿到爆炸小球的路徑(包含起點和終點的一條線段),然後把上一次繪製的路徑擦除。
當一個煙火的效果繪製完成後,進行下一個煙火的繪製。
function drawLine(span) { if (currFW && currBallIndex !== -1) { if (drawCount <= allDrawCount) { ctx.save(); drawCount++; for (var i = 0, j = currFW.balls.length; i < j; i++) { var currBall = currFW.balls[i], beginPoint = currBall.currPosition(), endPoint = currBall.nextPosition(); if (beginPoint && endPoint) { console.log(currBallIndex, drawCount, currBall, beginPoint, endPoint); ctx.beginPath(); ctx.moveTo(currBall.bx, currBall.by); ctx.lineTo(beginPoint.x, beginPoint.y); ctx.strokeStyle = "#000"; ctx.stroke(); ctx.beginPath(); ctx.moveTo(beginPoint.x, beginPoint.y); ctx.lineTo(endPoint.x, endPoint.y); var r = getRandom(0, 255); var g = getRandom(0, 255); var b = getRandom(0, 255); ctx.strokeStyle = rgbToHex(r, g, b); ctx.stroke(); } else { ctx.beginPath(); ctx.moveTo(currBall.bx, currBall.by); ctx.lineTo(currBall.ex, currBall.ey); ctx.strokeStyle = "#000"; ctx.stroke(); } } currBallIndex++; currBallIndex %= currFW.balls.length; ctx.restore(); } else { ctx.clearRect(0, 0, width, height); currFW = new FireWork(); currFW.run(); } } requestAnimationFrame(drawLine); }
這裡顏色取的是隨機值。
最後就是啟動繪製。
currFW = new FireWork(); currFW.run(); requestAnimationFrame(drawLine);
全部程式碼如下,共160行。
1 (function () { 2 var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame || function (callback) { 3 return window.setTimeout(callback, 1000 / 60); 4 }; 5 window.requestAnimationFrame = requestAnimationFrame; 6 })(); 7 8 var canvas = document.getElementById("myCanvas"); 9 if (!canvas.getContext) { 10 return; 11 } 12 canvas.width = window.innerWidth; 13 canvas.height = window.innerHeight; 14 15 var ctx = canvas.getContext("2d"); 16 17 var fwx = -1, 18 fwy = -1, 19 currFW = null, 20 currBallIndex = -1, 21 drawCount = 0, 22 allDrawCount = 40, 23 width = canvas.width, 24 height = canvas.height; 25 26 function componentToHex(c) { 27 var hex = c.toString(16); 28 return hex.length == 1 ? "0" + hex : hex; 29 } 30 31 function rgbToHex(r, g, b) { 32 return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b); 33 } 34 35 function getRandom(minNum, maxNum) { 36 var iChoices = maxNum - minNum + 1; 37 return Math.floor(Math.random() * iChoices + minNum); 38 } 39 40 function drawLine(span) { 41 if (currFW && currBallIndex !== -1) { 42 if (drawCount <= allDrawCount) { 43 ctx.save(); 44 drawCount++; 45 for (var i = 0, j = currFW.balls.length; i < j; i++) { 46 var currBall = currFW.balls[i], 47 beginPoint = currBall.currPosition(), 48 endPoint = currBall.nextPosition(); 49 if (beginPoint && endPoint) { 50 console.log(currBallIndex, drawCount, currBall, beginPoint, endPoint); 51 ctx.beginPath(); 52 ctx.moveTo(currBall.bx, currBall.by); 53 ctx.lineTo(beginPoint.x, beginPoint.y); 54 ctx.strokeStyle = "#000"; 55 ctx.stroke(); 56 ctx.beginPath(); 57 ctx.moveTo(beginPoint.x, beginPoint.y); 58 ctx.lineTo(endPoint.x, endPoint.y); 59 var r = getRandom(0, 255); 60 var g = getRandom(0, 255); 61 var b = getRandom(0, 255); 62 ctx.strokeStyle = rgbToHex(r, g, b); 63 ctx.stroke(); 64 } else { 65 ctx.beginPath(); 66 ctx.moveTo(currBall.bx, currBall.by); 67 ctx.lineTo(currBall.ex, currBall.ey); 68 ctx.strokeStyle = "#000"; 69 ctx.stroke(); 70 } 71 } 72 currBallIndex++; 73 currBallIndex %= currFW.balls.length; 74 ctx.restore(); 75 } else { 76 ctx.clearRect(0, 0, width, height); 77 currFW = new FireWork(); 78 currFW.run(); 79 } 80 } 81 requestAnimationFrame(drawLine); 82 } 83 84 function FireWork() { 85 this.x = -1; 86 this.y = -1; 87 this.balls = []; 88 } 89 90 FireWork.prototype = { 91 init: function () { 92 this.x = getRandom(200, width - 200); 93 this.y = getRandom(200, height - 200); 94 fwx = this.x; 95 fwy = this.y; 96 this.createBalls(); 97 drawCount = 0; 98 currBallIndex = 0; 99 }, 100 run: function () { 101 this.init(); 102 }, 103 createBalls: function () { 104 for (var i = 0; i < 300; i++) { 105 var angle = Math.random() * Math.PI * 2, 106 radius = getRandom(50, 200); 107 this.balls.push(new Ball(fwx, fwy, fwx + Math.cos(angle) * radius, fwy + Math.sin(angle) * radius)); 108 } 109 } 110 } 111 112 function Ball(bx, by, ex, ey) { 113 this.bx = bx; 114 this.by = by; 115 this.ex = ex; 116 this.ey = ey; 117 } 118 119 Ball.prototype = { 120 getSpan: function () { 121 var xSpan = (this.ex - this.bx) / allDrawCount, 122 ySpan = (this.ey - this.by) / allDrawCount; 123 return { 124 x: xSpan, 125 y: ySpan 126 }; 127 }, 128 currPosition: function () { 129 var span = this.getSpan(), 130 currX = -1, 131 currY = -1; 132 if (drawCount < allDrawCount) { 133 currX = this.bx + span.x * (drawCount - 1); 134 currY = this.by + span.y * (drawCount - 1); 135 return { 136 x: currX, 137 y: currY 138 }; 139 } 140 return null; 141 }, 142 nextPosition: function () { 143 var span = this.getSpan(), 144 currX = -1, 145 currY = -1; 146 if (drawCount < allDrawCount) { 147 currX = this.bx + span.x * drawCount; 148 currY = this.by + span.y * drawCount; 149 return { 150 x: currX, 151 y: currY 152 }; 153 } 154 return null; 155 } 156 } 157 158 currFW = new FireWork(); 159 currFW.run(); 160 requestAnimationFrame(drawLine);
歡迎討論。
以上是HTML5 Canvas實戰之實現煙火效果的程式碼案例的詳細內容。更多資訊請關注PHP中文網其他相關文章!