📜 ⬆️ ⬇️

Launch fractal snowflakes on HTML5 Canvas

Pre-Christmas entertainment on HTML5 Canvas to decorate the site with snowflakes (well, just an interesting example to see how Canvas works).

In my story, I will build on the Giorgio Sardo code , which in turn is based on the David Flanagan code .


')
Everything that is described below, you can try directly here, on Habré in any modern browser with development tools, simply by running the JavaScript console. In IE9, just press F12 and, if you want to test directly on this page, do not forget to switch the browser to Internet Explorer 9 Standards (Alt + 9), since by default, Habr requires IE8 mode.

Check Canvas Support



First of all, you need to start with the fact that you need to make sure that the browser supports Canvas, for this you need to create a Canvas element and try to get to the work context:

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


In the first case, you can move on and run the snowflakes.

Create canvas



To draw snowflakes, we will create a canvas (full canvas):

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


In this case, we create a new Canvas element and assign it a fixed location, trying to place it in such a way that it does not fit the other elements.

Next we get the context to draw:

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


Koch's snowflake



I think habrauders should be well aware of what the Koch Snowflake is , so I will limit myself to a picture:



This fractal thing is conveniently drawn recursively. To draw a triangle, you need to apply the same drawing pattern to each of its edges:



Let's start by trying to draw one line. When drawing, which is convenient, we can apply transformations (scaling and rotations), at which each local drawing will look like a drawing of a straight horizontal line. That is, we scale and rotate the context (change the transformation matrix) instead of turning the line drawing.

To save and restore the state of the transformation matrix, the save () and restore () functions are used, respectively.

In the course of work, we will need to convert degrees to radians (although if you wish, you can write immediately in radians):

var deg = Math.PI / 180;


The recursive function for drawing one edge looks like this:

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


To start drawing you can use this function:

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


Please note that for drawing you need to start creating a path, if necessary, close it and only then say what needs to be done to fill the areas and draw the lines. Result:



If we add a few more edges with the corresponding turns, we get a snowflake:

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


Result:



Creating and moving snowflakes



Then the idea is pretty transparent: 1) create a pool of snowflakes by hanging the addition of snowflakes on the timer, 2) change the position of the snowflakes by the timer and draw it.

Adding Snowflakes


Additional function for random values, an array of snowflakes and the maximum number. Set the timer:

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


And the creation of snowflakes itself (at the right moment, the creation of new snowflakes stops cleaning the timer):

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


Moving snowflakes


Here an additional variable invalidateMeasure appears, which is set to true when the screen size is changed. We set the timer to update the position and the actual movement function (clear the screen, update the position -> draw snowflakes).

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


Final code



Then you can add a few more details: a random turn of the snowflake and a random color of the snowflake + detailing depending on the size:

( 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 .


Copy the code, run from the console and get snowfall on the site.

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


All Articles