I am trying to animate a circle across the canvas in diagonal direction. I am using requestanimationframe. It works smoothly in all browsers but not in firefox. There is constant flickering while the animation is going on. Please someone tell me what wrong I am doing. I need smooth animatin with atleast reasonable speed in firefox.
Here is my simple code, maybe someone can point out what I am missing here..:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style>
/*html { background: #000; }*/
</style>
<title>Test</title>
</head>
<body>
<script type="text/javascript">
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(/* function */ callback, /* DOMElement */ element){
window.setTimeout(callback, 1000 / 60);
};
})();
window.cancelRequestAnimFrame = ( function() {
return window.cancelAnimationFrame ||
window.webkitCancelRequestAnimationFrame ||
window.mozCancelRequestAnimationFrame ||
window.oCancelRequestAnimationFrame ||
window.msCancelRequestAnimationFrame ||
clearTimeout
} )();
var canvas, context, toggle;
var ctr = 0;
var request;
var x = 10;
var y = 10;
init();
animate();
function init() {
canvas = document.createElement( 'canvas' );
canvas.width = 512;
canvas.height = 512;
context = canvas.getContext( '2d' );
document.body.appendChild( canvas );
}
function animate() {
request = requestAnimFrame( animate );
draw();
}
function draw() {
context.clearRect(0,0,canvas.width,canvas.height);
y+=2;
x+=2;
context.beginPath();
context.arc(x, y, 3, 0, 2 * Math.PI, false);
context.fillStyle = 'green';
context.fill();
context.lineWidth = 5;
context.strokeStyle = '#003300';
context.stroke();
//cancelRequestAnimFrame(request);
}
</script>
</body>
</html>
There are two things in your code which you need to be revised:
First try the new requestAnimationFrame polyfill.
(function() {
var lastTime = 0;
var vendors = ['ms', 'moz', 'webkit', 'o'];
for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
window.cancelRequestAnimationFrame = window[vendors[x]+
'CancelRequestAnimationFrame'];
}
if (!window.requestAnimationFrame)
window.requestAnimationFrame = function(callback, element) {
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = window.setTimeout(function() { callback(currTime + timeToCall); },
timeToCall);
lastTime = currTime + timeToCall;
return id;
};
if (!window.cancelAnimationFrame)
window.cancelAnimationFrame = function(id) {
clearTimeout(id);
};
}())
For an in-depth explanation read this article: http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating
The second is that you need to calculate the delta between one time stamp and the next one. You are working with a fixed value which on each rendering iteration got increased by one. Because your browser can not keep up the the pace it might happen to skip one or two frames. This way you get a smooth time.
This is the way you calculate the delta time:
end = Date.now();
delta = ( end - start ) / 1000.0 * 60;
end = start;
http://jsfiddle.net/p5c6c/1/
Related
<!DOCTYPE html>
<html>
<head>
<style type="text/css">
#canvasOne
{
border: 1px solid black;
}
</style>
<script src="http://code.jquery.com/jquery-1.10.2.js" type="text/javascript"></script>
</head>
<body>
<div align="center">
<canvas id="canvasOne">
</canvas>
</div>
<script type="text/javascript">
var myCanvas = document.getElementById("canvasOne");
var myContext = myCanvas.getContext("2d");
var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
init();
var numShapes;
var shapes;
var dragIndex;
var dragging;
var mouseX;
var mouseY;
var dragHoldX;
var dragHoldY;
var timer;
var targetX;
var targetY;
var easeAmount;
var bgColor;
var nodes;
var colorArr;
function init()
{
myCanvas.width = $(window).width() - 200;
myCanvas.height = $(window).height() - 200;
shapes = [];
nodes = ["0;Person;24828760;Alok Kumar;Gorakhpur;#F44336;28",
"0;Suspect;04/Dec/2016;4;Suman_Biswas;#3F51B5;20","1;Rule;4;Apparent Means;3 Parameter;#EEFF41;20",
"0;Policy;36QA649749;In-Force;Quarterly;#FF9800;20","3;Product;Pension;Saral Pension;SRPEN;#795548;20","3;Payment;Cheque;Realized;Lucknow;#0091EA;20",
"0;Policy;162348873;Lapsed;Quarterly;#FF9800;20","6;Product;Pension;Life-Long Pension;LLPP;#795548;20","6;Payment;Cheque;Realized;Gorakhpur;#0091EA;20",
"0;Policy;1EQF178639;Lapsed;Monthly;#FF9800;20","9;Product;Life;Shield;SHIELDA;#795548;20","9;Payment;Demand Draft;Realized;Lucknow;#0091EA;20"];
numShapes = nodes.length;
makeShapes();
drawScreen();
myCanvas.addEventListener("mousedown", mouseDownListener, false);
}
//drawing
function makeShapes()
{
var tempX;
var tempY;
for(var i = 0; i < numShapes; i++)
{
var centerX = myCanvas.width/2;
var centerY = myCanvas.height/2;
var nodeColor = nodes[i].split(";")[5];
var nodeRadius = nodes[i].split(";")[6];
var nodeConnect = nodes[i].split(";")[0];
if(i == 0)//center of circle
{
tempX = centerX
tempY = centerY;
}
else
{
//tempX = Math.random() * (myCanvas.width - tempRadius);
//tempY = Math.random() * (myCanvas.height - tempRadius);
//var x = x0 + r * Math.cos(2 * Math.PI * i / items);
//var y = y0 + r * Math.sin(2 * Math.PI * i / items);
//250 is the distance from center node to outside nodes it can be actual radius in degrees
tempX = shapes[nodeConnect].x + 300 * Math.cos(2 * Math.PI * i / numShapes);
tempY = shapes[nodeConnect].y + 300 * Math.sin(2 * Math.PI * i / numShapes);
}
tempShape = {x: tempX, y: tempY, rad: nodeRadius, color: nodeColor, text: nodes[i]};
shapes.push(tempShape);
}
}
//drawing both shape (line and circle) and screen
function drawScreen()
{
myContext.fillStyle = "#ffffff";
myContext.fillRect(0, 0, myCanvas.width, myCanvas.height);
drawShapes();
}
function drawShapes()
{
//line
for(var i = 1; i < numShapes; i++)
{
myContext.beginPath();
myContext.strokeStyle = "#B2B19D";
var nodeConnect = nodes[i].split(";")[0];
myContext.moveTo(shapes[nodeConnect].x, shapes[nodeConnect].y);
myContext.lineTo(shapes[i].x, shapes[i].y);
myContext.stroke();
}
//circle
for(var i = 0; i < numShapes; i++)
{
myContext.fillStyle = shapes[i].color;
myContext.beginPath();
myContext.arc(shapes[i].x, shapes[i].y, shapes[i].rad, 0, 2*Math.PI, false);
myContext.closePath();
myContext.fill();
}
//text
for(var i = 0; i < numShapes; i++)
{
myContext.beginPath();
myContext.font = '10pt Arial';
myContext.fillStyle = 'black';
var textarr = shapes[i].text.split(";");
myContext.fillText(textarr[1], shapes[i].x + 30, shapes[i].y - 24);
/*myContext.fillText(textarr[2], shapes[i].x + 30, shapes[i].y + 1);
myContext.fillText(textarr[3], shapes[i].x + 30, shapes[i].y + 22);
myContext.fillText(textarr[4], shapes[i].x + 30, shapes[i].y + 44);*/
myContext.closePath();
myContext.fill();
}
}
//animation
function mouseDownListener(evt)
{
var highestIndex = -1;
var bRect = myCanvas.getBoundingClientRect();
mouseX = (evt.clientX - bRect.left) * (myCanvas.width/bRect.width);
mouseY = (evt.clientY - bRect.top) * (myCanvas.height/bRect.height);
for(var i = 0; i < numShapes; i++)
{
if(hitTest(shapes[i], mouseX, mouseY))
{
dragging = true;
if(i > highestIndex)
{
dragHoldX = mouseX - shapes[i].x;
dragHoldY = mouseY - shapes[i].y;
highestIndex = i;
dragIndex = i;
}
}
}
if(dragging)
{
window.addEventListener("mousemove", mouseMoveListener, false);
}
myCanvas.removeEventListener("mousedown", mouseDownListener, false);
window.addEventListener("mouseup", mouseUpListener, false);
if(evt.preventDefault)
{
evt.preventDefault;
}
return false;
}
function mouseMoveListener(evt)
{
var shapeRad = shapes[dragIndex].rad;
var minX = shapeRad;
var maxX = myCanvas.width - shapeRad;
var minY = shapeRad;
var maxY = myCanvas.height - shapeRad;
//get mouse position correctly
var bRect = myCanvas.getBoundingClientRect();
mouseX = (evt.clientX - bRect.left)*(myCanvas.width / bRect.width);
mouseY = (evt.clientY - bRect.top)*(myCanvas.height / bRect.height);
//clamp x and y position to prevent object from dragging outside canvas
posX = mouseX - dragHoldX;
posX = (posX < minX) ? minX : ((posX > maxX) ? maxX : posX);
posY = mouseY - dragHoldY;
posY = (posY < minY) ? minY : ((posY > maxY) ? maxY : posY);
shapes[dragIndex].x = posX;
shapes[dragIndex].y = posY;
drawScreen();
}
function mouseUpListener(evt)
{
myCanvas.addEventListener("mousedown", mouseDownListener, false);
window.removeEventListener("mouseup", mouseUpListener, false);
if(dragging)
{
dragging = false;
window.removeEventListener("mousemove", mouseMoveListener, false);
}
}
function hitTest(shape, mx, my)
{
var dx = mx - shape.x;
var dy = my - shape.y;
return(dx * dx + dy * dy < shape.rad * shape.rad);
}
</script>
</body>
</html>
The following canvas animation creates nodes and edges. However due
to space constraint, some of the nodes are not visible due to canvas
height and width. Even adding overflow css to canvas dosen't help as
i am not able to scroll.
<canvas> context doesn't have a built-in scroll method.
You then have multiple ways to circumvent this limitation.
The first one, is as in #markE's answer, to scale your context's matrix so that your drawings fit into the required space. You could also refactor your code so that all coordinates are relative to the canvas size.
This way, you won't need scrollbars and all your drawings will just be scaled appropriately, which is the desirable behavior in most common cases.
But if you really need to have some scrolling feature, here are some ways :
The easiest and most recommended one : let the browser handle it.
You will have to set the size of your canvas to the maximum of your drawings, and wrap it in an other element which will scroll. By setting the overflow:auto css property on the container, our scrollbars appear and we have our scrolling feature.
In following example, the canvas is 5000px wide and the container 200px.
var ctx = canvas.getContext('2d');
ctx.textAlign = 'center';
for (var w = 0; w < canvas.width; w += 100) {
for (var h = 0; h < canvas.height; h += 100) {
ctx.fillText(w + ',' + h, w, h);
}
}
#container {
width: 200px;
height: 200px;
overflow: auto;
border: 1px solid;
}
canvas{
display: block;
}
<div id="container">
<canvas id="canvas" height="5000" width="5000"></canvas>
</div>
Main advantages :
easily implemented.
users are used to these scrollbars.
Main caveats :
You're limited by canvas maximum sizes.
If your canvas is animated, you'll also draw for each frame parts of the canvas that aren't visible.
You have small control on scrollbars look and you'll still have to implement drag-to-scroll feature yourself for desktop browsers.
A second solution, is to implement this feature yourself, using canvas transform methods : particularly translate, transform and setTransform.
Here is an example :
var ctx = canvas.getContext('2d');
var app = {};
// the total area of our drawings, can be very large now
app.WIDTH = 5000;
app.HEIGHT = 5000;
app.draw = function() {
// reset everything (clears the canvas + transform + fillStyle + any other property of the context)
canvas.width = canvas.width;
// move our context by the inverse of our scrollbars' left and top property
ctx.setTransform(1, 0, 0, 1, -app.scrollbars.left, -app.scrollbars.top);
ctx.textAlign = 'center';
// draw only the visible area
var visibleLeft = app.scrollbars.left;
var visibleWidth = visibleLeft + canvas.width;
var visibleTop = app.scrollbars.top
var visibleHeight = visibleTop + canvas.height;
// you probably will have to make other calculations than these ones to get your drawings
// to draw only where required
for (var w = visibleLeft; w < visibleWidth + 50; w += 100) {
for (var h = visibleTop; h < visibleHeight + 50; h += 100) {
var x = Math.round((w) / 100) * 100;
var y = Math.round((h) / 100) * 100;
ctx.fillText(x + ',' + y, x, y);
}
}
// draw our scrollbars on top if needed
app.scrollbars.draw();
}
app.scrollbars = function() {
var scrollbars = {};
// initial position
scrollbars.left = 0;
scrollbars.top = 0;
// a single constructor for both horizontal and vertical
var ScrollBar = function(vertical) {
var that = {
vertical: vertical
};
that.left = vertical ? canvas.width - 10 : 0;
that.top = vertical ? 0 : canvas.height - 10;
that.height = vertical ? canvas.height - 10 : 5;
that.width = vertical ? 5 : canvas.width - 10;
that.fill = '#dedede';
that.cursor = {
radius: 5,
fill: '#bababa'
};
that.cursor.top = vertical ? that.cursor.radius : that.top + that.cursor.radius / 2;
that.cursor.left = vertical ? that.left + that.cursor.radius / 2 : that.cursor.radius;
that.draw = function() {
if (!that.visible) {
return;
}
// remember to reset the matrix
ctx.setTransform(1, 0, 0, 1, 0, 0);
// you can give it any shape you like, all canvas drawings operations are possible
ctx.fillStyle = that.fill;
ctx.fillRect(that.left, that.top, that.width, that.height);
ctx.beginPath();
ctx.arc(that.cursor.left, that.cursor.top, that.cursor.radius, 0, Math.PI * 2);
ctx.fillStyle = that.cursor.fill;
ctx.fill();
};
// check if we're hovered
that.isHover = function(x, y) {
if (x >= that.left - that.cursor.radius && x <= that.left + that.width + that.cursor.radius &&
y >= that.top - that.cursor.radius && y <= that.top + that.height + that.cursor.radius) {
// we are so record the position of the mouse and set ourself as the one hovered
scrollbars.mousePos = vertical ? y : x;
scrollbars.hovered = that;
that.visible = true;
return true;
}
// we were visible last call and no wheel event is happening
else if (that.visible && !scrollbars.willHide) {
that.visible = false;
// the app should be redrawn
return true;
}
}
return that;
};
scrollbars.horizontal = ScrollBar(0);
scrollbars.vertical = ScrollBar(1);
scrollbars.hovered = null;
scrollbars.dragged = null;
scrollbars.mousePos = null;
// check both of our scrollbars
scrollbars.isHover = function(x, y) {
return this.horizontal.isHover(x, y) || this.vertical.isHover(x, y);
};
// draw both of our scrollbars
scrollbars.draw = function() {
this.horizontal.draw();
this.vertical.draw();
};
// check if one of our scrollbars is visible
scrollbars.visible = function() {
return this.horizontal.visible || this.vertical.visible;
};
// hide it...
scrollbars.hide = function() {
// only if we're not using the mousewheel or dragging the cursor
if (this.willHide || this.dragged) {
return;
}
this.horizontal.visible = false;
this.vertical.visible = false;
};
// get the area's coord relative to our scrollbar
var toAreaCoord = function(pos, scrollBar) {
var sbBase = scrollBar.vertical ? scrollBar.top : scrollBar.left;
var sbMax = scrollBar.vertical ? scrollBar.height : scrollBar.width;
var areaMax = scrollBar.vertical ? app.HEIGHT - canvas.height : app.WIDTH - canvas.width;
var ratio = (pos - sbBase) / (sbMax - sbBase);
return areaMax * ratio;
};
// get the scrollbar's coord relative to our total area
var toScrollCoords = function(pos, scrollBar) {
var sbBase = scrollBar.vertical ? scrollBar.top : scrollBar.left;
var sbMax = scrollBar.vertical ? scrollBar.height : scrollBar.width;
var areaMax = scrollBar.vertical ? app.HEIGHT - canvas.height : app.WIDTH - canvas.width;
var ratio = pos / areaMax;
return ((sbMax - sbBase) * ratio) + sbBase;
}
scrollbars.scroll = function() {
// check which one of the scrollbars is active
var vertical = this.hovered.vertical;
// until where our cursor can go
var maxCursorPos = this.hovered[vertical ? 'height' : 'width'];
var pos = vertical ? 'top' : 'left';
// check that we're not out of the bounds
this.hovered.cursor[pos] = this.mousePos < 0 ? 0 :
this.mousePos > maxCursorPos ? maxCursorPos : this.mousePos;
// seems ok so tell the app we scrolled
this[pos] = toAreaCoord(this.hovered.cursor[pos], this.hovered);
// redraw everything
app.draw();
}
// because we will hide it after a small time
scrollbars.willHide;
// called by the wheel event
scrollbars.scrollBy = function(deltaX, deltaY) {
// it's not coming from our scrollbars
this.hovered = null;
// we're moving horizontally
if (deltaX) {
var newLeft = this.left + deltaX;
// make sure we're in the bounds
this.left = newLeft > app.WIDTH - canvas.width ? app.WIDTH - canvas.width : newLeft < 0 ? 0 : newLeft;
// update the horizontal cursor
this.horizontal.cursor.left = toScrollCoords(this.left, this.horizontal);
// show our scrollbar
this.horizontal.visible = true;
}
if (deltaY) {
var newTop = this.top + deltaY;
this.top = newTop > app.HEIGHT - canvas.height ? app.HEIGHT - canvas.height : newTop < 0 ? 0 : newTop;
this.vertical.cursor.top = toScrollCoords(this.top, this.vertical);
this.vertical.visible = true;
}
// if we were called less than the required timeout
clearTimeout(this.willHide);
this.willHide = setTimeout(function() {
scrollbars.willHide = null;
scrollbars.hide();
app.draw();
}, 500);
// redraw everything
app.draw();
};
return scrollbars;
}();
var mousedown = function(e) {
// tell the browser we handle this
e.preventDefault();
// we're over one the scrollbars
if (app.scrollbars.hovered) {
// new promotion ! it becomes the dragged one
app.scrollbars.dragged = app.scrollbars.hovered;
app.scrollbars.scroll();
}
};
var mousemove = function(e) {
// check the coordinates of our canvas in the document
var rect = canvas.getBoundingClientRect();
var x = e.clientX - rect.left;
var y = e.clientY - rect.top;
// we're dragging something
if (app.scrollbars.dragged) {
// update the mouse position
app.scrollbars.mousePos = app.scrollbars.dragged.vertical ? y : x;
app.scrollbars.scroll();
} else if (app.scrollbars.isHover(x, y)) {
// something has changed, redraw to show or hide the scrollbar
app.draw();
}
e.preventDefault();
};
var mouseup = function() {
// we dropped it
app.scrollbars.dragged = null;
};
var mouseout = function() {
// we're out
if (app.scrollbars.visible()) {
app.scrollbars.hide();
app.scrollbars.dragged = false;
app.draw();
}
};
var mouseWheel = function(e) {
e.preventDefault();
app.scrollbars.scrollBy(e.deltaX, e.deltaY);
};
canvas.addEventListener('mousemove', mousemove);
canvas.addEventListener('mousedown', mousedown);
canvas.addEventListener('mouseup', mouseup);
canvas.addEventListener('mouseout', mouseout);
canvas.addEventListener('wheel', mouseWheel);
range.onchange = function() {
app.WIDTH = app.HEIGHT = this.value;
app.scrollbars.left = 0;
app.scrollbars.top = 0;
app.draw();
};
// an initial drawing
app.draw();
canvas {border: 1px solid;}
span{font-size: .8em;}
<canvas id="canvas" width="200" height="150"></canvas>
<span>
change the total area size
<input type="range" min="250" max="5000000" steps="250" value="5000" id="range" />
</span>
Main advantages :
no limitation for the size of your drawing areas.
you can customize your scrollbars as you wish.
you can control when the scrollbars are enable or not.
you can get the visible area quite easily.
Main caveats:
a bit more code than the CSS solution...
no really, that's a lot of code...
A third way I wrote some time ago for an other question took advantage of the ability to draw an other canvas with ctx.drawImage(). It has its own caveats and advantages, so I let you pick the one you need, but this last one also had a drag and slide feature which can be useful.
So your node drawings don't fit on the canvas size?
You can easily "shrink" your content to fit the visible canvas with just 1 command!
The context.scale(horizontalRescale,verticalRescale) command will shrink every following drawing by your specified horizontalRescale & verticalRescale percentages.
An Important note: You must make horizontalRescale,verticalRescale the same value or your content will be distorted.
The nice thing about using context.scale is that you don't have to change any of the code that draws your nodes ... canvas automatically scales all those nodes for you.
For example, this code will shrink your nodes to 80% of their original size:
var downscaleFactor= 0.80;
context.scale( downscaleFactor, downscaleFactor );
Rather than go through your 200+ lines of code, I leave it to you to calculate downscaleFactor.
I've been following various guides and have the base code up for a simple animation but for the life of me I cannot get the canvas to reset correctly. The clearRect() function is working perfectly but when I try to draw an arc again immediatly afterwards it draws the sum of all the paths again rather than just drawing the single segment of the circle.
Can someone tell me what I'm doing wrong here, I know it's a simple solution! In short I would like the test page below to produce a kind of spinning segment instead of just drawing a circle:
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
</head>
<body>
<span id="degrees_output" style="display:block;width:60px"></span>
<button onclick="continue_animation=false;">Stop</button>
<canvas id="myCanvas" width="578" height="200"></canvas>
<script>
var degrees = 0;
var continue_animation = true;
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
var output = document.getElementById('degrees_output');
window.requestAnimFrame = (function(callback) {
return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000 / 60);
};
})();
function animate() {
// update
output.innerHTML = degrees;
var radians = (degrees / 180) * Math.PI;
if (degrees >= 360)
degrees = 0;
else
degrees += 1;
// clear
if (degrees % 20 == 0)
{
context.clearRect(0, 0, 578, 200);
}
context.beginPath();
// setup the line style
context.strokeStyle = '#fa00ff';
context.lineWidth = 5;
context.lineCap = 'round';
context.arc(50, 50, 20, 0, radians, false);
// colour the path
context.stroke();
context.closePath();
// request new frame
requestAnimFrame(function() {
if (continue_animation)
animate();
});
}
animate();
</script>
If you want a fixed length arc to sweep around your circle, you'll want to specify both the starting and ending angles in your context.arc like this:
context.arc(50, 50, 20, startRadians, radians, false);
and startRadians can just be any angle behind your "radians" value:
var startRadians= (degrees-45) * Math.PI/180;
and, Yes, you will need to clear the canvas (at least the previous arc) before drawing the arc in a new position:
context.clearRect(0,0,canvas.width,canvas.height);
Here is code and a Fiddle: http://jsfiddle.net/m1erickson/VFhzX/
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
body{ background-color: ivory; }
canvas{border:1px solid red;}
</style>
<script>
$(function(){
var degrees = 0;
var continue_animation = true;
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
var output = document.getElementById('degrees_output');
window.requestAnimFrame = (function(callback) {
return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000 / 60);
};
})();
function animate() {
// update
output.innerHTML = degrees;
var radians = (degrees / 180) * Math.PI;
var startRadians= (degrees-45) * Math.PI/180;
if (degrees >= 360)
degrees = 0;
else
degrees += 1;
// clear
if (degrees % 20 == 0)
{
context.clearRect(0, 0, 578, 200);
}
context.clearRect(0,0,canvas.width,canvas.height);
context.beginPath();
// setup the line style
context.strokeStyle = '#fa00ff';
context.lineWidth = 5;
context.lineCap = 'round';
context.arc(50, 50, 20, startRadians, radians, false);
// colour the path
context.stroke();
context.closePath();
// request new frame
requestAnimFrame(function() {
if (continue_animation)
animate();
});
}
animate();
}); // end $(function(){});
</script>
</head>
<body>
<span id="degrees_output" style="display:block;width:60px"></span>
<button onclick="continue_animation=false;">Stop</button>
<canvas id="myCanvas" width="578" height="200"></canvas>
</body>
</html>
I got the following RequestAnimationframe function from http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating
I am trying to use it. But not sure how to call it and use it. Can someone give me a simple example. I am new to this html5 animation thing so you can understand..
I will really appreciate any help! The function is below..
(function() {
var lastTime = 0;
var vendors = ['ms', 'moz', 'webkit', 'o'];
for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
window.cancelRequestAnimationFrame = window[vendors[x]+
'CancelRequestAnimationFrame'];
}
if (!window.requestAnimationFrame)
window.requestAnimationFrame = function(callback, element) {
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = window.setTimeout(function() { callback(currTime + timeToCall); },
timeToCall);
lastTime = currTime + timeToCall;
return id;
};
if (!window.cancelAnimationFrame)
window.cancelAnimationFrame = function(id) {
clearTimeout(id);
};
}())
Just paste that code into your JS or in its own file and put this inside of your rendering function at the very bottom.
requestAnimationFrame(yourrenderingfunction);
Live Demo
// requestAnimationFrame shim
(function() {
var lastTime = 0;
var vendors = ['ms', 'moz', 'webkit', 'o'];
for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
window.cancelRequestAnimationFrame = window[vendors[x]+
'CancelRequestAnimationFrame'];
}
if (!window.requestAnimationFrame)
window.requestAnimationFrame = function(callback, element) {
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = window.setTimeout(function() { callback(currTime + timeToCall); },
timeToCall);
lastTime = currTime + timeToCall;
return id;
};
if (!window.cancelAnimationFrame)
window.cancelAnimationFrame = function(id) {
clearTimeout(id);
};
}())
// Sprite unimportant, just for example purpose
function Sprite(){
this.x = 0;
this.y = 50;
}
Sprite.prototype.draw = function(){
ctx.fillStyle = "rgb(255,0,0)";
ctx.fillRect(this.x, this.y, 10, 10);
}
// setup
var canvas = document.getElementsByTagName("canvas")[0],
ctx = canvas.getContext("2d");
canvas.width = 200;
canvas.height = 200;
//init the sprite
var sprite = new Sprite();
// draw the sprite and update it using request animation frame.
function update(){
ctx.clearRect(0,0,200,200);
sprite.x+=0.5;
if(sprite.x>200){
sprite.x = 0;
}
sprite.draw();
// makes it update everytime
requestAnimationFrame(update);
}
// initially calls the update function to get it started
update();
I implemented a small script to test requestAnimationFrame and it generates
"Uncaught RangeError: Maximum call stack size exceeded" error ..This is the code
<html>
<head>
<meta charset="utf-8">
<script>
window.onload=function(){
if (document.createElement("canvas").getContext){
//alert("browser supports canvas");
//console.log(document.getElementById("canvas").getContext);
canvas = document.getElementById("canvas");
shape = new shapes();
shape.drawball(canvas,5,"red");
}
};
function shapes(){
this.drawtriangle = function(canvas){
triangles = new triangle(0,0,0,200,200,200);
triangles.draw(canvas.getContext('2d'));
}
this.drawball = function(canvas,radius,color) {
ball = new Ball(radius,color);
ball.drawBall(canvas.getContext('2d'),canvas);
}
}
function coordinates(x1,y1){
this.x = x1;
this.y = y1;
}
function angle(angle){
this.angle = angle;
}
function Ball(radius,color){
this.origin = new coordinates(100,100);
this.radius = (radius === "undefined" ) ? 5 : radius;
this.color = (color === "undefined") ? red : color;
this.rotation = 0;
this.index = 0;
this.angles = new angle(0);
this.speed = 10;
}
Ball.prototype.drawBall = function (context,canvas){
context.fillStyle = this.color;
context.strokeStyle = "blue";
context.rotate(this.rotation);
context.beginPath();
context.arc(this.origin.x,this.origin.y,this.radius,0,(Math.PI*2),true);
context.closePath();
context.fill();
context.stroke(); this.animate(context,canvas);
}
Ball.prototype.animate = function (context,canvas){
var that = this;console.log(".......");
var time = new Date().getTime() * 0.002;
this.origin.x = Math.sin( time ) * 96 + 128;
this.origin.y = Math.cos( time * 0.9 ) * 96 + 128;
//context.clearRect(0,0,1000,1000);
console.log("Animating ... ");
this.origin.x = this.origin.x + this.speed;
this.origin.y = this.origin.y + this.speed;
this.angles.angle = this.angles.angle + this.speed;
window.webkitrequestAnimationFrame(that.drawBall(context,canvas));
}
</script>
<style>
body {
background-color: #bbb;
}
#canvas {
background-color: #fff;
}
</style>
</head>
<body>
<canvas id="canvas" width="1000px" height="1000px">
Your browser dows bot suppoet canvas
</canvas>
</body>
</html>
I got another code from the net and this works fine although it has recursion..
<!DOCTYPE HTML>
<html lang="en">
<head>
<title>RequestAnimationFrame.js example</title>
</head>
<body>
<script >/**
* Provides requestAnimationFrame in a cross browser way.
* http://paulirish.com/2011/requestanimationframe-for-smart-animating/
*/
if ( !window.requestAnimationFrame ) {
window.requestAnimationFrame = ( function() {
return window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame || // comment out if FF4 is slow (it caps framerate at ~30fps: https://bugzilla.mozilla.org/show_bug.cgi?id=630127)
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function( /* function FrameRequestCallback */ callback, /* DOMElement Element */ element ) {
window.setTimeout( callback, 1000 / 60 );
};
} )();
}</script>
<script>
var canvas, context;
init();
animate();
function init() {
canvas = document.createElement( 'canvas' );
canvas.width = 256;
canvas.height = 256;
context = canvas.getContext( '2d' );
document.body.appendChild( canvas );
}
function animate() {
requestAnimationFrame( animate );
draw();
}
function draw() {
var time = new Date().getTime() * 0.002;
var x = Math.sin( time ) * 96 + 128;
var y = Math.cos( time * 0.9 ) * 96 + 128;
context.fillStyle = 'rgb(245,245,245)';
context.fillRect( 0, 0, 255, 255 );
context.fillStyle = 'rgb(255,0,0)';
context.beginPath();
context.arc( x, y, 10, 0, Math.PI * 2, true );
context.closePath();
context.fill();
}
</script>
<div style="width:256px">
view source<br /><br/>
requestAnimationFrame() allows modern browsers to stop drawing graphics when a tab or window is not visible. Improving overall performance and batteries on mobile devices.<br /><br />
RequestAnimationFrame.js emulates the basic usage for old browsers.
</div>
</body>
</html>
What may be causing this error and how do I fix it ?
Ball.animate calls Ball.drawBall and vice versa. So, before either finishes executing, they call each other until the "call stack" size exceeds its limit (causing the error).
Instead of
ball.drawBall(canvas.getContext('2d'),canvas);
try
setInterval(function () { ball.animate(canvas.getContext('2d'),canvas); }, 1000/60);
and remove
this.animate(context,canvas);
from Ball.prototype.drawBall
There are many, many problems with your code, but that's the one you asked about.
I've explained the simple errors. My annotations are in /* */ comments. There are also formatting and style errors, which I've omitted.
<html>
<head>
<meta charset="utf-8">
<script>
window.onload=function(){
if (document.createElement("canvas").getContext){
//alert("browser supports canvas");
//console.log(document.getElementById("canvas").getContext);
canvas = document.getElementById("canvas");
shape = new shapes();
/* drawball should be called by a run() function, or similar */
shape.drawball(canvas,5,"red");
}
};
/**
* Is this supposed to be a singleton, or perhaps a module?
* otherwise, you should use prototype to define these methods
*/
function shapes(){
this.drawtriangle = function(canvas){
/* triangles isn't defined in this file? */
triangles = new triangle(0,0,0,200,200,200);
/* I'd store a reference to context as a property of the function (class)
* aka a "static variable" */
triangles.draw(canvas.getContext('2d'));
}
this.drawball = function(canvas,radius,color) {
ball = new Ball(radius,color);
/* same here */
ball.drawBall(canvas.getContext('2d'),canvas);
}
}
/**
* this is reasonable, but don't pluralize a class name unless you mean it
* I'd maybe call it "Point" or "Vec2d"
*/
function coordinates(x1,y1){
this.x = x1;
this.y = y1;
}
/* so you'd use this object as angle.angle?? Just store it as a scalar.*/
function angle(angle){
this.angle = angle;
}
/* This is correct, I'm sure it's also copy/pasted */
function Ball(radius,color){
this.origin = new coordinates(100,100);
this.radius = (radius === "undefined" ) ? 5 : radius;
this.color = (color === "undefined") ? red : color;
this.rotation = 0;
this.index = 0;
this.angles = new angle(0);
this.speed = 10;
}
/**
* Ball.drawBall()? I'd use Ball.draw()
* I'd again store context in the function object, and you don't need to
* pass canvas
*/
Ball.prototype.drawBall = function (context,canvas){
context.fillStyle = this.color;
context.strokeStyle = "blue";
context.rotate(this.rotation);
context.beginPath();
context.arc(this.origin.x,this.origin.y,this.radius,0,(Math.PI*2),true);
context.closePath();
context.fill();
context.stroke();
/* There is no reason to have your whole animation call here,
* there should only be code to _draw_ the _ball_ (like the function says) */
this.animate(context,canvas);
}
/* This makes sense for a singleton Ball, but in this case animate should be
* on a containing object. The Ball doesn't animate anything, the App does */
Ball.prototype.animate = function (context,canvas){
/* I can't explain this. I'm 99.999% sure it's not what you want. */
var that = this;console.log(".......");
var time = new Date().getTime() * 0.002;
this.origin.x = Math.sin( time ) * 96 + 128;
this.origin.y = Math.cos( time * 0.9 ) * 96 + 128;
//context.clearRect(0,0,1000,1000);
console.log("Animating ... ");
this.origin.x = this.origin.x + this.speed;
this.origin.y = this.origin.y + this.speed;
this.angles.angle = this.angles.angle + this.speed;
/* you complete your cycle here (animate calls drawBall and vice versa) */
window.webkitrequestAnimationFrame(that.drawBall(context,canvas));
}
</script>
<style>
body {
background-color: #bbb;
}
#canvas {
background-color: #fff;
}
</style>
</head>
<body>
<canvas id="canvas" width="1000px" height="1000px">
Your browser dows bot suppoet canvas
</canvas>
</body>
</html>
I want to animate a ball using html5 and i implemented this small script . However, I cannot see any animation .. How do I fix this ?
<html>
<head>
<meta charset="utf-8">
<script>
canvas = document.getElementById("canvas");
window.onload=function(){
if (document.createElement("canvas").getContext){
//alert("browser supports canvas");
//console.log(document.getElementById("canvas").getContext);
canvas = document.getElementById("canvas");
shape = new shapes();
shape.drawball(canvas,100,"red");
}
};
function shapes(){
this.drawtriangle = function(canvas){
triangles = new triangle(0,0,0,200,200,200);
triangles.draw(canvas.getContext('2d'));
}
this.drawball = function(canvas,radius,color) {
ball = new Ball(radius,color);
ball.draw(canvas.getContext('2d'),canvas);
}
}
function coordinates(x1,y1){
this.x = x1;
this.y = y1;
}
function angle(angle,speed){
this.angle = angle;
this.speed = speed;
}
function Ball(radius,color){
this.origin = new coordinates(100,100);
this.radius = (radius === "undefined" ) ? 40 : radius;
this.color = (color === "undefined") ? red : color;
this.rotation = 0;
this.index = 0;
this.angles = new angle(0,0.2);
}
Ball.prototype.draw = function(context,canvas){
context.fillStyle = this.color;
context.strokeStyle = "blue";
context.rotate(this.rotation);
context.beginPath();
context.arc(this.origin.x,this.origin.y,this.radius,0,(Math.PI*2),true)
context.closePath();
context.fill();
context.stroke();
this.animate(context,canvas);
}
Ball.prototype.animate = function(context,canvas){
if (this.angles.angle < 1){
context.clearRect(0,0,1000,1000);
console.log("Animating ... ");
this.origin.x = this.origin.x + 10;
this.origin.y = this.origin.y + 10;
this.angles.angle = this.angles.angle + this.angles.speed;
window.requestAnimationFrame(this.draw(context));
}
}
</script>
<style>
body {
background-color: #bbb;
}
#canvas {
background-color: #fff;
}
</style>
</head>
<body>
<canvas id="canvas" width="1000px" height="1000px">
Your browser dows bot suppoet canvas
</canvas>
</body>
</html>
Your call to requestAnimationFrame() is not passing a callback, it's executing a function and passing its return value which is undefined. I would suggest you change this:
Ball.prototype.animate = function(context,canvas) {
if (this.angles.angle < 1) {
context.clearRect(0,0,1000,1000);
console.log("Animating ... ");
this.origin.x = this.origin.x + 10;
this.origin.y = this.origin.y + 10;
this.angles.angle = this.angles.angle + this.angles.speed;
window.requestAnimationFrame(this.draw(context));
}
}
to this:
Ball.prototype.animate = function(context,canvas) {
if (this.angles.angle < 1) {
context.clearRect(0,0,1000,1000);
console.log("Animating ... ");
this.origin.x = this.origin.x + 10;
this.origin.y = this.origin.y + 10;
this.angles.angle = this.angles.angle + this.angles.speed;
var self = this;
window.requestAnimationFrame(function() {self.draw(context)});
}
}
so that you pass an appropriate callback function to requestAnimationFrame().
Now that you've included all the code, here is another issue. You can't do this:
canvas = document.getElementById("canvas");
in javascript in the head tag because the DOM is not yet loaded so it will not find that object. You must do that only when the DOM has been loaded either by waiting for an event that signifies the DOM has been loaded or by running the javascript at the every end of the <body> section AFTER all DOM elements.
Then, thirdly, you have to use the browser-specific form of requestAnimationFrame since each browser may have it's own prefix. I used this code:
var reqestAnimationFrame =
window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.msRequestAnimationFrame ||
window.webkitRequestAnimationFrame;
When I put your script into a jsFiddle and make the above changes, what I find is that the animation runs so quickly that it isn't seen. Your code will need to add a time element to it so that the animation runs over a particular time period. Usually this is done by defining a duration for the animation and at each animation step, you scale the position of the animation based on what percentage of the duration has elapsed.
Here's an example of a time-based animation using requestAnimationFrame: http://jsfiddle.net/jfriend00/nRE7S/