花火効果を実現するための HTML5 Canvas の実践的なコード ケース

黄舟
リリース: 2017-03-30 13:22:29
オリジナル
4221 人が閲覧しました

1. 効果

花火効果を実現するための HTML5 Canvas の実践的なコード ケース

2. コード分析

(1) requestAnimationFrame

requestAnimationFrame は、Web のループ操作をタイミングするために使用されるインターフェイスです。フレームごとにページが描かれています。

この API を設定する目的は、さまざまな Web ページのアニメーション効果 (DOM アニメーション、Canvas アニメーション、SVG アニメーション、WebGL アニメーション) に統一された更新メカニズムを持たせることにより、システム リソースを節約し、システム パフォーマンスを向上させ、視覚効果を向上させることです。 。コード内でこの API を使用すると、アニメーションを実行したいことをブラウザーに伝え、ブラウザーに次のアニメーション フレームで Web ページの再描画をスケジュールさせます。

requestAnimationFrame の利点は、表示の更新メカニズムを最大限に活用し、システム リソースを節約できることです。ディスプレイには固定のリフレッシュ周波数 (60 Hz または 75 Hz) があり、1 秒あたり最大 60 回または 75 回しか再描画できないことを意味します。requestAnimationFrame の基本的な考え方は、このリフレッシュ周波数との同期を維持し、このリフレッシュ周波数を使用することです。ページを再描画します。さらに、この API を使用すると、ページがブラウザの現在のタブに表示されなくなると、ページの更新が自動的に停止されます。これにより、CPU、GPU、電力が節約されます。

ただし、注意すべき点が 1 つあります。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);
              };
})();
ログイン後にコピー

上記のコードは、requestAnimationFrame を 1 秒あたり 60 回 (約 16.7 ミリ秒に 1 回) シミュレートします。

requestAnimationFrameを使用する場合は、繰り返し呼び出すだけです。

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

requestAnimationFrame(repeatOften);
ログイン後にコピー

再描画のキャンセルはcancelAnimationFrameで行えます。

window.cancelAnimationFrame(requestID);
ログイン後にコピー

そのパラメータは、requestAnimationFrame によって返されるタスク ID を表す

integer 値です。

(2) キャンバスを準備します

ブラウザがキャンバスをサポートしているかどうかを確認し、幅と高さをブラウザのウィンドウサイズに設定します。

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 は現在描画されているボール

index です。

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轴坐标}
ログイン後にコピー

ボールも現在のドロー数と総ドロー数に基づいて現在の座標と次の描画座標を計算できる必要があります。この2つの座標を結ぶ直線が今回描画する内容になります。それは次のように定義されます。

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 が呼び出す描画メソッドが 1 つ残っています。この描画方法は、現在の描画回数から爆発するボールの軌跡(始点と終点を含む線分)を取得し、最後に描画した軌跡を消去するというものです。

1つの花火のエフェクトを引いたら、次の花火を描きます。

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);
ログイン後にコピー

議論することを歓迎します。

以上が花火効果を実現するための HTML5 Canvas の実践的なコード ケースの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート