I recently got about doing some HTML5/Canvas stuff and was going about my business quite happily, testing stuff in Chrome, until I decided to try what I have been working on in Firefox... doesn't work so good.
This is a bare bones example of the kind of stuff I'm doing. Setting up the basic requestAnimationFrame shim, the main loop clears the canvas and then updates and draws my objects. Easy enough, examples about this stuff are every where to be found.
function loop() {
canvas.width = canvas.width;
requestAnimFrame(loop);
rR.update();
rG.update();
rB.update();
rY.update();
rR.draw();
rG.draw();
rB.draw();
rY.draw();
}
function Rect(color, x, y, speedX, speedY) {
this.x = x;
this.y = y;
this.color = color;
this.speedX = speedX;
this.speedY = speedY;
}
Rect.prototype.draw = function () {
context.fillStyle = this.color;
context.beginPath();
context.rect(this.x, this.y, 10, 10);
context.closePath();
context.fill();
};
Rect.prototype.update = function () {
if (this.x < 0 || this.x > canvas.width) this.speedX = -this.speedX;
if (this.y < 0 || this.y > canvas.height) this.speedY = -this.speedY;
this.x += this.speedX;
this.y += this.speedY;
};
var rR = new Rect("#FF0000", canvas.width/2, canvas.height/2, 2, 2);
var rG = new Rect("#00FF00", canvas.width/2, canvas.height/2, -2, -2);
var rB = new Rect("#0000FF", canvas.width/2, canvas.height/2, 2, -2);
var rY = new Rect("#FFFF00", canvas.width/2, canvas.height/2, -2, 2);
http://jsfiddle.net/Polaris666/psDM9/3/
When I test that in Chrome it looks great, but Firefox has a lot of stuttering and tearing, for what seems a rather simple task.
I have found similar questions but none with a good clear solution. Is this a Firefox thing? Are Webkit browsers just better at doing this? Should I just give up on it and hope it is fixed in future versions of the browser? Or maybe it is my particular set up? I'm using Windows 7 64bit with FireFox 17.0.1.
Any help is appreciated.
The solution #HakanEnsari provided seems to work. I was curious about the reason and found that it's because his version of the code doesn't clear the entire canvas. It only clears the individual 10x10 Rects and leaves the rest of the canvas alone.
This goes into it a bit, and also has a lot of other useful canvas performance tips:
http://www.html5rocks.com/en/tutorials/canvas/performance/#toc-render-diff
So you want this:
function loop() {
// get rid of this
//canvas.width = canvas.width;
requestAnimFrame(loop);
Just clear the individual rects
Rect.prototype.update = function () {
if (this.x < 0 || this.x > canvas.width) this.speedX = -this.speedX;
if (this.y < 0 || this.y > canvas.height) this.speedY = -this.speedY;
// clear just the rect
context.clearRect(this.x, this.y, 10, 10);
this.x += this.speedX;
this.y += this.speedY;
};
(tweaked fiddle: http://jsfiddle.net/shaunwest/B7z2d/1/)
Apparently, clearing the canvas with canvas.width = canvas.width; caused the lag on Safari (I'm browsing with version 5.1.9).
I've never used that way of clearing the screen: instead, I use this one:
context.clearRect(0,0, canvas.width, canvas.height);
If you try it out, it shouldn't lag anymore. See jsfiddle.
This is the fastest way to clear the canvas: on the contrary, clearing each individual element requires you to:
keep track of every element position
perform a clearRect call for every element you want to redraw
and also wouldn't work for shapes other than a rectangle (as there is no clearSphere or clearPath method).
Another reason of the stutters is that until FireFox24, the animations of FireFox was not fully synchronized to the refresh rate (VSYNC), especially if the refresh rate was not exactly 60Hz.
It has to do with the end of section 5 of W3C recommendation, http://www.w3.org/TR/animation-timing/ for the browsers to synchronize animations to the refresh rate. It now runs almost equally as smoothly in both Chrome and FireFox on Windows, since FireFox 24.
TestUFO lists all the supported browsers (that can sync requestAnimationFrame() to the refresh rate) at http://www.testufo.com/browser.html
Related
I am making a simple game in HTML Canvas. As a part of it, in the background i want to make falling stars which create the illusion of travelling. After the star reaches the end of the canvas i want to remove it. Each star is an instance of Star class, and depending on it's radius it has a different velocity. This is where problems start. When using constant velocity for every star, they disappear one by one like they should be. When velocity is changed, stars that "overtake" slower stars, when reached the end of the canvas, do not only dissapear themselves but also remove every star that was in array before them.
I have tried many solutions described below:
let canvas = document.querySelector("canvas");
let c = canvas.getContext('2d');
Star Class declaration:
class Star{
constructor(x, y, radius, color, velocity){
this.x = x;
this.y = y;
this.radius = radius;
this.color = color;
this.velocity = velocity
}
draw(){
c.globalCompositeOperation='destination-over'
c.beginPath()
c.arc(this.x, this.y, this.radius, 0, Math.PI*2, false);
c.fillStyle = this.color;
c.shadowColor= "white"
c.shadowBlur=12
c.fill();
c.shadowBlur=0
}
update(){
this.draw();
this.y = this.y + this.radius/2;
}
}
Creating stars and adding it to array
let stars = [];
function createStar(){
setInterval(()=>{
//Create random radius and X position
let randomRadius = Math.floor((Math.random()*5))
let randomXPosition = Math.floor((Math.random()*780)+15)
let velocity = 1;
stars.push(new Star(randomXPosition, -randomRadius, randomRadius, "white",velocity));
console.log("stars:"+ stars.length);
},300)
}
Below here I use a function calling itself to clear and refresh the star drawing. I have tried looping through stars array with forEach method, reversed loop (like in example below), I tried putting the if statement with splice function in seperate setTimeout(()=>{},0) or setTimeout(()=>{},10). I tried using the condition like
(forEach method removes stars, however the number of active stars do not remain more or less the same. It constantly slowly increases)
function animate(){
c.clearRect(0, 0, canvas.width, canvas.height);
for(let i = stars.length-1; i>=0; i--){
stars[i].update()
if (stars[i].y > canvas.height +stars[i].radius ){
stars.splice(stars[i], 1)
}
}
requestAnimationFrame(animate);
}
animate();
createStar();
I tried using the condition like:
if (stars[i].y > 5000 ){
stars.splice(stars[i], 1)
}
But it's not how it's supposed to be solved, because stars live for 5000 pixels longer,what makes game laggy.
Just to be clear on the problem.
If i generate 5 stars every 300ms and push the into the array called "stars" I get e.g.
[star1, star2, star3, star4, star5]
Lets say star1 and star 2 have small radius and they move slowly. Star3 is bigger therefore it moves faster and overtakes first two. When star3 reaches canvas.height + star[i].radius it disappear just over the canvas, but it also makes every star in array that was before star3 disappear (in this case it's star1 and star2).
This is my first post on stackoverflow. I apologise for all understatements in advance.
HTML Canvas
<body>
<div class="container">
<button class="logOutButton">Log Out</button>
<button class="topScores">Top 10</button>
<header>
<p class="hello">Hello <span id="spanName"></span></p>
<p class="topScore">Your TOP score: <span id="score"></span></p>
</header>
<div class="gameTitle">
<h2 class="title">Space Warrior</h2>
</div>
<canvas class="canvas" width="800" height="500"></canvas>
</div>
<script src="User.js"></script>
</body>
EDIT
I changed stars.splice(stars[i], 1) to stars.splice(i, 1) - not working
I tried adding another removal array like below but array stars just slowly gets bigger (even though some elements get removed)
var removeStar = [];
// stars.forEach((star,starIndex)=>{
let total = stars.length-1;
for(let i = total; i>=0; i--){
stars[i].update();
if (stars[i].y > canvas.height + stars[i].radius){
removeStar.push(i);
}
};
for(let i = removeStar.length; i>0; i--){
stars.splice(removeStar[i-1],1)
}
you wrote:
stars.splice(stars[i], 1)
first argument should be index which you want to remove ...just index stars.splice(i, 1)
..another problem is that you changing the array while looping within it, which is bad idea.
see this answer to very similar question:
https://stackoverflow.com/a/65725703/3054380
After fixing the bug mentioned above and adding minVelocity and star limiting condition (because you adding with interval and removing when star goes off the canvas - we need to limit in case interval goes faster) ...now everything looks good - working snippet below (added maxStars minVelocity)
let canvas = document.querySelector(".mycanvas");
let c = canvas.getContext('2d');
canvas.width = 300;
canvas.height = 150;
let maxStars = 60;
let minVelocity = 0.5;
let stars = [];
class Star {
constructor(x, y, radius, color, velocity) {
this.x = x;
this.y = y;
this.radius = radius;
this.color = color;
this.velocity = velocity
}
draw() {
c.globalCompositeOperation = 'destination-over'
c.beginPath()
c.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false);
c.fillStyle = this.color;
c.shadowColor = "white"
c.shadowBlur = 12
c.fill();
c.shadowBlur = 0
}
update() {
this.draw();
this.y = this.y + Math.max(minVelocity, this.velocity * this.radius / 2);
}
}
function createStar() {
if (stars.length < maxStars) {
//Create random radius and X position
let randomRadius = Math.floor((Math.random() * 5));
let randomXPosition = Math.floor((Math.random() * (canvas.width - 20)) + 10);
let velocity = 1;
stars.push(new Star(randomXPosition, -randomRadius, randomRadius, "white", velocity));
console.log("stars:" + stars.length);
}
}
function animate() {
c.clearRect(0, 0, canvas.width, canvas.height);
for (let i = stars.length - 1; i >= 0; i--) {
stars[i].update()
if (stars[i].y > canvas.height) {
stars.splice(i, 1)
}
}
requestAnimationFrame(animate);
}
setInterval(createStar, 50);
animate();
.mycanvas {
width: 300px;
height: 150px;
border: 1px solid #f00;
}
<body style="background-color:#000;color:#fff;">
<div class="container">
<canvas class="mycanvas"></canvas>
</div>
<script src="User.js"></script>
</body>
I think I found the issue causing your everexpanding array. It has nothing to do with your splice function, which is implemented incorrectly as webdev-dan mentioned in his answer.
follow this logic
The radius has been set with Math.floor((Math.random()*5)).This means the radius can be 0.
In your update method you are increasing y based on the radius in this.y = this.y + this.radius/2. So this is possibly changing y with 0.
In if (stars[i].y > 5000 ) you are removing values of y over 5000.
So if the radius is 0, y doesn't change, stars are never removed. Nor can you see them visually.
solution
You could guarantee a minimal speed of 1. this.y += Math.max(1, this.radius/2).
PS: I had to do quite a bit of refactoring to figure this out. You are writing your code in a too complex way. Several types of logic are mixed up.
You really want to separate out your rendering logic from management of the stars object.
It is also quite hard to mentally keep track of the stars array because you are modifying it from anywhere; inside for loops, with async code ( the interval ).
This is what I ended up while trying to clean up your code a bit: https://jsfiddle.net/5hk0vscg/1/ Hope it's useful. Note that it is not a full cleanup, but it's an improvement over your current code.
I'm in the middle of creating this simple animation using HTML5 Canvas and JavaScript and I'm experiencing a problem with flickering objects.
I was trying to find the solution on the internet before I asked this question and all I found was basically:
avoid loading new image , object at each new frame
use requestAnimationFrame()
I think I've done that all and the flickering is still happening.
(blue rectangles (obstacles) in my case.
The only solution that works is reducing the number of pixels in method responsible for moving the object, here:
obstacle.prototype.moveObstacle = function(){
this.x -=3
}
but the the animation is too slow.
Is there any way around it?
JSFiddle: https://jsfiddle.net/wojmjaq6/
Code:
var cnv = document.getElementById("gameField");
var ctx = cnv.getContext("2d");
var speedY = 1
var obst1 = new obstacle(cnv.width + 50);
var myBird = new bird(100, 1);
function bird(x, y) {
this.x = x;
this.y = y;
this.gravity = 0.3
this.gravitySpeed = 0
}
bird.prototype.drawbird = function() {
ctx.fillStyle = "red"
ctx.fillRect(this.x, this.y, 20, 20);
}
bird.prototype.animate = function() {
this.gravitySpeed += this.gravity
this.y += speedY + this.gravitySpeed
}
function obstacle(x) {
this.x = x;
this.y = 0;
this.obstLen = Math.floor(Math.random() * 400)
}
obstacle.prototype.drawobstacle = function() {
ctx.fillStyle = "blue";
ctx.fillRect(this.x, this.y, 15, this.obstLen)
ctx.fillRect(this.x, cnv.height, 15, -(cnv.height - this.obstLen - 100))
}
obstacle.prototype.moveObstacle = function() {
this.x -= 3
}
function myFun() {
ctx.clearRect(0, 0, cnv.width, cnv.height);
myBird.animate();
myBird.drawbird();
obst1.moveObstacle();
obst1.drawobstacle();
if (obst1.x < 0) {
obst1 = new obstacle(cnv.width + 50);
}
window.requestAnimationFrame(myFun)
};
function test() {
if (myBird.gravity > 0) {
myBird.gravity = -1
} else {
myBird.gravity = 0.3
}
}
document.getElementById("gameField").onmousedown = test
document.getElementById("gameField").onmouseup = test
window.requestAnimationFrame(myFun)
I do see some stuttering with the blue obstacle - the animation is not smooth.
Changing the x position of the obstacle based on the raw requestAnimationFrame loop will not necessarily result in a smooth operation as requestAnimationFrame just requests that the browser re-draws when it can.
The time between calls to requestAnimationFrame can vary depending on the power of the device the animation is on and how much there is to do each frame. There is no guarantee that requestAnimationFrame will give you 60 FPS.
The solutions are to decouple the changing of objects positions with the actual drawing of them, or factor it the elapsed time between frames and calculate the new position based on that to give a smooth animation.
Normally in my canvas animations I just use a library like GreenSock's Animation Platform (GSAP) https://greensock.com/get-started-js which can animate any numeric property over time, then I only have to write code for the drawing part.
It is possible to compute a time based animation in your own requestAnimationFrame, though there is a bit of complexity involved. This looks like a good tutorial on it http://www.javascriptkit.com/javatutors/requestanimationframe.shtml
Cheers,
DouG
I am writing a game engine for Javascript and am attempting to allow my sprites to be rotated.
However, when I rotate my sprites, the image gets skewed. It rotates, but it is not the correct dimensions.
I am following the same basic logic that I use when programming in Java with the Image object and 2d libraries but getting different results. (Image skewed and shouldn't be... just needs to be rotated)
function Sprite(imgg,w,h)
{
this.img = imgg;
this.x = 350;//Math.random()*700;
this.y = 350;//Math.random()*700;
this.vx = 0;//Math.random()*8-4;
this.vy = 0;//Math.random()*8-4;
this.width = w;
this.height = h;
this.rotatespeed = 0;//0.01;
this.rotate = 40;
}
function drawSprite(sprite, ctx)
{
ctx.save();
ctx.translate(sprite.x,sprite.y);
ctx.rotate(sprite.rotate);
ctx.drawImage(sprite.img,0,0,sprite.img.width,sprite.img.height,-sprite.width/2,-sprite.height,sprite.width,sprite.height);
ctx.restore();
}
function drawGame(g)
{
var gameLoop = setInterval(function(){
g.context.clearRect(0,0,g.canvas.width, g.canvas.height);
g.context.save();
g.context.translate(g.canvas.width/2, g.canvas.height/2);
g.context.scale(g.camera.scale,g.camera.scale);
g.context.rotate(g.camera.rotate);
g.context.translate(g.camera.x,g.camera.y);
for(var i=0;i<g.objects.length;i++)
{
updateSprite(g.objects[i]);
drawSprite(g.objects[i], g.context);
}
g.context.restore();
},setIntervalAmount);
}
Turns out that everything was okay, but the web browser was very stretched in one dimension because I had the developer console up, so it was all an optical illusion.
I am attempting to make a pong clone using canvas. I however seem to have a misunderstanding how the coordinate system works in canvas in regards to events.
I have created a playerPaddle class. With this class I want the center of the paddle to be drawn exactly where the mouse is hovering:
// Paddle Object
function Paddle( h, w, x, y, fill) {
this.h = h;
this.w = w;
this.midH = h / 2;
this.x = x;
this.y = y;
this.fill = fill;
}
Paddle.prototype.draw = function(ctx) {
ctx.fillStyle = this.fill;
ctx.fillRect(this.x, this.y, this.w, this.h);
}
Paddle.prototype.updatePos = function(y) {
this.y = y - this.midH
}
I thought the correct way to offset the paddle so that the center of the paddle is on the mousemove event would be to draw the rectangle by subtracting half the paddle height from the new position of the paddle like I am doing in the updatePos method above.
However this seems to not be working correctly as it is drawing the top of my paddle exactly where the mousemove event is occurring.
What does work though is if I subtract this.h from the y location of the event, which makes absolutely no sense to me.
Below is the code I use to instantiate the paddle, add the event handler to the canvas element itself, and the function I use to render everything.
// Paddle Globals
var paddleHeight = 50,
paddleWidth = 10,
paddleOffset = 10;
var playerPaddle = new Paddle( paddleHeight, paddleWidth, paddleOffset, midY, '#FFFFFF');
// Does the actual rendering
function render() {
ctx.clearRect(0, 0, cWidth, cHeight);
playerPaddle.draw(ctx);
}
//Event Handlers
canvas.addEventListener('mousemove', function(e) {
e.preventDefault();
var position = canvas.getBoundingClientRect();
var y = e.clientY - position.top;
playerPaddle.updatePos(y);
}, false);
Also here is an example of the behavior that I am describing: http://jsfiddle.net/u57QD/
I think I solve the problem.
Try to delete the css style on Canvas.
I think the padding-top is making you canvas downward. So it look like the y - this.midH is not working :D Good luck
Delete follow line of code in stylesheet
padding-top: 20px;
I am writing a script to draw an image of the sun, and have an image of the earth orbit around it.
I have defined a planet class as so:
function planet(name, size, rotateRate, startx, starty, colour, scale, oRad, oAng, oSpd){//container class for planets
this.name = name;
this.rotateRate = rotateRate;
this.x = startx;
this.y = starty;
this.colour = colour;
this.scale = scale;
this.size = size;
this.orbitRadius= oRad;
this.orbitAngle = oAng;
this.orbitSpeed = oSpd;
this.drawByArc =
function drawArcCircle(){//draws circles using the arc method
context.save();
context.strokeStyle = '#000000';
context.fillStyle = this.colour;
context.lineWidth=3;
context.beginPath();
context.arc(this.x,this.y,this.size*this.scale,0,Math.PI * 2,false)
context.stroke();
context.fill();
context.closePath();
context.restore();
}
}
Now I have created two instances of the class in the program and drawn them fine using the following functions:
function gameLoop(){//The Game Loop
var thisTime = Date.now();//current time
var deltaTime = thisTime - lastTime;//find difference, not yet used
update();
draw();
lastTime = thisTime;
setTimeout(gameLoop, 1000/60);
}
function draw(){// Draws all the objects
drawBackground();
Sun.drawByArc();
Earth.drawByArc();
}
function update(){// Updates for animation
//var newRotation = this.getCurrantRotation() + (this.getRotationRate()*deltaTime);
var gSegments;
gScale = 4;
simSpeed = 10;
Sun.scale = gScale;
Earth.scale = gScale;
Earth.orbitSpeed = 360/simSpeed;
//Earth.x = Sun.x + Earth.orbitRadius * Math.cos(Earth.orbitAngle * Math.pi / 180);
//Earth.y = Sun.y - Earth.orbitRadius * Math.sin(Earth.orbitAngle * Math.pi / 180);
}
When i have the last two lines of the update method commented out, both circles draw fine, however when i add the final two lines in to attempt to update the earths position in orbit, when i attempt to run the code in chrome the Earth sphere vanishes!
Chrome debugger shows no errors so i'm at a loss as to why it occurs.
EDITED::
Well, I found that thanks to a small typing error (math.pi instead of Math.PI) my planet x and y values were becoming NaN.
however now my earth is stuck at at 90 degree point in its orbit and simply doesnt move, at least it draws, any ideas?
Solved it.
Most of the issues came from using math.pi instead of Math.PI
on top of that, i had not set a value to change the angle of orbit, meaning the orbit always remained at 90, making there be no orbit.
Chrome debug very much helped me in figuring all this out, so thanks a lot user1816548