站長資訊網
最全最豐富的資訊網站

HTML5 Canvas實戰之實現煙花效果的代碼案例


1、效果

HTML5 Canvas實戰之實現煙花效果的代碼案例

2、代碼解析

(1)requestAnimationFrame

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的整數值。

(2)準備畫版(canvas)

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

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

(3)煙花對象(FireWork)

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

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

(4)爆炸產生的小球對象(Ball)

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

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

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

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

(6)繪制方法

最后還剩一個供 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);  }

這里顏色取的是隨機值。

(7)啟動繪制

最后就是啟動繪制。

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)
網站地圖   滬ICP備18035694號-2    滬公網安備31011702889846號
免费看国产精品麻豆| 亚洲av无码成人精品国产| 在线观看亚洲精品福利片 | 精品无码人妻一区二区三区18| 精品人妻系列无码一区二区三区 | 久久99热精品这里久久精品| 中文字幕精品一区二区三区视频| jazzjazz国产精品一区二区| 日韩一区二区久久久久久| 国产精品爆乳在线播放第一人称 | 亚洲国产精品碰碰| 日韩精品系列产品| 免费精品国产自产拍观看| 国产精品亚洲а∨天堂2021| 精品国产av一二三四区| 国产精品亚洲专区无码牛牛| 在线精品视频一区二区| 99热亚洲色精品国产88| 精品国产一二三产品价格| 亚洲精品人成电影网| 久久99国产综合精品女同| 91精品国产综合久久青草| 99热这里只有精品7| 国产精品禁18久久久夂久| 日本精品不卡视频| 国产精品看高国产精品不卡| 亚洲AV无码精品无码麻豆| 午夜精品乱人伦小说区| 熟女精品视频一区二区三区| 日韩国产成人精品视频| 99久久久国产精品免费牛牛四川| 日韩精品无码一区二区三区不卡| 99精品国产在热久久婷婷| 久久久久成人精品一区二区| 色一乱一伦一图一区二区精品| 日韩精品久久久肉伦网站| 精品偷自拍另类在线观看| 久久久久se色偷偷亚洲精品av| 国产精品蜜芽在线观看| 九九精品久久久久久噜噜| 人妻无码久久精品人妻|