The problem seems to be related only with Internet Explorer.
When the example is run with jsfiddle in IE, it runs smooth.
If tried on my local machine and opened with IE, it stutters.
What could the problem be?
UPDATE: If setTimeout is removed, it does not stutter, but if I force it to 60 frames by adding the setTimeout, it does.
I need to force it to 60 frames/sec and at least run smooth.
The code in jsfiddle
// Check if canvas exists and get the context of the canvas
var canvas = document.getElementsByTagName('canvas')[0];
var ctx = canvas.getContext('2d');
// Add width and height of the canvas
var width = 640, height = 480;
// Make the canvas width and height as the variables
canvas.width = width, canvas.height = height;
rect = {
x: 0,
y: 0,
width: 100,
height: 100
}
ctx.fillStyle = 'red';
ctx.fillRect(rect.x,rect.y,rect.width,rect.height);
function animate(){
setTimeout(function(){
rect.x += 5;
ctx.fillStyle = 'red';
ctx.clearRect ( 0 , 0 , canvas.width, canvas.height );
ctx.fillRect(rect.x,rect.y,rect.width,rect.height);
if(rect.x <= 200){
requestID = requestAnimationFrame(animate);
}
}, 1000 / 60);
}
requestAnimationFrame(animate);
It makes no sense to combine requestAnimationFrame and setTimeout for painting. Maybe you want to update your animation at a stable rate of 60 fps, but that doesn't mean you need to repaint at exactly 60 fps.
Separate animation update from animation paint and your problem will be solved:
function paintAnimation() {
ctx.fillStyle = 'red';
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillRect(rect.x, rect.y, rect.width, rect.height);
requestAnimationFrame(paintAnimation);
}
requestAnimationFrame(paintAnimation);
function updateAnimation() {
rect.x += 5;
if (rect.x <= 200)
setTimeout(updateAnimation, 1000 / 60);
}
updateAnimation();
See JSFiddle
Related
This question already has an answer here:
HTML5 Canvas performance very poor using rect()
(1 answer)
Closed 2 years ago.
Okay, so I am really scratching my head of what is happening here.
I have the following code running in a loop:
const canvas = document.getElementById("canvas") as HTMLCanvasElement;
const ctx = canvas.getContext("2d") as CanvasRenderingContext2D;
let latestTime = performance.now();
function draw(time: number) {
const dt = time - latestTime;
const { width, height } = (document.getElementById(
"main"
) as HTMLElement).getBoundingClientRect();
if (canvas.width != width) canvas.width = width;
if (canvas.height != height) canvas.height = height;
ctx.clearRect(0, 0, width, height);
ctx.fillStyle = "#333333";
ctx.rect(0, 0, width, height);
ctx.fill();
/* When this section is commented out, the performance INCREASES...
const size = 20;
const radius = 100;
const period = 10;
const x =
width / 2 + radius * Math.sin((((2 * Math.PI) / period) * time) / 1000);
const y =
height / 2 + radius * Math.cos((((2 * Math.PI) / period) * time) / 1000);
ctx.fillStyle = "#9999ff";
ctx.beginPath();
ctx.ellipse(x, y, size, size, 0, 0, 2 * Math.PI);
ctx.fill();*/
ctx.font = "12px Montserrat";
ctx.fillStyle = "#ffffff";
ctx.fillText(`Frame rate: ${Math.round(1.0 / (dt / 1000))}`, 10, 20);
latestTime = time;
window.requestAnimationFrame(draw);
}
draw(performance.now());
It is a simple circle orbiting around the center when it is uncommented. However, I commented the circle part and only render the framerate.
When I run this and inspect in the Chrome Dev Tools the frame budget, I see this:
7ms is taken up by the system.
The framerate gradually goes down, but in steps. It is nicely 60. Then it goes to 30 all of a sudden after 30 seconds or so. Then it goes to 15. And so on.
When I uncomment the circle code the result in the inspector is this:
The system task only takes up 0.67ms and the framerate is constant.
I tried moving the section but it doesn't matter.
What is going on here? It doesn't make sense to me.
Calling ctx.fillText also does a ctx.beginPath thus resetting any paths you have created.
The function ctx.rect add to the current path. Without the beginPath you are adding a rect to the current path each frame. Thus over time you are rendering more and more rectangles and thus the slow down.
Use ctx.fillRect rather than ctx.rect or start a new path with ctx.beginPath before the call to ctx.rect
I've made a sine wave animation with javascript where the area below the sine wave is filled with a light blue color. But when I run the code my computer starts heating up and lags. This could also be beacause my computer is pretty worn out by now, but I really would like to know how to optimize this code or maybe recreate the effect with something else that isn't so performance intensive if possible.
The Sine wave animation:
https://jsfiddle.net/x2audoqk/13/
The code:
const canvas = document.querySelector("canvas")
const c = canvas.getContext("2d")
canvas.width = innerWidth
canvas.height = innerHeight
window.addEventListener("resize", function () {
canvas.width = innerWidth
canvas.height = innerHeight
wave.y = canvas.height / 1.5
wave.length = -4.5 / canvas.width
amplitude = canvas.width / 35
})
const wave = {
y: canvas.height / 1.5,
length: -4.5 / canvas.width,
amplitude: canvas.width / 25,
frequency: 0.0045
}
let increment = wave.frequency
function animate() {
requestAnimationFrame(animate)
// Deletes previous waves
c.clearRect(0, 0, canvas.width, canvas.height)
c.beginPath()
// Get all the points on the line so you can modify it with Sin
for (let i = 0; i <= canvas.width; i++) {
c.moveTo(i, wave.y + Math.sin(i * wave.length + increment) * wave.amplitude * Math.sin(increment))
c.lineTo(i, canvas.height)
}
// Fill the path
c.strokeStyle = 'rgba(1, 88, 206, .25)'
c.stroke()
increment += wave.frequency
c.closePath()
}
animate()
Any suggestions are welcome.
The heavy load is due to requestAnimationFrame which run over and over again. An approach is to limit the frame rate of the animation. Knowing that the human's eyes need at least 24 fps for a fluid image, you can pick a fps between 24-60 fps of your choice (limited by monitor refresh rate up to 60Hz depends on configuration but this is mostly the default).
Here is a guide how to control the fps
var fps = 30;
var now;
var then = Date.now();
var interval = 1000/fps;
var delta;
function animate() {
requestAnimationFrame(animate);
now = Date.now();
delta = now - then;
if (delta > interval) {
then = now - (delta % interval);
//your code drawing here
}
}
animate();
The the difference between 30 fps and 60 fps
Another technique to achieve the same effect with less workload is to use CSS animation (horizontal), with your background wave pre-draw as an image.
I am working with canvas animations for the first time and I am having an issue when trying to animate multiple images at once.
I am able to draw multiple images on a canvas and position them randomly. I can get a single image to animate on the canvas but only the last image drawn from an array.
I know that the issue is with clearRect() clearing all previously drawn images from said array but can't figure out how to only clearRect once all images have been drawn in each animation frame, I was wondering if anyone has dealt with something like this before and if they could point me in the right direction of how to only clearRect() after all images are drawn?
function animate() {
srequestAnimationFrame(animate);
for(let i = 0; i < images.length; i++) {
let y = images[i].y;
let img = new Image();
img.src = images[i].url;
img.onload = function() {
// This is clearing all images drawn before the last image
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(this, images[i].x, y, images[i].width, images[i].height);
}
images[i].y -= 1;
if(images[i].y < (0 - images[i].height)) {
images[i].y = window.innerHeight;
images[i].x = (Math.random() * (canvas.width - 160));
}
}
}
I would like to animate all the images vertically up the page with them resetting to the bottom after reaching the top of the screen, I have this working but only for the last image as mentioned above
In the animate() function, after updating the value for the y you need to draw again every image. Also, you need to clear the canvas with every frame (the animate() function), not after drawing every image, since clearRect will delete everything previously drawn on the canvas. The reason why you need to clear the rect is that you have to delete the images drawn at the previous position.
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
let cw = (canvas.width = window.innerWidth);
cx = cw / 2;
let ch = (canvas.height = window.innerHeight),
cy = ch / 2;
let images = [
{
url: "https://s3-us-west-2.amazonaws.com/s.cdpn.io/222579/puppy150x150.jpg",
x: Math.random() * cw,
y: Math.random() * ch,
width: 50,
height: 50
},
{
url: "https://s3-us-west-2.amazonaws.com/s.cdpn.io/222579/puppy200x200.jpg",
x: Math.random() * cw,
y: Math.random() * ch,
width: 40,
height: 40
},
{
url:
"https://s3-us-west-2.amazonaws.com/s.cdpn.io/222579/puppyBeagle300.jpg",
x: Math.random() * cw,
y: Math.random() * ch,
width: 60,
height: 60
}
];
for (let i = 0; i < images.length; i++) {
let y = images[i].y;
images[i].img = new Image();
images[i].img.src = images[i].url;
images[i].img.onload = function() {
// This is clearing all images drawn before the last image
//ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(this, images[i].x, y, images[i].width, images[i].height);
};
}
function animate() {
requestAnimationFrame(animate);
// clear the canvas here!
ctx.clearRect(0, 0, cw, ch);
for (let i = 0; i < images.length; i++) {
//update the y value
images[i].y -= 1;
if (images[i].y < -images[i].height) {
images[i].y = window.innerHeight;
images[i].x = Math.random() * (canvas.width - 160);
}
//draw again the image
ctx.drawImage(
images[i].img,
images[i].x,
images[i].y,
images[i].width,
images[i].height
);
}
}
animate();
<canvas id="canvas"></canvas>
I’m trying to write a simple enough animation with javascript using HTML5 canvas. It’s an image of rain drops that I want to animate seamlessly. This is the image:
https://s31.postimg.org/475z12nyj/raindrops.png
This is how I'm currently animating it:
function Background() {
this.x = 0, this.y = 0, this.w = bg.width, this.h = bg.height;
this.render = function() {
ctx.drawImage(bg, 0, this.y++);
if (this.y <= -199) { //If image moves out of canvas, reset position to 0
this.y = 0;
}
}
}
I’m facing two issues though.
I can’t get the image to loop at all. It just falls down the one time, I need to have this on a loop so it’ll continue again when it starts to leave the canvas.
Once I know how to properly loop it, there is the issue that the rain isn’t suppose to fall down exactly vertically. It needs to fall down diagonally as the raindrops in the image suggest.
This is where it stops to be a simple enough animation.
Here’s my fiddle, it includes all my code. Thanks a lot.
PS: I’ll take any help with either Javascript or CSS I can get. But I do need the rain effect to be using the image only! I can’t accept anything else unfortunately.
I'd recommend splitting out your loop into an animation loop that calls update() and draw() separately. Update your state in update() and then render that state in draw().
Something like this (bit ragged, but you can perhaps make it better :) ):
var lastTick = 0;
var position = { x:0, y:0 };
var bg = document.getElementById('bg');
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
function update(gameTime) {
position.x += (70 * gameTime.diff / 1000);
position.y += (110 * gameTime.diff / 1000);
if (position.x > canvas.width) {
position.x = 0;
}
if (position.y > canvas.height) {
position.y = 0;
}
}
function draw(gameTime) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(bg, position.x, position.y, canvas.width, canvas.height);
ctx.drawImage(bg, position.x - canvas.width, position.y, canvas.width, canvas.height);
ctx.drawImage(bg, position.x, position.y - canvas.height, canvas.width, canvas.height);
ctx.drawImage(bg, position.x - canvas.width, position.y - canvas.height, canvas.width, canvas.height);
}
function loop(tick) {
var diff = tick - lastTick;
var gameTime = { tick:tick, diff:diff };
update(gameTime);
draw(gameTime);
requestAnimationFrame(loop);
lastTick = tick;
}
requestAnimationFrame(loop);
<title>Rain</title>
<meta charset="UTF-8">
<style>
canvas {
width:100vw;
height:100vh;
}
</style>
<img id="bg" src="https://s31.postimg.org/475z12nyj/raindrops.png" style="display:none;">
<canvas id="canvas"><h1>Canvas not supported</h1></canvas>
I'm working with phonegap and trying to make simple app for Android, where a ball rolls around based on the accelerometer. I'm using a canvas that takes up the whole screen and drawing a circle for the ball.
Everything works fine, except that the canvas is not getting cleared properly between each frame. It clears everything except the ball at the original position. So there are always 2 balls, one moving around, and one stationary in the middle.
Here is my render function
function render(){
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.arc(posX, posY, size, 0, 2*Math.PI, false);
ctx.fillStyle = 'red';
ctx.fill();
ctx.lineWidth = 2;
ctx.strokeStyle = 'black';
ctx.stroke();
}
I've tried using canvas.width = canvas.width, but this simply moves the fake ball to the real balls current position. It also gives really poor performance if I do this every frame.
This problem doesn't happen when I use a browser (Chrome) and move the ball with the console.
The really weird thing is that it doesn't always happen. Sometimes in rare cases, when i start the app or use canvas.width, the fake ball disappears.
Edit:
Tested with different devices, and the problem is only on my Samsung Galaxy Note (Android 4.1.2), while it works fine on my HTC Desire HD (Android 2.3.5) and iPhone 5 (iOS 7.1)
Full code
document.addEventListener("deviceready", onDeviceReady, false);
function onDeviceReady(){
navigator.accelerometer.watchAcceleration(onSuccess, onError, { frequency: 100 });
}
function onSuccess(acc){
accX = -acc.x;
accY = acc.y;
}
function onError(message){
alert('Failed because: ' + message);
}
var size = 40;
var speed = 80;
var posX;
var posY;
var speedX;
var speedY;
var accX;
var accY;
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
canvas.width = screen.width / window.devicePixelRatio;
canvas.height = screen.height / window.devicePixelRatio;
document.body.appendChild(canvas);
reset();
var then = Date.now();
setInterval(main, 1);
function reset(){
posX = canvas.width/2;
posY = canvas.height/2;
speedX = 0;
speedY = 0;
accX = 0;
accY = 0;
}
function update(mod){
speedX += accX*mod;
speedY += accY*mod;
posX += speedX*mod*speed;
posY += speedY*mod*speed;
if((posX < 0-size) || (posX > canvas.width+size) || (posY < 0-size) || (posY > canvas.height+size))
reset();
}
function render(){
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.arc(posX, posY, size, 0, 2*Math.PI, false);
ctx.fillStyle = 'red';
ctx.fill();
ctx.lineWidth = 2;
ctx.strokeStyle = 'black';
ctx.stroke();
}
function main(){
var now = Date.now();
var delta = now - then;
update(delta/1000);
render();
then = now;
}
Instead of clearing the canvas, I tried filling it with white.
context.fillStyle = "#FFFFFF";
context.fillRect(0, 0, canvas.width, canvas.height);
I tested it on a LG P880, CyanogenMod 10.1, with its stock Android browser, and the original canvas disappeared afterwards.