I am trying to implement a wating animation of the kind mentioned in this question, particularly something that looks like this:
But I do not want to use graphic files, and am trying to implement it purely in html5 canvas and javascript. Also, I want a circular black background rather than a square one. As a first step, I tried to draw a static frame (without any movement/rotation) and did this:
<html>
<head><script>
window.onload = function(){
var c=document.getElementById("waiting").getContext("2d");
c.lineCap="round";
c.fillStyle="#353535";
c.translate(100,100);
function slit(p){
shade = 256*p;
th = 2*Math.PI*p;
cos = Math.cos(th);
sin = Math.sin(th);
c.strokeStyle = '#'+((shade<<16)+(shade<<8)+shade).toString(16);
c.moveTo(55*cos, 55*sin);
c.lineTo(84*cos, 84*sin);
c.stroke();
c.closePath();
}
c.lineWidth=0;
c.arc(0,0,100,0,Math.PI*2);
c.fill();
c.lineWidth=7;
for(var i = 0;i<1;i+=0.05){slit(i);}
}
</script></head>
<body><canvas id="waiting" width=200 height=200></canvas></body>
</html>
The result I get is:
The problem is that, the lineCap="round" is not working correctly for all of the "slits", and the lineWidth=0 attribute is not working for the edge of the circle. What am I doing wrong? I checked it with Chrome 16.0.912.63 and Firefox 10.0, and got similar results.
For the next step, I am going to let parts of the functions that I created above interact with
window.animationFrames = (function(callback){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback){window.setTimeout(callback, 1000 / 60);};
})();
but for the time being, I need to solve this problem first.
There's a bit of confusion here.
Zero is not an acceptable value for line width. The spec dictates that if you say lineWidth = 0 that it will be a no-op.
Furthermore you are not using lineWidth there because you are not stroking. fill() does not take line width into account.
As for the other issue, all you have to do is call beginPath! See here:
http://jsfiddle.net/JfcDL/
Just adding the beginPath call and you'll get this with your code:
What you were doing incorrectly was drawing the entire path so far with every new stroke(). You need to call beginPath to open a new one so that the stroke only applies to the last part and not all the parts made so far.
Thanks to the help by the several people here, I finally came up with this code, which works fully with the movement:
<html>
<head><script>
var r1 = 400;
var r2 = 340;
var r3 = 220;
var slitWidth = 40;
var speed = 0.0004;
var attenuation = 1.7;
function rgbToHex(r, g, b){
return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
}
window.nextFrame = (function(callback){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback){window.setTimeout(callback, 1000 / 60);};
})();
window.onload = function(){
var waiting=document.getElementById('waiting').getContext('2d');
function slit(d,p){
shade = Math.round(Math.pow(1-(d+p)%1, attenuation)*255)
th = Math.PI*2*(p);
cos = Math.cos(th);
sin = Math.sin(th);
waiting.strokeStyle = rgbToHex(shade, shade, shade);
waiting.beginPath();
waiting.moveTo(r2*cos, r2*sin);
waiting.lineTo(r3*cos, r3*sin);
waiting.stroke();
waiting.closePath();
}
function frame(){
waiting.arc(0,0,r1,0,Math.PI*2);
waiting.fill();
var time = new Date().getTime()* speed;
for(var p = 1;p>0;p-=0.05){slit(time,p);}
nextFrame(function(){frame();});
}
waiting.lineCap='round';
waiting.lineWidth=slitWidth;
waiting.fillStyle='#353535';
waiting.translate(r1, r1);
frame();
}
</script></head>
<body><canvas id='waiting' width=800 height=800></canvas></body>
</html>
Related
I am trying to use the method 'requestAnimationFrame' with the help I can find on Internet. I can move an element, but when I want to do it with sprites, I am lost.
For example, the code below is working normally with 'setInterval', but I cannot manage to make it work with 'requestAnimationFrame'.
<!DOCTYPE html>
<html>
<head>
<title>Animating Sprites in HTML5 Canvas</title>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, user-scalable=no">
<style>
.demo {background: #aaaaaa;}
#myCanvas{background: #cccccc}
</style>
</head>
<body class="demo">
<canvas id="myCanvas" width="800" height="100"></canvas>
<script>
(function() {
// Canvas
var canvas = document.getElementById('myCanvas');
var ctx = canvas.getContext('2d');
// Set the fill style for the drawing context.
ctx.fillStyle = '#3399ff';
// VARIABLES
var width = 48; // Width CANVAS
var height = 60; // Height CANVAS
var xFrame = 0; // Frame x coordinate
var yFrame = 0; // Frame y coordinate
var dxFrame = 0; // Frame dx position in canvas
var dyFrame = 0; // Frame dy position in canvas
// SPRITE used
image = new Image()
image.src = 'myRunner2.png';
//
var requestID;
// shim layer with setTimeout fallback
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(/* function FrameRequestCallback */ callback, /* DOMElement Element */ element){
window.setTimeout(callback, 1000 / 60);
};
})();
// FUNCTION DRAWING MOVE (xFrame = 0 & yFrame = 1)
var drawMove = function(){
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(image, width * xFrame, height * yFrame, width, height, dxFrame, dyFrame, width, height);
if (xFrame == 7) {
xFrame = 0;
} else {
xFrame++
dxFrame+=2;
}
//window.requestAnimFrame(eMove);
}
// ANIMATION in 3 moves: Idle + Move + Tired
var intA;
function eMove() {
// Runner in motion (8 frames)
yFrame = 1;
xFrame = 0;
clearInterval(intA);
intA = setInterval(drawMove, 100);
}
eMove();
}());
</script>
</body>
</html>
I am looking for help about this issue, a portion of code will be great but a way to work or a direction to look for will be good as well. How to manipulate sprite with the method 'requestAnimationFrame'?
Ultimately, my goal is to move a sprite in one direction and the background in the other direction. I can move the sprite in one direction with setTimeout/setInterval methods alone or I can move in the other direction the background with 'requestAnimationFrame', but also separately.
I hope you understand my problem.
Thank you,
JLuc01
For requestAnimationFrame to work well it needs an accurate timer by which it can update the progress. The animation would then also depend on this variable. Of course a total duration will have to be set as well (to measure the progress). Here's a general piece of code :
var initial = update = new Date().getTime(), progress = 0, duration = 2000;
requestAnimationFrame(frameSequence);
function frameSequence() {
update = new Date().getTime();
var elapsed = update-initial;
progress = Math.max(elapsed/duration, 1);
someFunction(); // do calculations and implement them based on progress
if (progress < 1) requestAnimationFrame(frameSequence);
}
And a live example (relevant code at the bottom)
http://codepen.io/Shikkediel/pen/vEzqoX?editors=001
Edit - some comments promoted to update :
The requestAnimationFrame call is just a basic loop really to replace the timeout. It could be as simple as using requestAnimationFrame(drawMove) instead of clearInterval(intA); intA = setInterval(drawMove, 100). It'll probably do the whole thing in 8/60 of a second that way though (I see there are 8 frames and 60 is the common display refresh rate) - hence a timer would be needed.
This would optimise and work for sure : setInterval(requestAnimationFrame(drawMove), 100). It will not force a frame on the display then like a timeout does (giving performance issues and flickering) but make it wait for the first appropriate instance when there is a new paint of the screen. But not using timeouts at all is a much better approach.
I've gotten a lot of help from this site, but I seem to be having a problem putting all of it together. Specifically, in JS, I know how to
a) draw an image onto canvas
b) make a rectangle follow the cursor (Drawing on a canvas) and (http://billmill.org/static/canvastutorial/ball.html)
c) draw a rectangle to use as a background
What I can't figure out is how to use a rectangle as the background, and then draw an image (png) on the canvas and get it to follow the cursor.
What I have so far looks like this:
var canvas = document.getElementByID('canvas');
var ctx = canvas.getContext('2d');
var WIDTH = canvas.width;
var HEIGHT = canvas.height;
var bgColor = '#FFFFFF';
var cirColor = '#000000';
clear = function() {
ctx.clearRect(0, 0, WIDTH, HEIGHT);
}
drawIMG = function(x,y,r) {
ctx.fillStyle = cirColor;
ctx.beginPath();
ctx.arc(x, y, r, 0, Math.PI*2, true);
ctx.closePath();
ctx.fill();
}
draw = function() {
ctx.fillStyle = bgColor;
clear();
ctx.fillRect(0, 0, WIDTH, HEIGHT);
drawIMG(150, 150, 30);
drawIMG(300, 500, 12);
};
draw();
This will draw in the HTML5 canvas element, the height and width of which are specified in the HTML and so are variable, with a white rectangle the size of the canvas beneath two black circles at (150,150) and (300,500). It does that perfectly well.
However, I don't know how to also make JS draw a .png on top of that that follows the cursor. Like I said, I've been able to do most of the steps individually, but I have no idea how to combine them. I know, for instance, that I have to do
img = new Image();
and then
img.src = 'myPic.png';
at some point. They need to be combined with position modifiers like
var xPos = pos.clientX;
var yPos = pos.clientY;
ctx.drawImage(img, xPos, yPos);
But I have no idea how to do that while maintaining any of the other things I've written above (specifically the background).
Thanks for your patience if you read through all of that. I have been up for a while and I'm afraid my brain is so fried I wouldn't recognize the answer if it stripped naked and did the Macarena. I would appreciate any help you could possibly send my way, but I think a working example would be best. I am an initiate in the religion of programming and still learn best by shamelessly copying and then modifying.
Either way, you have my optimistic thanks in advance.
First off, I've made an animated purple fire follow the mouse. Click (edit doesn't exist anymore)here to check it out.
Before you continue, I recommend you check out these websites:
http://www.williammalone.com/articles/create-html5-canvas-javascript-sprite-animation/
William talks about the basic techniques of canvas animations
http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
Paul Irish talks about a recursive animation function that turns at 60 fps.
Using both of their tutorials is pretty a good start for animation.
Now from my understanding you want one 'background' and one animation that follows the cursor. The first thing you should keep in mind is once you draw on your canvas, whatever you draw on, gets replaced. So the first thing I notice that will cause performance issues is the fact you clear your whole canvas, and not what needs to be cleared.
What you need to do is memorize the position and size of your moving element. It doesn't matter what form it takes because your clearRect() should completely remove it.
Now you're probably asking, what if I draw on the rectangle in the background. Well that will cause a problem. You have two solutions. Either, (a) Clear the background and clear your moving animation and draw them back again in the same order or (b) since you know your background will never move, create a second canvas with position = absolute , z-index = -1 , and it's location the same as the first canvas.
This way you never have to worry about the background and can focus on the animation currently going on.
Now getting back to coding part, the first thing you'll want to do is copy Paul Irish's recursive function:
(function() {
var lastTime = 0;
var vendors = ['webkit', 'moz'];
for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
window.cancelAnimationFrame =
window[vendors[x]+'CancelAnimationFrame'] || 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);
};
}());
Question then is, how to use it? If you go here you can check out how it was done:
function fireLoop()
{
window.requestAnimationFrame(fireLoop);
fire.update();
fire.render();
console.log('you spin me right round baby right round');
follow();
}
This is the loop I use. Every second Paul Irish's function will call the main loop. In this loop. I update the information choose the right animation that needs to be drawn and then I draw on the canvas (after having removed the previous element).
The follow function is the one that chooses the next coordinates for the animation. You'll have to change this part since, you don't want to move the canvas but move the animation. You can use the same code, but you need to apply location to where you want to draw on the canvas.
function follow()
{
$(fireCanvas).offset({
top: getTop(),
left: getLeft()
});
}
function getTop()
{
var off = $(fireCanvas).offset();
if(off.top != currentMousePos.y - $(fireCanvas).height() + 10)
{
if(off.top > currentMousePos.y - $(fireCanvas).height() + 10)
{
return off.top - 1;
}
else
{
return off.top + 1;
}
}
}
function getLeft()
{
var off = $(fireCanvas).offset();
if(off.left != currentMousePos.x - $(fireCanvas).width()/2)
{
if(off.left > currentMousePos.x - $(fireCanvas).width()/2)
{
return off.left - 1;
}
else
{
return off.left + 1;
}
}
}
var currentMousePos = { x: -1, y: -1 };
$(document).mousemove(function(event) {
currentMousePos.x = event.pageX;
currentMousePos.y = event.pageY;
});
If you want me to go into depth about anything specific let me know.
I need to rotate an image inside an canvas..I have googled and saw similar questions in stackoverflow.What i learnt is
I cannot rotate single object inside canvas.
I can rotate only thewhole canvas.
So i need to translate to center of the object and then rotate the
canvas.
I Followed exactly same stuffs , But i am struck at translating to the image center..Below is the code i am using.Here is the jsfiddle http://jsfiddle.net/J6Pfa/1/. This one is rotating the whole canvas regardless image..some one guide me where i am wrong.Thanks
//hero canvas
ball = new Hero();
var ang = 0;
herocanvas = document.createElement("canvas");
herocanvas.width = herocanvas.width = 500;
herocanvas.height = herocanvas.height = 500;
heroctx = herocanvas.getContext('2d');
document.body.appendChild(herocanvas);
var requestAnimFrame = window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.msRequestAnimationFrame ||
window.oRequestAnimationFrame;
var imgSprite = new Image();
imgSprite.src = 'http://i.imgur.com/WTgOHGg.png';
imgSprite.addEventListener('load',init,false);
function init()
{
startloop();
}
function startloop()
{
heroctx.save(); //saves the state of canvas
clearherobg() ; // clear canvas
heroctx.translate(ball.drawX, ball.drawY); //let's translate
heroctx.rotate(Math.PI / 180 * (ang += 4));
ball.draw(); // draw image here
heroctx.restore();
requestAnimFrame(startloop);
}
function Hero() {
this.srcX = 0;
this.srcY = 500;
this.drawX = 220;
this.drawY = 200;
this.width = 100;
this.height = 40;
this.speed = 5;
this.isUpKey = false;
this.isRightKey = false;
this.isDownKey = false;
this.isLeftKey = false;
}
Hero.prototype.draw = function () {
heroctx.drawImage(imgSprite,this.srcX,this.srcY,this.width,this.height,this.drawX,this.drawY,this.width,this.height);
};
function clearherobg() {
heroctx.clearRect(0,0,500,500);
}
Those who cannot read full code ..Please check startloop() , thats the main infinite loop..ball.drawX and ball.drawY are the x and y position of image inside canvas
Hero's drawX and drawY need to be updated to new coordinate system of canvas, if u want it to be in the middle of canvas it needs to be 0,0.
Something like this: jsfiddle.net/J6Pfa/3
Hm, i dont exactly understand what you want to achieve, an image moving on the circle path ? or having image in one place, and just rotate it ?
I was trying to get the canvas to display the i in a loop - I want the i to be changing just like the counting t, from 1-9 non-stop. I just couldn't figure out what's wrong. The javascript looks like this:
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback){window.setTimeout(callback, 1000/60)};
})();
var cvs = document.getElementById("canvasId"),
c = cvs.getContext("2d"),
t = 0;
window.onload=function loop(){
window.requestAnimFrame(loop);
t++;
for(i=0;i<10;i++){
c.clearRect(0,0,cvs.width,cvs.height);
c.font = "bold 90px Arial";
c.fillText(i + " " + t, 100, 200);
}
};
http://jsfiddle.net/luxiyalu/9UZU5/
This is part of a mini game and I've been stuck in here for 2 days; if anyone could tell me what's wrong with it... Thanks a lot!
As you're iterating i from 0 to 9 in your drawing function, there is no time left between two drawings and the user can only see the last iteration value, that is 9.
I'm not sure of what you really want to achieve but it looks like you want this :
var cvs = document.getElementById("canvasId"),
c = cvs.getContext("2d"),
t = 0,
i = 0;
window.onload = function loop(){
window.requestAnimFrame(loop);
i++;
if (i==10) {
i = 0;
t++;
}
c.clearRect(0,0,cvs.width,cvs.height);
c.font = "bold 90px Arial";
c.fillText(i + " " + t, 100, 200);
};
Demonstration
I have a huge performance issue on iOS html5 webapp when I modify the position of multiple html elements in CSS. I would like also to move manually my elements. I do not want to use CSS transformation because it is not possible to stop the animation (we are making a highly responsive game).
My example works fine on a desktop browser (chrome, firefox, etc.), on Android. But it is very slow on an iPad 2 and an iPhone 4S (both running iOS 5.1).
Running the html5 code in a Phonegap app is better than directly in the browser but is still slow.
What do you suggest to improve things?
editable example
full screen example
First of all, if you want something that is not slow, avoid all jQuery call you can.
Here is how I would rewrite (really quickly) your code :
// shim layer with setTimeout fallback
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function( callback ){
window.setTimeout(callback, 1000 / 60);
};
})();
var canvas = document.getElementById('canvas-test');
canvas.height = 500;
canvas.width = 500;
var context = canvas.getContext('2d');
// in this example, the fillstyle is always the same. no need to change it at every loop
context.fillStyle = "#FF0000";
var balls = [];
var ballcanvas = [];
var ballctx = [];
// create 30 balls in canvases
var eDivBody = document.getElementById('divbody');
for (var i = 0; i < 30; i++){
balls[i] = {
x : 250,
y : 100 + i * 2,
dx : 3, // direction
};
// create the canvas
var eBall = document.createElement('canvas');
eBall.id = 'ballcanvas' + i;
eBall.width = 75;
eBall.height = 75;
eDivBody.appendChild(eBall);
// some css
// no need for jQuery
eBall.style.position = "absolute";
eBall.style.left = balls[i].x + "px";
eBall.style.top = balls[i].y + "px";
eBall.style.backgroundColor = "#000000";
// associate the element to the ball, no need to go threw the DOM after
balls[i].element = eBall;
}
var ball_test = {
x : 250,
y : 300,
dx : 3 // direction
};
function loop(ball_test, balls, canvas, context, ballcanvas, ballctx){
//change of direction on the sides
if (ball_test.x > 400 || ball_test.x < 100)
ball_test.dx *= -1;
// movement
ball_test.x += ball_test.dx;
// the same for balls in canvases
// never use array.legth in a loop condition. put it in a variable then compare.
for (var i = 0, j = balls.length; i < j; i++){
// balls are following the test ball, no need to re-check the bounds
// we take the test ball direction
balls[i].dx = ball_test.dx;
//movement
balls[i].x += balls[i].dx;
// change left style - No need for jQuery
balls[i].element.style.left = balls[i].x + "px";
}
// display ball_test
displayBallTest(ball_test, canvas, context);
// Prefer the use of requestAnimationFrame
requestAnimFrame(function(){
loop(ball_test, balls, canvas, context, ballcanvas, ballctx);
});
};
// no need to recalculate Math.PI * 2 for every loop.
// do it just the first time then use the value
var pi2 = Math.PI * 2;
function displayBallTest(ball, canvas, context){
// clear canvas
// you don't need to clear all the canvas, just the zone where you now the ball is.
// must need some calculation to be the most efficient possible
context.clearRect(ball.x - 50 , ball.y - 50, 100, 100);
context.beginPath();
context.arc(ball.x, ball.y, 40, 0, pi2 );
context.fill();
};
// start main loop
loop(ball_test, balls, canvas, context, ballcanvas, ballctx);
I commented the code but here are what I did :
totally avoiding jQuery. No need, except maybe for the ready if you choose to not put your script at the end of the content
using requestAnimationFrame when possible
avoiding recalculation or reset of values when they are global
(Math.PI*2 , context.fillStyle ... )
avoiding the use of .length if for loop condition
But I think your problem come from the fact that you want to move 30 canvas elements instead of drawing theyre content into the main canvas.
iOS is known to be fast when you use Canvas Drawing.
For me, your performance problems will be resolved if you choose to draw on the main canvas instead of moving DOM elements.
One obvious thing you can do is cache your selector instead of executing it every time:
// some css
$('#ballcanvas' + i).css("position", "absolute");
$('#ballcanvas' + i).css("left", balls[i].x + "px");
$('#ballcanvas' + i).css("top", balls[i].y + "px");
$('#ballcanvas' + i).css("background-color", "#000000");
Should be something like:
var thisBall = $('#ballcanvas' + i)
thisBall.css("position", "absolute");
... rest of your code ....
Aside: Why bother using document.getElementById, when you already have Jquery $.