intro__codes
intro__codes

Reputation: 182

Looping a frame with if statement with canvas and javascript

I'm building a photo frame with a infinite animation loop. It's a colored rectangle that goes on and on for each border. It's built using pure Javascript and the frame is drawn on canvas.

How can I recreate the effect permanently? I want this frame to go on and on forever.

var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');


 var objectSpeed = 8;

 var recTop = {
   x: -300,
    y: 0,
    width: 300,
    height: 20,
    isMoving: true

  };

 var recRight = {
     x: 480,
     y: -480,
    width: 20,
      height: 300,
    isMoving:false

 };

var recBottom = {
    x: 500,
   y: 480,
   width: 300,
    height: 20,
    isMoving:false

 };

 var recLeft = {
    x: 0,
    y: 500,
    width: 20,
    height: 300,
     isMoving:false

   };

 function drawRecTop(recTop, context) {
  context.beginPath();
   context.rect(recTop.x, recTop.y, recTop.width, recTop.height);
   context.fillStyle = '#FB0202';
   context.fill();

}


 function drawRecRight(recRight, context) {
     context.beginPath();
     context.rect(recRight.x , recRight.y , recRight.width, recRight.height);
      context.fillStyle = '#FB0202';
      context.fill();

    }

 function drawRecBottom(recBottom, context) {
     context.beginPath();
     context.rect(recBottom.x , recBottom.y , recBottom.width, recBottom.height);
     context.fillStyle = '#FB0202';
      context.fill();

}

  function drawRecLeft(recLeft, context) {
    context.beginPath();
    context.rect(recLeft.x , recLeft.y , recLeft.width, recLeft.height);
    context.fillStyle = '#FB0202';
     context.fill();

}


  var squareXSpeed = objectSpeed;
   var squareYSpeed = objectSpeed;


function animate(myRectangle, canvas, context, startTime) {


    if(recTop.x > canvas.width - recTop.width && !recRight.isMoving){
        //START RIGHT RECTANGLE MOVEMENT
       recRight.isMoving = true;
       recRight.y = - recRight.height + recRight.width;

    }



      if(recRight.y > canvas.height - recRight.height && !recBottom.isMoving){
        //START BOTTOM RECTANGLE MOVEMENT
       recBottom.isMoving = true;
       recBottom.x = canvas.width-recBottom.height;

     }



    if(recBottom.x < 0 && !recLeft.isMoving){
        //START LEFT RECTANGLE MOVEMENT
        // recLeft.y = - recLeft.width + recLeft.height;
          recLeft.isMoving = true;
         recLeft.y = canvas.height-recLeft.width;

      }


    if(recLeft.y < 0 && !recTop.isMoving){
        //START BOTTOM RECTANGLE MOVEMENT
       recTop.isMoving = true;
        recTop.x = -(canvas.width - recTop.width);
    }

    if(recTop.x > canvas.width && recLeft.isMoving){
       //TOP RECTANGLE HAS LEFT THE STAGE
       recTop.isMoving = false;

     }



    if(recTop.isMoving)recTop.x += objectSpeed;
     if(recRight.isMoving)recRight.y += objectSpeed;
     if(recBottom.isMoving)recBottom.x -= objectSpeed;
     if(recLeft.isMoving)recLeft.y -= objectSpeed;

// clear
context.clearRect(0, 0, canvas.width, canvas.height);

   drawRecTop(recTop, context);
   drawRecRight(recRight, context);
   drawRecBottom(recBottom, context);
    drawRecLeft(recLeft, context);


// request new frame
   requestAnimFrame(function() {
        animate(recLeft, canvas, context, startTime);
   });

}

Here's what I got so far: https://jsfiddle.net/jwp9ya5w/

Upvotes: 3

Views: 755

Answers (1)

Blindman67
Blindman67

Reputation: 54026

Chained animations on canvas.

Looking at your code and there is a problem in your logic. You have to reset all the animations when you loop around. Rather than point to the code that needs modification I will present a more flexible method of animating for the canvas.

Use events to chain together animation

Animations are not as simple as just play start to end, you need to chain animation depending on screen size, user interaction, and whatever. To do this you create events that check for a particular condition and call a function if true.

Flexible animations

Also you never really know on the onset what the animation will look and feel like. Many times the concept does not match the reality and you need to modify the animation. This can be time consuming if you have too much logic set in code.

To make modifying animations easier you can separate out all the various parts. There is rendering (what a particular animation item looks like), then there are the keyframes and the properties that define the keyframes. The timing functions that control the current state of the animation and check for events.

Animation item

A animation item represents one animation from start to end. Its has a start and end keyframe that have a set of properties that are animated. A update function that is generic to all animations takes the two keyframes and calculate the current frame. then it calls the items on the event list and fires any that need to be. There is a draw function that uses the current state to draw what is needed, and a start function that starts the animation by setting the start Time and resetting all the events.

This is an example of one animation item

    {
        animate : ["x","y","w","h"],// what to animate
        draw : drawFunc,            // function to draw
        update : animateFunc,       // function to set current anim state
        start : startFunc,          // function to start the animation
        startTime : null,           // this item's start time
        length : timePerSide,       // how long in seconds this animation is
        events : [{                 // list of events
                fired : false,      // true if fired ( to stop it from repeating)
                callback : null,    // callback to call when event fires
                // this function determines when to fire the event
                check : function(owner,time){
                    if(owner.current.x >= canvas.width - owner.current.w){
                        this.fired = true;
                        this.callback(time);
                    }
                },
            }
        ],
        current : {}, // defined when running holds the current state
        startKey : {  // the key frame defining the start position
            x : -boxLength,
            y : 0,
            w : boxLength,
            h : boxHeight,
        },
        endKey : {   // keyframe for end
            x : canvas.width,
            y : 0,
            w : boxLength,
            h : boxHeight,
        }
    },

It is easy to change the animation, by changing the keyframe settings. If you need to change the render you only need to change the draw function, if you need to add an extra animatable value you just add it to the start and end keyframes, list it in the animate array and change the draw function to include the new property.

The event is an array so can be many, and can be dependent on any type of condition. For your animation I have 4 animation items that move a box along the edge. Each items has an event that checks if the current box has reached the other side and calls the next items start function. this starts the next animation before the current has ended.

It may on the outside look a little more complex than your code, but using a system like this makes it very easy to make changes to the animation.

The snippet show this method used to create your animation. It has plenty of comments and can be adapted to your particular needs.

// create and add canvas
var createImage=function(w,h){var i=document.createElement("canvas");i.width=w;i.height=h;i.ctx=i.getContext("2d");return i;}
var canvas = createImage(200,200);
var ctx = canvas.ctx;
document.body.appendChild(canvas);




//==============================================================================
// common properties

const boxAnimProps = ["x","y","w","h"]; // List of keyframe properties to animate
const boxHeight = 10; // width of box if looking at it on the sides
const boxLength = 100; // length of box
const timePerSide = 2;  // time to animate each item


//==============================================================================
// common functions
// this function updates the time and check for events
const animateFunc = function(time){
    var i,prop;
    if(!this.started){
        return;
    }
    // set local (this item's) time 
    lTime = time - this.startTime;  // adjust to start time
    // set time clamped to start and end
    lTime = this.current.time = lTime < 0 ? 0 : lTime > this.length ? this.length: lTime;
    // normalise the time 0-1
    var nTime = lTime / this.length;
    for(i = 0; i < this.animate.length; i ++){
        prop = this.animate[i];
        this.current[prop] = (this.endKey[prop]-this.startKey[prop]) * nTime + this.startKey[prop];
    }
    // check for events
    for(i = 0; i < this.events.length; i ++){
        if(!this.events[i].fired){
            this.events[i].check(this,time);
        }
    }
};
// function starts an animtion item and resets any events it has
const startFunc = function(time){
    this.started = true;
    this.startTime = time;
    // reset events
    for(var i = 0;i < this.events.length; i ++){ 
        this.events[i].fired = false;
    }
}
// this function draws the item
const drawFunc = function(ctx){
    if(!this.started){
        return;
    }
    ctx.fillRect(this.current.x,this.current.y,this.current.w,this.current.h);
    
}



//==============================================================================
// the animation. Time is in seconds
// animation.start starts the animation
// animation.update(ctx,time) to update

var animation = {
    start : function(time){
        animation.items[0].start(time);
    },
    update : function(ctx,time){
        var i;
        var len = this.items.length;
        // update animation details
        for(i = 0; i < len; i ++){
            this.items[i].setTime(time);
        }
        // draw animation
        for(i = 0; i < len; i ++){
            this.items[i].draw(ctx);
        }        
    },
    items : [
        {
            animate : boxAnimProps,     // what to animate
            draw : drawFunc,            // function to draw
            setTime : animateFunc,      // function to update state
            start : startFunc,          // function to start the animation
            startTime : null,           // this items start time
            length : timePerSide,       // how long in seconds this animation is
            events : [{                 // list of events
                    fired : false,      // true if fired ( to stop it from repeating)
                    callback : null,    // callback to call when event fires
                    // this function determines when to fire the event
                    check : function(owner,time){
                        if(owner.current.x >= canvas.width - owner.current.w){
                            this.fired = true;
                            this.callback(time);
                        }
                    },
                }
            ],
            current : {}, // defined when running holds the current state
            startKey : {  // the key frame defining the start position
                x : -boxLength,
                y : 0,
                w : boxLength,
                h : boxHeight,
            },
            endKey : {   // keyframe for end
                x : canvas.width,
                y : 0,
                w : boxLength,
                h : boxHeight,
            }
        },{
            animate : boxAnimProps,
            draw : drawFunc, // function to draw
            setTime : animateFunc, // function to set current anim state
            start : startFunc, // function to start the animation
            startTime : null,
            length : timePerSide,
            events : [{
                    fired : false,
                    callback : null,
                    check : function(owner,time){
                        if(owner.current.y >= canvas.height - owner.current.h){
                            this.fired = true;
                            this.callback(time);
                        }
                    },
                }
            ],
            current : {}, // defined when running
            startKey : {
                x : canvas.width - boxHeight,
                y : -boxLength,
                w : boxHeight,
                h : boxLength,
            },
            endKey : {
                x : canvas.width - boxHeight,
                y : canvas.height,
                w : boxHeight,
                h : boxLength,
            }
        },{
            animate : boxAnimProps,
            draw : drawFunc, // function to draw
            setTime : animateFunc, // function to set current anim state
            start : startFunc, // function to start the animation
            startTime : null,
            length : timePerSide,
            events : [{
                    fired : false,
                    callback : null,
                    check : function(owner,time){
                        if(owner.current.x <= 0){
                            this.fired = true;
                            this.callback(time);
                        }
                    },
                }
            ],
            current : {}, // defined when running
            startKey : {
                x : canvas.width,
                y : canvas.height - boxHeight,
                w : boxLength,
                h : boxHeight,
            },
            endKey : {
                x : - boxLength,
                y : canvas.height - boxHeight,
                w : boxLength,
                h : boxHeight,
            }
        },{
            animate : boxAnimProps,
            draw : drawFunc, // function to draw
            setTime : animateFunc, // function to set current anim state
            start : startFunc, // function to start the animation
            startTime : null,
            length : timePerSide,
            events : [{
                    fired : false,
                    callback : null,
                    check : function(owner,time){
                        if(owner.current.y <= 0){
                            this.fired = true;
                            this.callback(time);
                        }
                    },
                }
            ],
            current : {}, // defined when running
            startKey : {
                x : 0,
                y : canvas.height,
                w : boxHeight,
                h : boxLength,
            },
            endKey : {
                x : 0,
                y : - boxLength,
                w : boxHeight,
                h : boxLength,
            }
        }
    ]
};
// set events. Item one calls item two and so on
animation.items[0].events[0].callback = animation.items[1].start.bind(animation.items[1]);
animation.items[1].events[0].callback = animation.items[2].start.bind(animation.items[2]);
animation.items[2].events[0].callback = animation.items[3].start.bind(animation.items[3]);
animation.items[3].events[0].callback = animation.items[0].start.bind(animation.items[0]);



// update the canvas
var firstFrame = true;
function update(time){
    if(firstFrame){
        firstFrame = false;
        animation.start(time / 1000); // set start time in seconds.
    }
    ctx.fillStyle = "#AAA";
    ctx.fillRect(0,0,canvas.width,canvas.height);
    ctx.fillStyle = "#F00";
    animation.update(ctx,time / 1000); // animate and convert time from ms to seconds
    requestAnimationFrame(update);
}
requestAnimationFrame(update);

Upvotes: 1

Related Questions