1

I'm trying to animate the movement of image across the canvas using requestAnimationFrame in 2 directions using a recursive function.

Animate 1 move the image in one direction on 1 click and then the opposite direction on 2nd click. Animate 2 moves the image in one direction.

Before I get to test my recursive function, my requestAnimationFrame returns an error on my first click of Animate 1. I ran a simple debug to find out that after my first click on Animate 1, the image animates successfully to the left. But as it exits the animation2(), it returns back to requestAnimationFrame to return the following error:

Uncaught TypeError: Failed to execute 'requestAnimationFrame' on 'Window': The callback provided as parameter 1 is not a function.

What causes this error? And I'm pretty sure my recursive function will not work as it is. What correction should I make?

The following is my crude coding.

var canvas = document.getElementsByTagName("canvas")[0];
canvas.width = 286;
canvas.height = 60;
var ctx = canvas.getContext("2d");
trackTransforms(ctx);

var img = new Image();
img.src = "https://www.dropbox.com/s/bq8wk2rashbsjxw/Capture.PNG?raw=1";

window.onload = function() {
  redraw();
};

var button1 = false;
var pause;
var coordinate = new Array();
coordinate[0] = {
  X1: 286,
  X2: 136
};
coordinate[1] = {
  X1: 150,
  X2: 0
};
var coordinate2 = new Array();
coordinate2[0] = {
  X1: 286,
  X2: 136
};
coordinate2[1] = {
  X1: 150,
  X2: 0
};

function animation() {
  var track = coordinate.slice(); // copy the array to keep track of which items are left to process after click event
  coordinate.forEach(function(item, index) {
    track.splice(track.indexOf(item), 1); // remove the item from the processed array
    button1 = true;
    animation2(item);
    button1 = false;

    button.onclick = function(event) {
      animation(track); // call function in the callback passing in the unprocessed items array
      event.target.onclick = null; // remove the click event so it can't be fired again
    }

  });
}

var fps = 1; // animation speed
var slideX = 0;

function animation2(coordinate) {
  if (button1) {
    var cornerRight = coordinate.X1; // animation in to and fro
    var cornerLeft = coordinate.X2;
  } else {
    var cornerRight = coordinate2[0].X1; // animation in single direction
    var cornerLeft = coordinate2[0].X2; // change index number to change direction
  }

  var cornerCenterX = (cornerLeft + cornerRight) / 2;
  var canvasCenterX = canvas.width / 2;

  if (cornerCenterX > canvasCenterX) {
    // to determine image to move right or left
    slideX -= fps;
    var distanceX = Math.abs(cornerCenterX - canvasCenterX);
    var pt = ctx.transformedPoint(slideX, 0);
    ctx.translate(pt.x, pt.y);
    redraw();
    if (button1) {
      if (Math.abs(slideX) < distanceX) requestAnimationFrame(animation2(coordinate));
    } else {
      if (Math.abs(slideX) < distanceX) requestAnimationFrame(animation2);
    }

  } else if (cornerCenterX < canvasCenterX) {
    slideX += fps;
    var distanceX = Math.abs(cornerCenterX - canvasCenterX);
    var pt = ctx.transformedPoint(slideX, 0);
    ctx.translate(pt.x, pt.y);
    redraw();
    if (button1) {
      if (Math.abs(slideX) < distanceX) requestAnimationFrame(animation2(coordinate));
    } else {
      if (Math.abs(slideX) < distanceX) requestAnimationFrame(animation2);
    }
  }
}

function redraw() {
  var p1 = ctx.transformedPoint(0, 0);
  var p2 = ctx.transformedPoint(canvas.width, canvas.height);
  ctx.clearRect(p1.x, p1.y, p2.x - p1.x, p2.y - p1.y);
  ctx.save();
  ctx.setTransform(1, 0, 0, 1, 0, 0);
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ctx.restore();
  ctx.drawImage(img, 0, 0);
}

function reset() {
  var p1 = ctx.transformedPoint(0, 0);
  var p2 = ctx.transformedPoint(canvas.width, canvas.height);
  ctx.clearRect(p1.x, p1.y, p2.x - p1.x, p2.y - p1.y);
  ctx.setTransform(1, 0, 0, 1, 0, 0);
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ctx.drawImage(img, 0, 0);
  slideX = 0;
  button1 = false;
}

function trackTransforms(ctx) {
  var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
  var xform = svg.createSVGMatrix();
  ctx.getTransform = function() {
    return xform;
  };

  var savedTransforms = [];
  var save = ctx.save;
  ctx.save = function() {
    savedTransforms.push(xform.translate(0, 0));
    return save.call(ctx);
  };

  var restore = ctx.restore;
  ctx.restore = function() {
    xform = savedTransforms.pop();
    return restore.call(ctx);
  };

  var translate = ctx.translate;
  ctx.translate = function(dx, dy) {
    xform = xform.translate(dx, dy);
    return translate.call(ctx, dx, dy);
  };

  var transform = ctx.transform;
  ctx.transform = function(a, b, c, d, e, f) {
    var m2 = svg.createSVGMatrix();
    m2.a = a;
    m2.b = b;
    m2.c = c;
    m2.d = d;
    m2.e = e;
    m2.f = f;
    xform = xform.multiply(m2);
    return transform.call(ctx, a, b, c, d, e, f);
  };

  var setTransform = ctx.setTransform;
  ctx.setTransform = function(a, b, c, d, e, f) {
    xform.a = a;
    xform.b = b;
    xform.c = c;
    xform.d = d;
    xform.e = e;
    xform.f = f;
    return setTransform.call(ctx, a, b, c, d, e, f);
  };

  // convert the mouse coordinates (in pixels) into the global space of your SVG document
  var pt = svg.createSVGPoint();
  ctx.transformedPoint = function(x, y) {
    pt.x = x;
    pt.y = y;
    return pt.matrixTransform(xform.inverse());
  };
}
body {
  background: #eee;
  margin: 1em;
  text-align: center;
}

canvas {
  display: block;
  margin: 1em auto;
  background: #fff;
  border: 1px solid #ccc;
}
<input id="button" type="button" value="Animate 1" onclick="animation();" />
<input id="button2" type="button" value="Animate 2" onclick="animation2();" />
<input id="button3" type="button" value="Reset" onclick="reset();" />
<canvas id="main"></canvas>

5
  • 1
    see this requestAnimationFrame(animation2(coordinate)) ... that's the issue, that's not a function you are passing to it, that's the result of calling that function immediately that you are passing ... try requestAnimationFrame(() => animation2(coordinate)) instead Commented Aug 2, 2018 at 4:25
  • 1
    not sure what pausing has to do with bad code? Commented Aug 2, 2018 at 4:25
  • hi, I'm still new to coding. Would you mind explain the syntax behind requestAnimationFrame(() => animation2(coordinate))? Commented Aug 2, 2018 at 4:52
  • ok, that's an arrow function ... so, old school would be requestAnimationFrame(function { animation2(coordinate);}) - i.e, now you're passing a function to RAF, as it expects Commented Aug 2, 2018 at 4:55
  • Thanks for your reply. rAF recognizes the function in requestAnimationFrame(animation2) so I thought I would also work if I pass a variable into animation2()'. Without the coordinate` variable, I can't get the rAF to work Commented Aug 2, 2018 at 5:06

1 Answer 1

1

Following up from the comments:

You should read more about the requestAnimationFrame method:
https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame

The syntax is: window.requestAnimationFrame(callback);
That callback is a parameter specifying a function to call when it's time to update your animation for the next repaint.

Using your code as an example:

  • animation2 is a function
  • animation2(coordinate) is NOT a function
  • function() { animation2(coordinate) } is a function

Consider reading about nameless/anonymous functions



Here is your code working without errors:

var canvas = document.getElementsByTagName("canvas")[0];
canvas.width = 286;
canvas.height = 60;
var ctx = canvas.getContext("2d");
trackTransforms(ctx);

var img = new Image();
img.src = "https://www.dropbox.com/s/bq8wk2rashbsjxw/Capture.PNG?raw=1";

window.onload = function() {
  redraw();
};

var button1 = false;
var pause;
var coordinate = new Array();
coordinate[0] = {
  X1: 286,
  X2: 136
};
coordinate[1] = {
  X1: 150,
  X2: 0
};
var coordinate2 = new Array();
coordinate2[0] = {
  X1: 286,
  X2: 136
};
coordinate2[1] = {
  X1: 150,
  X2: 0
};

function animation() {
  var track = coordinate.slice(); // copy the array to keep track of which items are left to process after click event
  coordinate.forEach(function(item, index) {
    track.splice(track.indexOf(item), 1); // remove the item from the processed array
    button1 = true;
    animation2(item);
    button1 = false;

    button.onclick = function(event) {
      animation(track); // call function in the callback passing in the unprocessed items array
      event.target.onclick = null; // remove the click event so it can't be fired again
    }

  });
}

var fps = 1; // animation speed
var slideX = 0;

function animation2(coordinate) {
  if (button1) {
    var cornerRight = coordinate.X1; // animation in to and fro
    var cornerLeft = coordinate.X2;
  } else {
    var cornerRight = coordinate2[0].X1; // animation in single direction
    var cornerLeft = coordinate2[0].X2; // change index number to change direction
  }

  var cornerCenterX = (cornerLeft + cornerRight) / 2;
  var canvasCenterX = canvas.width / 2;

  if (cornerCenterX > canvasCenterX) {
    // to determine image to move right or left
    slideX -= fps;
    var distanceX = Math.abs(cornerCenterX - canvasCenterX);
    var pt = ctx.transformedPoint(slideX, 0);
    ctx.translate(pt.x, pt.y);
    redraw();
    if (button1) {
      if (Math.abs(slideX) < distanceX) requestAnimationFrame(function() { animation2(coordinate) });
    } else {
      if (Math.abs(slideX) < distanceX) requestAnimationFrame(animation2);
    }

  } else if (cornerCenterX < canvasCenterX) {
    slideX += fps;
    var distanceX = Math.abs(cornerCenterX - canvasCenterX);
    var pt = ctx.transformedPoint(slideX, 0);
    ctx.translate(pt.x, pt.y);
    redraw();
    if (button1) {
      if (Math.abs(slideX) < distanceX) requestAnimationFrame(function() { animation2(coordinate) });
    } else {
      if (Math.abs(slideX) < distanceX) requestAnimationFrame(animation2);
    }
  }
}

function redraw() {
  var p1 = ctx.transformedPoint(0, 0);
  var p2 = ctx.transformedPoint(canvas.width, canvas.height);
  ctx.clearRect(p1.x, p1.y, p2.x - p1.x, p2.y - p1.y);
  ctx.save();
  ctx.setTransform(1, 0, 0, 1, 0, 0);
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ctx.restore();
  ctx.drawImage(img, 0, 0);
}

function reset() {
  var p1 = ctx.transformedPoint(0, 0);
  var p2 = ctx.transformedPoint(canvas.width, canvas.height);
  ctx.clearRect(p1.x, p1.y, p2.x - p1.x, p2.y - p1.y);
  ctx.setTransform(1, 0, 0, 1, 0, 0);
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ctx.drawImage(img, 0, 0);
  slideX = 0;
  button1 = false;
}

function trackTransforms(ctx) {
  var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
  var xform = svg.createSVGMatrix();
  ctx.getTransform = function() {
    return xform;
  };

  var savedTransforms = [];
  var save = ctx.save;
  ctx.save = function() {
    savedTransforms.push(xform.translate(0, 0));
    return save.call(ctx);
  };

  var restore = ctx.restore;
  ctx.restore = function() {
    xform = savedTransforms.pop();
    return restore.call(ctx);
  };

  var translate = ctx.translate;
  ctx.translate = function(dx, dy) {
    xform = xform.translate(dx, dy);
    return translate.call(ctx, dx, dy);
  };

  var transform = ctx.transform;
  ctx.transform = function(a, b, c, d, e, f) {
    var m2 = svg.createSVGMatrix();
    m2.a = a;
    m2.b = b;
    m2.c = c;
    m2.d = d;
    m2.e = e;
    m2.f = f;
    xform = xform.multiply(m2);
    return transform.call(ctx, a, b, c, d, e, f);
  };

  var setTransform = ctx.setTransform;
  ctx.setTransform = function(a, b, c, d, e, f) {
    xform.a = a;
    xform.b = b;
    xform.c = c;
    xform.d = d;
    xform.e = e;
    xform.f = f;
    return setTransform.call(ctx, a, b, c, d, e, f);
  };

  // convert the mouse coordinates (in pixels) into the global space of your SVG document
  var pt = svg.createSVGPoint();
  ctx.transformedPoint = function(x, y) {
    pt.x = x;
    pt.y = y;
    return pt.matrixTransform(xform.inverse());
  };
}
body {
  background: #eee;
  margin: 1em;
  text-align: center;
}

canvas {
  display: block;
  margin: 1em auto;
  background: #fff;
  border: 1px solid #ccc;
}
<input id="button" type="button" value="Animate 1" onclick="animation();" />
<input id="button2" type="button" value="Animate 2" onclick="animation2();" />
<input id="button3" type="button" value="Reset" onclick="reset();" />
<canvas id="main"></canvas>

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.