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

        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號
        主站蜘蛛池模板: 亚洲国产精品国产自在在线| 精品久久久久香蕉网| 一本大道无码日韩精品影视| 97精品久久天干天天天按摩| 亚洲精品永久在线观看| 国产成人高清精品一区二区三区| 国产精品无码无需播放器| 亚洲国产精品成人久久蜜臀| 国产精品成人观看视频网站| 国产99久久精品一区二区| 午夜不卡久久精品无码免费| 欧美人与性动交α欧美精品成人色XXXX视频 | 日本精品卡一卡2卡3卡四卡| 日本午夜精品一区二区三区电影| 久久狠狠一本精品综合网| 91精品国产成人网在线观看| 国产精品一久久香蕉国产线看| 无码人妻精品一区二区三区66| 久久99精品久久久久久水蜜桃| 国产精品女同一区二区久久| 亚洲国产精品久久| 亚洲国产精品久久久久久| 精品国产污污免费网站| 国产乱码精品一区二区三区中文| 午夜精品久久久久久毛片| 亚洲精品人成无码中文毛片| 男人的天堂精品国产一区| 国产乱子伦精品免费视频| 国产精品一区二区三区免费| 国产精品∧v在线观看| 亚洲欧洲国产日韩精品| 久久精品人人做人人爽电影| 国产精品久久久久9999| 国产精品爽黄69天堂a| 国产精品视频一区二区三区| 国内精品久久九九国产精品| 九九在线精品视频专区| 午夜精品视频在线观看| 国产福利精品在线观看| 国产精品欧美亚洲韩国日本久久 | 久久99国产综合精品|