站長(zhǎng)資訊網(wǎng)
最全最豐富的資訊網(wǎng)站

HTML5 Canvas實(shí)戰(zhàn)之實(shí)現(xiàn)煙花效果的代碼案例


1、效果

HTML5 Canvas實(shí)戰(zhàn)之實(shí)現(xiàn)煙花效果的代碼案例

2、代碼解析

(1)requestAnimationFrame

requestAnimationFrame是瀏覽器用于定時(shí)循環(huán)操作的一個(gè)接口,類似于setTimeout,主要用途是按幀對(duì)網(wǎng)頁(yè)進(jìn)行重繪。

設(shè)置這個(gè)API的目的是為了讓各種網(wǎng)頁(yè)動(dòng)畫效果(DOM動(dòng)畫、Canvas動(dòng)畫、SVG動(dòng)畫、WebGL動(dòng)畫)能夠有一個(gè)統(tǒng)一的刷新機(jī)制,從而節(jié)省系統(tǒng)資源,提高系統(tǒng)性能,改善視覺效果。代碼中使用這個(gè)API,就是告訴瀏覽器希望執(zhí)行一個(gè)動(dòng)畫,讓瀏覽器在下一個(gè)動(dòng)畫幀安排一次網(wǎng)頁(yè)重繪。

requestAnimationFrame的優(yōu)勢(shì),在于充分利用顯示器的刷新機(jī)制,比較節(jié)省系統(tǒng)資源。顯示器有固定的刷新頻率(60Hz或75Hz),也就是說,每秒最多只能重繪60次或75次,requestAnimationFrame的基本思想就是與這個(gè)刷新頻率保持同步,利用這個(gè)刷新頻率進(jìn)行頁(yè)面重繪。此外,使用這個(gè)API,一旦頁(yè)面不處于瀏覽器的當(dāng)前標(biāo)簽,就會(huì)自動(dòng)停止刷新。這就節(jié)省了CPU、GPU和電力。

不過有一點(diǎn)需要注意,requestAnimationFrame是在主線程上完成。這意味著,如果主線程非常繁忙,requestAnimationFrame的動(dòng)畫效果會(huì)大打折扣。

requestAnimationFrame使用一個(gè)回調(diào)函數(shù)作為參數(shù)。這個(gè)回調(diào)函數(shù)會(huì)在瀏覽器重繪之前調(diào)用。

requestID = window.requestAnimationFrame(callback);

目前,高版本瀏覽器Firefox 23 / IE 10 / Chrome / Safari)都支持這個(gè)方法。可以用下面的方法,檢查瀏覽器是否支持這個(gè)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的時(shí)候,只需反復(fù)調(diào)用它即可。

function repeatOften() {  // Do whatever  requestAnimationFrame(repeatOften);  }    requestAnimationFrame(repeatOften);

取消重繪可以用 cancelAnimationFrame。

window.cancelAnimationFrame(requestID);

它的參數(shù)是requestAnimationFrame返回的一個(gè)代表任務(wù)ID的整數(shù)值。

(2)準(zhǔn)備畫版(canvas)

判斷瀏覽器是否支持canvas,并把寬高設(shè)置為瀏覽器窗口大小。

var canvas = document.getElementById("myCanvas");  if (!canvas.getContext) {    return;  }  canvas.width = window.innerWidth;  canvas.height = window.innerHeight;var ctx = canvas.getContext("2d");

(3)煙花對(duì)象(FireWork)

煙花效果可以簡(jiǎn)單地認(rèn)為是圍繞一個(gè)點(diǎn),爆炸產(chǎn)生很多小球向邊上擴(kuò)散。因此需要一個(gè)煙花對(duì)象,這個(gè)對(duì)象主要是記錄煙花綻放的位置和周圍小球的信息。所以我們的煙花對(duì)象定義如下。

function FireWork() {      this.x = -1;      this.y = -1;      this.balls = [];  }

那這個(gè)對(duì)象應(yīng)該有哪些方法呢?

首先,要?jiǎng)?chuàng)建爆炸產(chǎn)生的小球。

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軸坐標(biāo),fwy為煙花位置Y軸坐標(biāo),下同。

這里小球的運(yùn)行長(zhǎng)度為 50 到200 的隨機(jī)值,小球運(yùn)行軌跡起點(diǎn)為煙花位置,終點(diǎn)在一個(gè)圓上隨機(jī)的一點(diǎn)。

然后,要對(duì)煙花進(jìn)行初始化,主要是確定位置,產(chǎn)生小球。

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為繪制次數(shù),currBallIndex為當(dāng)前繪制的小球索引。

整個(gè)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));          }      }  }

(4)爆炸產(chǎn)生的小球?qū)ο?Ball)

小球需要知道自己的起點(diǎn)和終點(diǎn)的位置,所以定義如下。

function Ball(bx, by, ex, ey) {    this.bx = bx;//起點(diǎn)X軸坐標(biāo)      this.by = by;//起點(diǎn)Y軸坐標(biāo)      this.ex = ex;//終點(diǎn)X軸坐標(biāo)      this.ey = ey;//終點(diǎn)Y軸坐標(biāo)}

小球還要能根據(jù)當(dāng)前繪制的次數(shù)和總繪制次數(shù)計(jì)算得到當(dāng)前坐標(biāo)和下一次繪制坐標(biāo),這兩個(gè)坐標(biāo)連接起來的直線就是本次要繪制的內(nèi)容,所以定義如下。

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;      }  }

(5)全局變量及工具方法

var fwx = -1,                 //煙花位置X軸坐標(biāo)      fwy = -1,                 //煙花位置Y軸坐標(biāo)      currFW = null,            //煙花實(shí)例      currBallIndex = -1,       //當(dāng)前正在繪制的小球索引      drawCount = 0,            //繪制次數(shù)      allDrawCount = 40,        //總共需要的繪制次數(shù)      width = canvas.width,     //畫布寬度      height = canvas.height;   //畫布高度

然后還要幾個(gè)工具方法。

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);  }

(6)繪制方法

最后還剩一個(gè)供 requestAnimationFrame 調(diào)用的繪制方法。這個(gè)繪制方法就是根據(jù)當(dāng)前的繪制次數(shù),拿到爆炸小球的路徑(包含起點(diǎn)和終點(diǎn)的一條線段),然后把上一次繪制的路徑擦除。

當(dāng)一個(gè)煙花的效果繪制完成后,進(jìn)行下一個(gè)煙花的繪制。

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);  }

這里顏色取的是隨機(jī)值。

(7)啟動(dòng)繪制

最后就是啟動(dòng)繪制。

currFW = new FireWork();  currFW.run();  requestAnimationFrame(drawLine);

(8)全部代碼。

全部代碼如下,共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);

歡迎討論。

贊(0)
分享到: 更多 (0)
網(wǎng)站地圖   滬ICP備18035694號(hào)-2    滬公網(wǎng)安備31011702889846號(hào)
国产精品久久久久久影视| 亚洲精品电影在线| 免费精品国产自产拍在线观看| 国产69精品久久久久久久| 国产成人精品免费午夜app| 久久免费观看国产99精品| 亚洲精品国产字幕久久不卡| 亚洲精品无码AV中文字幕电影网站| 日韩精品在线视频| 国产成人一区二区动漫精品| 日本加勒比在线精品视频| 青青草原综合久久大伊人精品| 99国产精品国产精品九九| 1313午夜精品久久午夜片| 一区二区三区国产精品| 日韩欧国产精品一区综合无码| 日本道免费精品一区二区| 精品国产一区二区三区在线观看| 色老成人精品视频在线观看| 日韩精品一卡2卡3卡4卡新区乱码 日韩精品一线二线三线优势 | 99精品国产在热久久| 好湿好大硬得深一点动态图91精品福利一区二区| 日韩午夜免费视频| 日韩毛片免费在线观看| 老湿机一区午夜精品免费福利| 精品一区二区三区在线视频观看 | 午夜精品福利在线| 国产乱码精品一区二区三区 | 国产精品先锋资源站先锋影院 | 亚洲精品宾馆在线精品酒店| 国产精品青草久久久久婷婷 | 久久久一本精品99久久精品36| 无码精品尤物一区二区三区| 国产精品专区第二| 无码国产精品一区二区免费式直播| 国产在线精品香蕉麻豆| 囯产精品久久久久久久久久妞妞 | segui久久综合精品| 一本大道无码日韩精品影视| 亚洲а∨天堂久久精品| 国产成人高清精品免费观看|