📜 ⬆️ ⬇️

Making a greeting card on March 8 in HTML5 and EaselJS



(the picture is clickable and congratulatory)

The general idea : flying butterflies on the background of a beautiful picture and spring music. ( I confess at once that I also had a Silverlight version of a postcard that I did a year ago. A lesson on the animation of butterflies in Silverlight . )
')
In addition to the standard CSS tools, we will use HTML5 Canvas and the EaselJS animation library to render the postcard .

Butterfly Animation



We will start with the assumption that you have already prepared butterflies and that you have separate wings in the form of corresponding pictures:



Step 1. Drawing the Wings

Let's first draw a couple of wings. To draw each of the wings, we need a Bitmap class, whose objects we will create based on the loaded images. To put the wings together, we need the Container class . To draw a scene in EaselJS there is a class Stage .

Loading images
To upload images, you need to use the standard Image by hanging the corresponding events on it:

// loading left wing
leftImg = new Image();
leftImg.src = leftSrc;
leftImg.onload = onWingImageLoaded;

// loading right wing
rightImg = new Image();
rightImg.src = rightSrc;
rightImg.onload = onWingImageLoaded;


(Ideally, you should also hang up events for an error in the download and handle it correctly.)

Butterfly assembly
After both wings are loaded, you need to collect them in one container, which can be operated as a whole:

// create a new container for butterfly wings
var butterfly = new Container();
// create wings
var lWing = new Bitmap(leftImg);
var rWing = new Bitmap(rightImg);

// assemble a batterfly
butterfly.addChild(lWing);
butterfly.addChild(rWing);


For each of the wings inside the container, you need to move the registration point relative to which they will be positioned and further rotate:

// change wings registration point inside container
lWing.regX = lWing.image.width;
lWing.regY = lWing.image.height / 2;
rWing.regX = 0;
rWing.regY = rWing.image.height / 2;


Butterfly placement
Next, add a butterfly to the stage, place it in the right place and start the scene drawing:

// add butterfly to stage
stage.addChild(butterfly);

// change batterfly position
butterfly.x = canvas.width * Math.random() | 0;
butterfly.y = canvas.height * Math.random() | 0;

// redraw stage
stage.update();


Common code
var leftSrc = "../images/Cypres 1.png" ;
var rightSrc = "../images/Cypres 2.png" ;
var leftImg;
var rightImg;
var loadedWings = 0;

function init() {
// create a new stage and point it at our canvas:
canvas = document .getElementById( "canvas" );
stage = new Stage(canvas);

// loading left wing
leftImg = new Image();
leftImg.src = leftSrc;
leftImg.onload = onWingImageLoaded;

// loading right wing
rightImg = new Image();
rightImg.src = rightSrc;
rightImg.onload = onWingImageLoaded;
}

function onWingImageLoaded( event ) {
loadedWings++;
if (loadedWings == 2)
onButterflyReady();
}

function onButterflyReady() {
// create a new container for butterfly wings
var butterfly = new Container();
// create wings
var lWing = new Bitmap(leftImg);
var rWing = new Bitmap(rightImg);

// assemble a batterfly
butterfly.addChild(lWing);
butterfly.addChild(rWing);

// change wings registration point inside container
lWing.regX = lWing.image.width;
lWing.regY = lWing.image.height / 2;
rWing.regX = 0;
rWing.regY = rWing.image.height / 2;


// add butterfly to stage
stage.addChild(butterfly);

// change batterfly position
butterfly.x = canvas.width * Math.random() | 0;
butterfly.y = canvas.height * Math.random() | 0;

// redraw stage
stage.update();
}




Ready example, you can find here: example 1 .

Step 2. Wing our wings

Timer
Run animations or sequentially occurring events in various ways. In EaselJS, you can subscribe to the scene update timer and make changes at each step. (This is similar to the game state update cycle, in which scene rendering occurs on a timer.)

First you need to subscribe to the timer (the response frequency can also be set):

Ticker.addListener(window);


Next, you need to define the tick function in the windows scope, inside which you need to update the state of the scene and start the drawing:

// update scene on each tick
function tick() {
butterfly.move()
stage.update();
}


Step by Step Wing Animation
To describe the animation of a butterfly, you need to break all the wings of the wings and move into separate steps. But first, let's set another parameter defining the initial state of the butterfly, the initial scale (in principle, you can not set it if you do not plan to make butterflies of different sizes, then scale = 1.0):

// initial scale
butterfly.scale = butterfly.lWing.scaleX
= butterfly.lWing.scaleY = butterfly.rWing.scaleX
= butterfly.rWing.scaleY = 0.5 + 0.2 * Math.random();


Wing animations are well done through horizontal (X) scaling, and for periodicity, you can use trigonometric functions.

Let's set the number of animation steps:

// animation steps
butterfly.step = butterfly.steps = 40 + 60 * Math.random();


It now remains to describe each stroke:

butterfly.move = function () {
var wingAngle = (butterfly.steps - butterfly.step) / butterfly.steps * Math.PI;
butterfly.lWing.scaleX = butterfly.rWing.scaleX
= butterfly.scale * (0.4 + 0.6 * Math.abs(Math.cos(wingAngle * 4)));

butterfly.step--;
}


Common code
var leftSrc = "../images/Cypres 1.png" ;
var rightSrc = "../images/Cypres 2.png" ;
var leftImg;
var rightImg;
var loadedWings = 0;
var butterfly;
var butterflyLoaded = false ;

function init() {
// create a new stage and point it at our canvas:
canvas = document .getElementById( "canvas" );
stage = new Stage(canvas);

// loading left wing
leftImg = new Image();
leftImg.src = leftSrc;
leftImg.onload = onWingImageLoaded;

// loading right wing
rightImg = new Image();
rightImg.src = rightSrc;
rightImg.onload = onWingImageLoaded;

// subscribe to Ticker
Ticker.addListener(window);
}

function onWingImageLoaded( event ) {
loadedWings++;
if (loadedWings == 2)
onButterflyReady();
}

function onButterflyReady() {
// create a new container for butterfly wings
butterfly = new Container();
// create wings
butterfly.lWing = new Bitmap(leftImg);
butterfly.rWing = new Bitmap(rightImg);

// assemble a batterfly
butterfly.addChild(butterfly.lWing);
butterfly.addChild(butterfly.rWing);

// change wings registration point inside container
butterfly.lWing.regX = butterfly.lWing.image.width;
butterfly.lWing.regY = butterfly.lWing.image.height / 2;
butterfly.rWing.regX = 0;
butterfly.rWing.regY = butterfly.rWing.image.height / 2;


// add butterfly to stage
stage.addChild(butterfly);

// change batterfly position
butterfly.x = canvas.width * Math.random() | 0;
butterfly.y = canvas.height * Math.random() | 0;

// initial rotation
butterfly.lWing.rotation = butterfly.rWing.rotation
= butterfly.angle = 360 * Math.random() | 0;

// initial scale
butterfly.scale = butterfly.lWing.scaleX
= butterfly.lWing.scaleY = butterfly.rWing.scaleX
= butterfly.rWing.scaleY = 0.5 + 0.2 * Math.random();

// animation steps
butterfly.step = butterfly.steps = 40 + 60 * Math.random();

// move butterfly
butterfly.move = function () {
var wingAngle = (butterfly.steps - butterfly.step) / butterfly.steps * Math.PI;
butterfly.lWing.scaleX = butterfly.rWing.scaleX
= butterfly.scale * (0.4 + 0.6 * Math.abs(Math.cos(wingAngle * 4)));

butterfly.step--;
}

butterflyLoaded = true ;

// redraw stage
stage.update();
}

// update scene on each tick
function tick() {
if (butterflyLoaded && butterfly.step >= 0) {
butterfly.move()
}
stage.update();
}




Ready example, you can find here: example 2 .

Step 3. Move the butterfly

Now that we have taught the butterfly to flap its wings, let's get it moving. To do this, we will need to take into account the angle of rotation and the direction and speed of movement:

// initial rotation
butterfly.lWing.rotation = butterfly.rWing.rotation
= butterfly.angle = 360 * Math.random() | 0;
// movement direction
butterfly.direction = 8 * Math.random() - 4.0;
// movement speed
butterfly.speed = 5 * Math.random() + 5;


Inside the butterfly.move function, it remains to add a change in the angle of rotation:

butterfly.angle += butterfly.direction + (5.0 * Math.random() - 2.5);
butterfly.lWing.rotation = butterfly.rWing.rotation = butterfly.angle;


and increase in movement:

butterfly.x += (butterfly.speed * Math.sin((butterfly.angle) / 180.0 * Math.PI));
butterfly.y -= (butterfly.speed * Math.cos((butterfly.angle) / 180.0 * Math.PI));


The butterfly should fly.



Ready example, you can find here: example 3 .

Step 4. We make the code

Since we will need to process the whole collection of butterflies, it is logical to select the code describing the butterfly's work in a separate block (the added reaction to the mouse is also highlighted in blue):

( function (window) {

Butterfly = function (leftImg, rightImg) {
this .initialize();
this .initButterfly(leftImg, rightImg);
}

var p = Butterfly.prototype = new Container();

p.lWing = null ;
p.rWing = null ;
p.angle = 0;
p.direction = 0;
p.speed = 0;

p.steps = 0;
p.step = -1;

p.initButterfly = function (leftImg, rightImg) {
// create wings
this .lWing = new Bitmap(leftImg);
this .rWing = new Bitmap(rightImg);

// change wings registration point inside container
this .lWing.regX = this .lWing.image.width;
this .lWing.regY = this .lWing.image.height / 2;
this .rWing.regX = 0;
this .rWing.regY = this .rWing.image.height / 2;

// initial rotation
this .lWing.rotation = this .rWing.rotation = this .angle = 360 * Math.random() | 0;

// initial scale
this .scale = this .lWing.scaleX = this .lWing.scaleY = this .rWing.scaleX = this .rWing.scaleY = 0.5 + 0.2 * Math.random();

// assembling batterfly
this .addChild( this .lWing);
this .addChild( this .rWing);

this .mouseEnabled = true ;
this .onMouseOver = function (e) { this .reset(); };
};

p.reset = function () {
// animation steps
this .step = this .steps = 40 + 60 * Math.random();
// movement direction
this .direction = 8 * Math.random() - 4.0;
// movement speed
this .speed = 5 * Math.random() + 5;
};

p.move = function () {
// update rotation
this .angle += this .direction + (5.0 * Math.random() - 2.5);
this .lWing.rotation = this .rWing.rotation = this .angle;

// update wings
var wingAngle = ( this .steps - this .step) / this .steps * Math.PI;
this .lWing.scaleX = this .rWing.scaleX = this .scale * (0.4 + 0.6 * Math.abs(Math.cos(wingAngle * 4)));

// update position
this .x += ( this .speed * Math.sin(( this .angle) / 180.0 * Math.PI));
this .y -= ( this .speed * Math.cos(( this .angle) / 180.0 * Math.PI));
this .step--;
};

p.isActive = function () {
return this .step >= 0;
};

window.Butterfly = Butterfly;
} (window));


The remaining piece of code is simplified a bit, you also need to enable tracking of mouse events:

var leftSrc = "../images/Cypres 1.png" ;
var rightSrc = "../images/Cypres 2.png" ;
var leftImg;
var rightImg;
var loadedWings = 0;
var butterfly;
var butterflyLoaded = false ;

function init() {
// create a new stage and point it at our canvas:
canvas = document .getElementById( "canvas" );
stage = new Stage(canvas);
stage.enableMouseOver(10);

// loading left wing
leftImg = new Image();
leftImg.src = leftSrc;
leftImg.onload = onWingImageLoaded;

// loading right wing
rightImg = new Image();
rightImg.src = rightSrc;
rightImg.onload = onWingImageLoaded;

// subscribe to Ticker
Ticker.addListener(window);
}

function onWingImageLoaded( event ) {
loadedWings++;
if (loadedWings == 2)
onButterflyImagesReady();
}

function onButterflyImagesReady() {
// create a new container for butterfly wings
var butterfly = new Butterfly(leftImg, rightImg);

// add butterfly to stage
stage.addChild(butterfly);

// change batterfly position
butterfly.x = canvas.width * Math.random() | 0;
butterfly.y = canvas.height * Math.random() | 0;

butterfly.reset();
butterflyLoaded = true ;

// redraw stage
stage.update();
}

// update scene on each tick
function tick() {
if (butterflyLoaded && butterfly.step >= 0) {
butterfly.move()
}
stage.update();
}


Ready example, you can find here: example 4 .

Lots and lots of butterflies


Now let's add more butterflies. We will need an array of wing image addresses:

var bfimgsrc = [{ left: "../images/Didius 1.png" , right: "../images/Didius 2.png" },
{ left: "../images/Amphitrion 1.png" , right: "../images/Amphitrion 2.png" },
{ left: "../images/Catenarius 1.png" , right: "../images/Catenarius 2.png" },
{ left: "../images/Cyanides 1.png" , right: "../images/Cyanides 2.png" },
{ left: "../images/Cypres 1.png" , right: "../images/Cypres 2.png" },
{ left: "../images/Diana 1.png" , right: "../images/Diana 2.png" },
{ left: "../images/Hecuba 1.png" , right: "../images/Hecuba 2.png" },
{ left: "../images/Peleides 1.png" , right: "../images/Peleides 2.png" },
{ left: "../images/Polyphemus 1.png" , right: "../images/Polyphemus 2.png" },
{ left: "../images/Sulkowski 1.png" , right: "../images/Sulkowski 2.png" }];


Creating the butterflies will be moved to a separate function loadButterflyis and we will launch more butterflies by mouse click:

function init() {
// create a new stage and point it at our canvas:
canvas = document .getElementById( "canvas" );
stage = new Stage(canvas);
stage.enableMouseOver(10);

loadButterflyis(18);

canvas.onclick = function () {
loadButterflyis(5);
};

// subscribe to Ticker
Ticker.addListener(window);
}

function loadButterflyis(count) {
for ( var k = 0; k < count; k++) {
var i = Math.floor(bfimgsrc.length * Math.random());

var bfimages = {
left: new Image(),
right: new Image(),
loadedWings: 0,
onready: onButterflyImagesReady
};

// loading left wing
bfimages.left.src = bfimgsrc[i].left;
bfimages.left.onload = onWingImageLoaded;
bfimages.left.butterfly = bfimages;

// loading right wing
bfimages.right.src = bfimgsrc[i].right;
bfimages.right.onload = onWingImageLoaded;
bfimages.right.butterfly = bfimages;
}
}


Updated tick :

// update scene on each tick
function tick() {
for ( var i = 0; i < bfs.length; i++) {
var butterfly = bfs[i];
if (butterfly.isActive()) {
butterfly.move();
}
else if (Math.random() > 0.999) {
butterfly.reset();
}
}
stage.update();
}




Ready example, you can find here: example 5 .


Special effects


Additional effects will be pretty simple. Themed background:



And the audio on the background:

< audio src ="audio/ArrivalForest.mp3" autoplay loop />

(Mogul enthusiasts easily add support for other audio formats.)

The final version is also available .

Upd.


I solve the problem with hosting, laid out temporarily on the test parking and separately: intermediate examples and the final version in one file - narod.ru/disk/6923480001/March8.zip.html

Source code was highlighted with Source Code Highlighter .

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


All Articles