HTML5 Canvasでフラクタル雪片を実行する

サイトを雪で装飾するための、HTML5 Canvasでの大Year日のエンターテインメント(Canvasの動作を確認するための興味深い例です)。

私の話では Giorgio Sardo コードに基づいて構築します 。これは、David Flanaganコードに基づいています



以下で説明するすべてのことは、JavaScriptコンソールを起動するだけで、開発ツールを備えた最新のブラウザーでHabréで直接試すことができます。 IE9では、F12キーを押すだけで、このページで直接テストする場合は、ブラウザーをInternet Explorer 9 Standard(Alt + 9)モードにすることを忘れないでください。 デフォルトでは、HabrにはIE8モードが必要です。

Canvasサポートの確認



まず、ブラウザーがCanvasをサポートしていることを確認することから始める必要があります。そのためには、Canvasエレメントを作成し、作業のコンテキストを取得する必要があります。

if ( document .createElement( 'canvas' ).getContext) {
...
}
else {
...
}


最初のケースでは、先に進んで雪片を開始できます。

キャンバスを作成する



雪片を描くために、フルスクリーンでキャンバス(キャンバス)を作成します:

var canvas = document .createElement( 'canvas' );
canvas.style.position = 'fixed' ;
canvas.style.top = '0px' ;
canvas.style.left = '0px' ;
canvas.style.zIndex = '-10' ;
canvas.width = document .body.offsetWidth;
canvas.height = window.innerHeight;

document .body.insertBefore(canvas, document .body.firstChild);


この場合、新しいCanvas要素を作成し、固定された場所を指定して、残りの要素と干渉しないように配置しようとします。

次に、レンダリングのコンテキストを取得します。

var sky = canvas.getContext( '2d' );


コッホスノーフレーク



habrayuzersにとって、 コッホ・スノーフレークが何であるかはよく知られているべきだと思うので、私は写真に自分自身を制限します:



この作品はフラクタルで、便利に再帰的に描かれています。 三角形を描くには、同じ描画パターンを各エッジに順番に適用する必要があります。



1本の線を引くことから始めましょう。 便利なレンダリングの場合、各ローカルレンダリングが水平な直線の描画のように見える変換 (スケーリングと回転)を適用できます。 つまり、線の描画を回転させるのではなく、コンテキストを拡大縮小して回転させます(変換行列を変更します)。

変換マトリックスの状態を保存および復元するには、それぞれsave()およびrestore()関数を使用します。

作業中に、度をラジアンに変換する必要があります(ただし、必要に応じて、すぐにラジアンで記述できます)。

var deg = Math.PI / 180;


1つのエッジを描く再帰関数は次のようになります。

function leg(n, len) {
sky.save(); //
if (n == 0) { // -
sky.lineTo(len, 0); }
else {
sky.scale(1 / 3, 1 / 3); // 3
leg(n – 1, len); sky.rotate(60 * deg);
leg(n – 1, len); sky.rotate(-120 * deg);
leg(n – 1, len); sky.rotate(60 * deg); leg(n – 1, len); }
sky.restore(); //
sky.translate(len, 0); //
}


レンダリングを開始するには、次の関数を使用できます。

function drawFlake(x, y, len, n, stroke, fill) {
sky.save(); sky.strokeStyle = stroke;
sky.fillStyle = fill;
sky.beginPath();
sky.translate(x, y);
sky.moveTo(0, 0); leg(n, len); sky.closePath();
sky.fill();
sky.stroke();
sky.restore();
}


描画するには、パスの作成を開始する必要があることに注意してください。必要に応じて、パスを閉じてから、領域を塗りつぶして線を描画する必要があることを伝えます。 結果:



対応する回転でさらにいくつかのエッジを追加すると、雪片が得られます。

function drawFlake(x, y, len, n, stroke, fill) {
sky.save(); sky.strokeStyle = stroke;
sky.fillStyle = fill;
sky.beginPath();
sky.translate(x, y);
sky.moveTo(0, 0); leg(n, len); sky.rotate(-120 * deg);
leg(n, len); sky.rotate(-120 * deg);
leg(n, len);
sky.closePath();
sky.fill();
sky.stroke();
sky.restore();
}


結果:



雪片の作成と移動



さらに、アイデアは非常に透明です。1)タイマーに雪片を追加して雪片のプールを作成し、2)タイマーで雪片の位置を変更して描画します。

雪片の追加


ランダムな値、雪片の配列、最大数の追加機能。 タイマーを設定します。

var rand = function (n) { return Math.floor(n * Math.random()); }
var flakes = []; var maxflakes = 20;
var snowspeed = 500;
var snowingTimer = setInterval(createSnowflake, snowspeed);


そして実際には、雪片の作成(適切なタイミングで、タイマーをクリーニングすることで新しい雪片の作成を停止します):

function createSnowflake() {
var order = 3;
var size = 10 + rand(50);
var x = rand( document .body.offsetWidth);
var y = window.pageYOffset;

flakes.push({ x: x, y: y, vx: 0, vy: 3 + rand(3), size: size, order: order, stroke: "#99f" , fill: "transparent" });
if (flakes.length > maxflakes) clearInterval(snowingTimer);
}


雪片の移動


画面サイズを変更するとtrueに設定される追加の変数invalidateMeasureが表示されます。 タイマーを設定して、位置と実際の移動機能を更新します(画面をクリアし、位置を更新->雪片を描画)。

var scrollspeed = 64;
setInterval(moveSnowflakes, scrollspeed);

function moveSnowflakes() {
sky.clearRect(0, 0, canvas.width, canvas.height);

var maxy = canvas.height;

for ( var i = 0; i < flakes.length; i++) {
var flake = flakes[i];
flake.y += flake.vy;
flake.x += flake.vx;

if (flake.y > maxy) flake.y = 0;
if (invalidateMeasure) {
flake.x = rand(canvas.width);
}

drawFlake(flake.x, flake.y, flake.size, flake.order, flake.stroke, flake.fill);

//
if (rand(4) == 1) flake.vx += (rand(11) - 5) / 10;
if (flake.vx > 2) flake.vx = 2;
if (flake.vx < -2) flake.vx = -2;
}
if (invalidateMeasure) invalidateMeasure = false ;
}


最終コード



さらに、雪片のランダムな回転と雪片のランダムな色+サイズに応じた詳細など、いくつかの詳細を追加できます。

( function () {
if ( document .createElement( 'canvas' ).getContext) {
if ( document .readyState === 'complete' )
snow();
else
window.addEventListener( 'DOMContentLoaded' , snow, false );
}
else {
return ;
}

var deg = Math.PI / 180;
var maxflakes = 20; var flakes = []; var scrollspeed = 64; var snowspeed = 500;
var canvas, sky;
var snowingTimer;
var invalidateMeasure = false ;

var strokes = [ "#6cf" , "#9cf" , "#99f" , "#ccf" , "#66f" , "#3cf" ];

function rand (n) {
return Math.floor(n * Math.random());
}

//
function snow() {
canvas = document .createElement( 'canvas' );
canvas.style.position = 'fixed' ;
canvas.style.top = '0px' ;
canvas.style.left = '0px' ;
canvas.style.zIndex = '-10' ;

document .body.insertBefore(canvas, document .body.firstChild);
sky = canvas.getContext( '2d' );

ResetCanvas();

snowingTimer = setInterval(createSnowflake, snowspeed);
setInterval(moveSnowflakes, scrollspeed);
window.addEventListener( 'resize' , ResetCanvas, false );
}

// Canvas
function ResetCanvas() {
invalidateMeasure = true ;
canvas.width = document .body.offsetWidth;
canvas.height = window.innerHeight;
}

//
function leg(n, len) {
sky.save(); //
if (n == 0) { // -
sky.lineTo(len, 0); }
else {
sky.scale(1 / 3, 1 / 3); // 3
leg(n - 1, len); sky.rotate(60 * deg);
leg(n - 1, len); sky.rotate(-120 * deg);
leg(n - 1, len); sky.rotate(60 * deg); leg(n - 1, len); }
sky.restore(); //
sky.translate(len, 0); //
}

//
function drawFlake(x, y, angle, len, n, stroke, fill) {
sky.save(); sky.strokeStyle = stroke;
sky.fillStyle = fill;
sky.beginPath();
sky.translate(x, y);
sky.moveTo(0, 0); sky.rotate(angle);
leg(n, len);
sky.rotate(-120 * deg);
leg(n, len); sky.rotate(-120 * deg);
leg(n, len); sky.closePath();
sky.fill();
sky.stroke();
sky.restore();
}

//
function createSnowflake() {
var order = 2+rand(2);
var size = 10*order+rand(10);
var x = rand( document .body.offsetWidth);
var y = window.pageYOffset;
var stroke = strokes[rand(strokes.length)];

flakes.push({ x: x, y: y, vx: 0, vy: 3 + rand(3), angle:0, size: size, order: order, stroke: stroke, fill: 'transparent' });

if (flakes.length > maxflakes) clearInterval(snowingTimer);
}

//
function moveSnowflakes() {
sky.clearRect(0, 0, canvas.width, canvas.height);

var maxy = canvas.height;

for ( var i = 0; i < flakes.length; i++) {
var flake = flakes[i];

flake.y += flake.vy;
flake.x += flake.vx;

if (flake.y > maxy) flake.y = 0;
if (invalidateMeasure) {
flake.x = rand(canvas.width);
}

drawFlake(flake.x, flake.y, flake.angle, flake.size, flake.order, flake.stroke, flake.fill);

//
if (rand(4) == 1) flake.vx += (rand(11) - 5) / 10;
if (flake.vx > 2) flake.vx = 2;
if (flake.vx < -2) flake.vx = -2;
if (rand(3) == 1) flake.angle = (rand(13) - 6) / 271;
}
if (invalidateMeasure) invalidateMeasure = false ;
}
} ());


* This source code was highlighted with Source Code Highlighter .


コードをコピーし、コンソールから実行して、サイトで降雪を取得します。

Source: https://habr.com/ru/post/J110237/


All Articles