⬆️ ⬇️

Animation and Canvas

My hands got to the Canvas. I looked at it for a long time, I really liked it as a tool for charts. And week Canvas on Habré has supported interest.



But instead of boring graphs, I began to dig in the direction of animation. It turned out that we draw the scene every time in a new way, respectively, all the information about current frames is stored in JS. And I decided to try to create a simple algorithm that would allow to store animations for the object, their state and choice by the user.



Canvas



Everything is simple and standard.

<! DOCTYPE html >

< html >

< head >

< title > Animation test </ title >

< meta http-equiv ="Content-Type" content ="text/html; charset=UTF-8" >

</ head >

< body >

< canvas id ="cnv" width ="600" height ="200" > It's not working! </ canvas >

< select id ="animations" name ="animations" onchange ="changeAnimation(this.value)" >

< option value ="stop" > Stop </ option >

< option value ="jump" > Jump </ option >

< option value ="bum" > Bum </ option >

< option value ="dead" > Dead </ option >

</ select >

</ body >

</ html >


* This source code was highlighted with Source Code Highlighter .

<! DOCTYPE html >

< html >

< head >

< title > Animation test </ title >

< meta http-equiv ="Content-Type" content ="text/html; charset=UTF-8" >

</ head >

< body >

< canvas id ="cnv" width ="600" height ="200" > It's not working! </ canvas >

< select id ="animations" name ="animations" onchange ="changeAnimation(this.value)" >

< option value ="stop" > Stop </ option >

< option value ="jump" > Jump </ option >

< option value ="bum" > Bum </ option >

< option value ="dead" > Dead </ option >

</ select >

</ body >

</ html >


* This source code was highlighted with Source Code Highlighter .

<! DOCTYPE html >

< html >

< head >

< title > Animation test </ title >

< meta http-equiv ="Content-Type" content ="text/html; charset=UTF-8" >

</ head >

< body >

< canvas id ="cnv" width ="600" height ="200" > It's not working! </ canvas >

< select id ="animations" name ="animations" onchange ="changeAnimation(this.value)" >

< option value ="stop" > Stop </ option >

< option value ="jump" > Jump </ option >

< option value ="bum" > Bum </ option >

< option value ="dead" > Dead </ option >

</ select >

</ body >

</ html >


* This source code was highlighted with Source Code Highlighter .




A Select has been added under the canvas with a list of animations for the object. I will have one object, although the algorithm does not imply any restrictions.



Global variables


We now turn to JS. Here I will need some variables for storing scene objects and possible animations.

//

function $(id) {

return document .getElementById(id);

}



var tx = $( 'cnv' ).getContext( '2d' ); //

var childs = {}; //

var animate = {}; //


* This source code was highlighted with Source Code Highlighter .

//

function $(id) {

return document .getElementById(id);

}



var tx = $( 'cnv' ).getContext( '2d' ); //

var childs = {}; //

var animate = {}; //


* This source code was highlighted with Source Code Highlighter .

//

function $(id) {

return document .getElementById(id);

}



var tx = $( 'cnv' ).getContext( '2d' ); //

var childs = {}; //

var animate = {}; //


* This source code was highlighted with Source Code Highlighter .




')

All objects in the scene with basic parameters will be stored in childs. We will go over the array and draw one by one.

In animate store for the object of its animation. Here you can do it differently, for example, in animate, we store animations only for objects that need them, and for others, we store information on drawing in the childs array itself. In my version, each object must have at least one animation if it wants to be displayed on the canvas.



Fill arrays


First add our object. I did not write the parameters, because I think that for the sake of brevity of the code you can go for this, and the names are all intuitive.



function initAnimation() {



//

childs[ 'ball' ] = {

'at' : 'jump' , //

'w' : 30, //

'h' : 30, //

'fw' : 30, //

'x' : 100, //

'y' : 100 //

}



//

animate[ 'ball' ] = {

'jump' : { //

'el' : null , // Image

'src' : 'images/ball.png' , //

'step' : 0, //

'speed' : 3, //

'curr' : 0, //

'steps' : 3, // , 0

'onend' : null //

},

'bum' : {

'el' : null ,

'src' : 'images/ball_m.png' ,

'step' : 0,

'speed' : 3,

'curr' : 0,

'steps' : 7,

'onend' : 'onBumEnd'

},

'stop' : {

'el' : null ,

'src' : 'images/ball_s.png' ,

'step' : 0,

'speed' : 10,

'curr' : 0,

'steps' : 0,

'onend' : null

},

'dead' : {

'el' : null ,

'src' : 'images/ball_d.png' ,

'step' : 0,

'speed' : 10,

'curr' : 0,

'steps' : 0,

'onend' : null

}

}



//

for ( var o in childs) {



//

for ( var a in animate[o]) {



//

var img = new Image();

img.src = animate[o][a].src;

//

animate[o][a].el = img;



}



}



}


* This source code was highlighted with Source Code Highlighter .

function initAnimation() {



//

childs[ 'ball' ] = {

'at' : 'jump' , //

'w' : 30, //

'h' : 30, //

'fw' : 30, //

'x' : 100, //

'y' : 100 //

}



//

animate[ 'ball' ] = {

'jump' : { //

'el' : null , // Image

'src' : 'images/ball.png' , //

'step' : 0, //

'speed' : 3, //

'curr' : 0, //

'steps' : 3, // , 0

'onend' : null //

},

'bum' : {

'el' : null ,

'src' : 'images/ball_m.png' ,

'step' : 0,

'speed' : 3,

'curr' : 0,

'steps' : 7,

'onend' : 'onBumEnd'

},

'stop' : {

'el' : null ,

'src' : 'images/ball_s.png' ,

'step' : 0,

'speed' : 10,

'curr' : 0,

'steps' : 0,

'onend' : null

},

'dead' : {

'el' : null ,

'src' : 'images/ball_d.png' ,

'step' : 0,

'speed' : 10,

'curr' : 0,

'steps' : 0,

'onend' : null

}

}



//

for ( var o in childs) {



//

for ( var a in animate[o]) {



//

var img = new Image();

img.src = animate[o][a].src;

//

animate[o][a].el = img;



}



}



}


* This source code was highlighted with Source Code Highlighter .

function initAnimation() {



//

childs[ 'ball' ] = {

'at' : 'jump' , //

'w' : 30, //

'h' : 30, //

'fw' : 30, //

'x' : 100, //

'y' : 100 //

}



//

animate[ 'ball' ] = {

'jump' : { //

'el' : null , // Image

'src' : 'images/ball.png' , //

'step' : 0, //

'speed' : 3, //

'curr' : 0, //

'steps' : 3, // , 0

'onend' : null //

},

'bum' : {

'el' : null ,

'src' : 'images/ball_m.png' ,

'step' : 0,

'speed' : 3,

'curr' : 0,

'steps' : 7,

'onend' : 'onBumEnd'

},

'stop' : {

'el' : null ,

'src' : 'images/ball_s.png' ,

'step' : 0,

'speed' : 10,

'curr' : 0,

'steps' : 0,

'onend' : null

},

'dead' : {

'el' : null ,

'src' : 'images/ball_d.png' ,

'step' : 0,

'speed' : 10,

'curr' : 0,

'steps' : 0,

'onend' : null

}

}



//

for ( var o in childs) {



//

for ( var a in animate[o]) {



//

var img = new Image();

img.src = animate[o][a].src;

//

animate[o][a].el = img;



}



}



}


* This source code was highlighted with Source Code Highlighter .






I will explain a little. Each animation is a file with all frames. On the canvas, I bring only part of the image corresponding to the current frame. For this, I store step and steps (number of frames).



The image is as follows:

image

This is the largest image responsible for the longest “Bum” animation in 8 frames.



Speed ​​and curr are responsible for the frame switching speed. I redraw the canvas every 16ms and that the frames change with the required speed, I consider curr and when I reach speed I change the frame.



Onend I call at the end of the animation. Here we can change the type of animation - from the explosion to the display of the remains or remove an object from the array. For this, a little higher, I have a function:

function onBumEnd() {



//

childs[ 'ball' ].at = 'dead' ;

//

animate[ 'ball' ][childs[ 'ball' ].at].curr = 0;



}




* This source code was highlighted with Source Code Highlighter .

function onBumEnd() {



//

childs[ 'ball' ].at = 'dead' ;

//

animate[ 'ball' ][childs[ 'ball' ].at].curr = 0;



}




* This source code was highlighted with Source Code Highlighter .

function onBumEnd() {



//

childs[ 'ball' ].at = 'dead' ;

//

animate[ 'ball' ][childs[ 'ball' ].at].curr = 0;



}




* This source code was highlighted with Source Code Highlighter .






Animations


There are 4 animations for the object:





Function to change the animation


To conveniently change animations, we have Select and a function for it:

function changeAnimation(value) {



//

childs[ 'ball' ].at = value;

//

animate[ 'ball' ][childs[ 'ball' ].at].curr = 0;



}




* This source code was highlighted with Source Code Highlighter .

function changeAnimation(value) {



//

childs[ 'ball' ].at = value;

//

animate[ 'ball' ][childs[ 'ball' ].at].curr = 0;



}




* This source code was highlighted with Source Code Highlighter .

function changeAnimation(value) {



//

childs[ 'ball' ].at = value;

//

animate[ 'ball' ][childs[ 'ball' ].at].curr = 0;



}




* This source code was highlighted with Source Code Highlighter .




Run


So all the images and arrays are ready, and I run the animation.

function startAnimation() {



//

setInterval( function () {



//

ctx.save();

ctx.fillStyle = '#FFFFFF' ;

ctx.fillRect(0, 0, 600, 200);

ctx.restore();



//

for ( var o in childs) {



//

if (animate[o]) {



//

var step = animate[o][childs[o].at].step;



//

ctx.drawImage(

animate[o][childs[o].at].el, // Image

Math.round(childs[o].fw * step), // , *

0, // , 0

childs[o].w, //

childs[o].h, //

childs[o].x, //

childs[o].y, //

childs[o].w, //

childs[o].h //

);



// speed,

if (animate[o][childs[o].at].curr >= animate[o][childs[o].at].speed) {



//,

if (animate[o][childs[o].at].step >= animate[o][childs[o].at].steps) {



animate[o][childs[o].at].step = 0;



// , ,

if (animate[o][childs[o].at].onend)

window[animate[o][childs[o].at].onend]();



}

else animate[o][childs[o].at].step++;



//

animate[o][childs[o].at].curr = 0;



}



//

animate[o][childs[o].at].curr++;



}



}



}, 1000/16);



}




* This source code was highlighted with Source Code Highlighter .

function startAnimation() {



//

setInterval( function () {



//

ctx.save();

ctx.fillStyle = '#FFFFFF' ;

ctx.fillRect(0, 0, 600, 200);

ctx.restore();



//

for ( var o in childs) {



//

if (animate[o]) {



//

var step = animate[o][childs[o].at].step;



//

ctx.drawImage(

animate[o][childs[o].at].el, // Image

Math.round(childs[o].fw * step), // , *

0, // , 0

childs[o].w, //

childs[o].h, //

childs[o].x, //

childs[o].y, //

childs[o].w, //

childs[o].h //

);



// speed,

if (animate[o][childs[o].at].curr >= animate[o][childs[o].at].speed) {



//,

if (animate[o][childs[o].at].step >= animate[o][childs[o].at].steps) {



animate[o][childs[o].at].step = 0;



// , ,

if (animate[o][childs[o].at].onend)

window[animate[o][childs[o].at].onend]();



}

else animate[o][childs[o].at].step++;



//

animate[o][childs[o].at].curr = 0;



}



//

animate[o][childs[o].at].curr++;



}



}



}, 1000/16);



}




* This source code was highlighted with Source Code Highlighter .

function startAnimation() {



//

setInterval( function () {



//

ctx.save();

ctx.fillStyle = '#FFFFFF' ;

ctx.fillRect(0, 0, 600, 200);

ctx.restore();



//

for ( var o in childs) {



//

if (animate[o]) {



//

var step = animate[o][childs[o].at].step;



//

ctx.drawImage(

animate[o][childs[o].at].el, // Image

Math.round(childs[o].fw * step), // , *

0, // , 0

childs[o].w, //

childs[o].h, //

childs[o].x, //

childs[o].y, //

childs[o].w, //

childs[o].h //

);



// speed,

if (animate[o][childs[o].at].curr >= animate[o][childs[o].at].speed) {



//,

if (animate[o][childs[o].at].step >= animate[o][childs[o].at].steps) {



animate[o][childs[o].at].step = 0;



// , ,

if (animate[o][childs[o].at].onend)

window[animate[o][childs[o].at].onend]();



}

else animate[o][childs[o].at].step++;



//

animate[o][childs[o].at].curr = 0;



}



//

animate[o][childs[o].at].curr++;



}



}



}, 1000/16);



}




* This source code was highlighted with Source Code Highlighter .






In principle, I think from the comments it is clear what is following what and how it works. As a result, I was able to save the objects of the scene, dynamically set the animation and respond to its end.

Since often the objects in the animation are repeated, it makes sense to use childs as an array of objects, according to the principle of the library.

Of course there is something to improve, but at the moment I have not found a good article on the topic of animation and offer my version.



Complete code


You can see here .

Download here .

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



All Articles