var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
context.canvas.width = window.innerWidth;
context.canvas.height = window.innerHeight;
var x1 = Math.random()*context.canvas.width;
var y1 = Math.random()*context.canvas.height;
var xdir = 0; var ydir = 0;
context.beginPath();
setInterval(function(){
for (var i = 0; i < 10; i++) {
randx = Math.random(); randy = Math.random();
if (randx > 0.95) {
if (xdir < 0) xdir = (xdir+((Math.random()*1.5) - 1))/2;
else if (xdir > 0) xdir = (xdir+((Math.random()*1.5) - 0.5))/2;
else xdir = (Math.random()*1.5) - 0.75;
}
if (randy > 0.95) {
if (ydir < 0) ydir = (ydir+((Math.random()*1.5) - 1))/2;
else if (ydir > 0) ydir = (ydir+((Math.random()*1.5) - 0.5))/2;
else ydir = (Math.random()*1.5) - 0.75;
}
context.lineTo(x1+xdir, y1+ydir);
context.stroke();
x1 = x1+xdir;
y1 = y1+ydir;
}
},50);
This is my random line script, but my lines are really ugly: http://i.stack.imgur.com/YZT2o.png
Is there any better way for achieving a smooth line using canvas?
take a look at this question:
Drawing GOOD LOOKING (like in Flash) lines on canvas (HTML5) - possible?
Lines on HTML5 Canvas are nicely antialiased on all browsers/OS (AFAIK). However, in your update callback with its 10-strokes-per-loop you are neither clearing your canvas nor clearing your path and so you are drawing the same path on top of itself 200 times per second. This is causing all the anti-aliasing to be destroyed as even the faintest opacity pixels build up until they are solid lines.
The simplest fix to make your code look pretty is to add this line:
context.clearRect(0,0,context.canvas.width,context.canvas.height);
inside your for loop, for example right before context.stroke();.
This one-line change makes it look good, but is bad for performance, clearing and redrawing the canvas 10 times for each visual update.
Here's a better alternative:
context.beginPath();
context.moveTo(x1,y1);
context.lineTo(x1+xdir, y1+ydir);
context.stroke();
x1 += xdir; y1 += ydir;
This way you never clear the canvas, and instead draw only the changed line each frame.
One other alternative (if you need the full path always available) is to accumulate your changes to the context path in one high-speed setInterval loop, and in another, slower loop occasionally clear the canvas and re-stroke the entire path. This is similar to what I've done for my Langton's (Very Fast) Ant simulation.
Related
I am working on a project where I would like to have darkness covering the screen and the character glowing in the darkness. I tried to animate the scene then draw darkness over it using this code:
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var canvasWidth = canvas.width;
var canvasHeight = canvas.height;
var pixelSize = 30;
var width = canvasWidth/pixelSize;
var height = canvasHeight/pixelSize;
var lightX = canvasWidth/2;
var lightY = canvasHeight/2;
var lightDiameter = 100;
var a = lightDiameter*pixelSize;
for(var x = 0; x < width; x++) {
for(var y = 0; y < height; y++) {
var alpha = 1.25 - a/(Math.pow(x*30 - lightX, 2) + Math.pow(y*30 -
lightY, 2));
ctx.fillStyle = "rgba( 25, 25, 30," + alpha + ")";
ctx.fillRect(x*pixelSize, y*pixelSize, pixelSize, pixelSize);
}
}
This worked pretty well and I liked the way it looked, but when this was repeatedly animated alongside the other code it slowed the rest down significantly. I think a possible solution may be to somehow draw a gradient with a lower "quality?", another solution I have considered is to save this drawing in a separate canvas and drawing it translated to the players location but that would make it impossible to add multiple sources of light, which I would like to do by simply adding their effect. I may just have to deal with the lag and I'm a noob at this stuff, but if anyone can help me that would be wonderful.
To clarify, I am using this code in the drawing loop, and also it is re-calculated in every iteration. I would prefer to recalculate this way so I can have multiple moving sources of light.
This is because fillRect is pretty slow compared to other methods. You could probably speed things up by using ImageData objects instead.
The way to do this would be to render everything to the canvas, get the corresponding ImageData, modify its contents and put it back onto the canvas:
var ctx = canvas.getContext("2d");
// render stuff here
var imageData = ctx.getImageData(0,0,canvasWidth,canvasHeight);
for (let x=0;x<canvasWidth;x++){
for (let y=0;y<canvasHeight;y++){
let i = (x+y*canvasWidth)*4;
let alpha = calculateAlpha(x,y); // your method here (should result in a value between 0 and 1)
imageData.data[i] = (1-alpha)*imageData.data[i]+alpha*25;
imageData.data[i+1] = (1-alpha)*imageData.data[i+1]+alpha*25;
imageData.data[i+2] = (1-alpha)*imageData.data[i+2]+alpha*30;
imageData.data[i+3] = 1-(1-alpha)*(1-imageData.data[i+3]);
}
}
ctx.putImageData(imageData,0,0);
This should do the lighting on a per-pixel basis, and much faster than using clearRect all the time. However, it might still slow things down, as you're doing a lot of calculations each frame. In that case, you could speed thing up by doing the lighting in a second canvas that is positioned over your main canvas using css:
<div id="container">
<canvas id="canvas"></canvas>
<canvas id="lightingCanvas"></canvas>
</div>
Css:
#container {
position: relative;
}
#canvas, #lightingCanvas {
position: absolute;
top: 0;
left: 0;
}
#container, #canvas, #lightingCanvas {
width: 480px;
height: 360px;
}
Javascript:
var canvas = document.getElementById("lightingCanvas")
var ctx = canvas.getContext("2d");
ctx.fillStyle = "rgb(25,25,30)";
ctx.fillRect(0,0,canvas.width,canvas.height);
var imageData = ctx.getImageData(0,0,canvasWidth,canvasHeight);
for (let x=0;x<canvasWidth;x++){
for (let y=0;y<canvasHeight;y++){
let i = (x+y*canvasWidth)*4;
let alpha = calculateAlpha(x,y); // your method here (should result in a value between 0 and 1)
imageData.data[i+3] = 255*alpha;
}
}
ctx.putImageData(imageData,0,0);
This way the browser takes care of the blending for you and you just need to plug in the correct alpha values - so rendering should be even faster now.
This will also allow you to bring the large pixels back in - just use a lower resolution on the second canvas and use some css effect like image-rendering: -webkit-crisp-edges to make the canvas pixelated when scaled up.
I'm updating a canvas one row at a time using JS. On my laptop, the canvas renders as expected. On my iPad, white horizontal lines appear in the canvas at different rows every time I refresh.
If I zoom in or out on my iPad, the lines go away. Any thoughts on why this is happening and how I can work around it? I've tried forcing a repaint of the browser window via several methods mentioned on SO. It didn't change anything.
Here is the code: http://jsfiddle.net/RFf5r/
function paintRow(y)
{
if(y == 100)
return;
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
var imageData = ctx.createImageData(100, 1);
var i = 0;
for(x = 0; x<100; x++)
{
imageData.data[i] = x / 100 * 255;
imageData.data[i+1] = 0;
imageData.data[i+2] = 0;
imageData.data[i+3] = 255;
i+=4
}
ctx.putImageData(imageData, 0, y);
setTimeout(function() { paintRow(y+1); }, 10);
}
paintRow(0);
Here is a workaround that appears to work:
c.style.zoom = c.style.zoom == "100%" ? "100.0001%" : "100%";
after each update.
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 am attempting to use particlesDepthBlur() in place of the opacity for the "snowflakes" - which is located inside the step function, however it produces an undesired strobe effect - why? Consider the following code,
Edited for clarification:
<canvas id="canvas"></canvas>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var num = 2000;
var canvas = document.getElementById("canvas");
var width = canvas.width = 960;
var height = canvas.height = 500;
var ctx = canvas.getContext("2d");
var particles = d3.range(num).map(function(i) {
return [Math.round(width*Math.random()), Math.round(height*Math.random())];
});
function particlesDepthBlur(){
return Math.random();
console.log(Math.random());
}
function particlesDepthSize(){
return Math.floor((Math.random()*4)+1);
}
d3.timer(step);
function step() {
ctx.shadowBlur=0;
ctx.shadowColor="";
ctx.fillStyle = "rgba(0,0,0,"+particlesDepthBlur()+")";
ctx.fillRect(0,0,width,height);
ctx.shadowBlur=particlesDepthSize();
ctx.shadowColor="white";
ctx.fillStyle = "rgba(255,255,255,1)";
particles.forEach(function(p) {
p[0] += Math.round(2*Math.random()-1);
p[1] += Math.round(2*Math.random()-1) + 2;
if (p[0] < 0) p[0] = width;
if (p[0] > width) p[0] = 0;
if (p[1] < 0) p[1] = height;
if (p[1] > height) p[1] = 0;
drawPoint(p);
});
};
function drawPoint(p) {
ctx.fillRect(p[0],p[1],1,1);
};
</script>
<style>
html, body { margin: 0; padding: 0; }
</style>
Couple things:
Firstly you are calling ctx.fillStyle = "rgba(0,0,0,"+particlesDepthBlur()+")"; immediately before filling the background of the canvas.
Second, you are only calculating the blur and opacity once per frame, not per particle.
Thirdly, if you calculate it per particle (and continue to use Math.random()) then it bogs down my machine with several thousand operations per second.
Here is my
fiddle!
~Every frame I calculate 10 opacities and 10 sizes and iterate across the particles setting them per particle.~ << This was an
old version; now the opacities are all set up before step() is called, and the sizes are proportional to opacities.
edit: good job with the random falling-downward motion!
edit2: tweaking to set constant opacity and size per particle. this still runs very slowly for me, probably because you are running Math.random() 4000 times per frame. You might consider calculating a couple dozen positional vectors once per frame, and iterate across all your particles. This way every n snowflakes would be falling in the same pattern, at the benefit of much less computation needed.
Finally, perhaps consider making the 'close' snowflakes (big and bright) fall faster than the 'far' snowflakes.
<snip>
// Set up an opacity value for each particle, this will later be indexed with j
var particleOpacities = [];
particles.forEach(function(p){
particleOpacities.push(particlesDepthBlur());
});
d3.timer(step);
var j = 0;
// since j is used by both step and drawPoint, it has to be outside both functions
function step() {
ctx.shadowBlur=0;
ctx.shadowColor="";
ctx.fillStyle = "rgba(0,0,0,1)";
ctx.fillRect(0,0,width,height);
j = 0;
particles.forEach(function(p) {
p[0] += Math.round(2*Math.random()-1);
p[1] += Math.round(2*Math.random()-1) + 2;
if (p[0] < 0) p[0] = width;
if (p[0] > width) p[0] = 0;
if (p[1] < 0) p[1] = height;
if (p[1] > height) p[1] = 0;
drawPoint(p);
});
};
function drawPoint(p) {
j++; // iterate over points
var particleSize = particleOpacities[j] * 4;
ctx.shadowBlur=particleSize;
ctx.shadowColor="white";
ctx.fillStyle = "rgba(255,255,255," + particleOpacities[j] + ")";
ctx.fillRect(p[0],p[1],particleSize,particleSize);
};
The ctx.fillStyle = "rgba(0,0,0,"+particlesDepthBlur()+")"; randomly changes how much of the previous canvas is visible. It does so uniformly across the entire canvas. Sometimes if fills the screen completely with black wiping out the past views, other times it lets the last screen partially show. When it lets previous views be seen it can as much double the amount of white on the canvas, and when followed by a low opacity suddenly the quantity of white drops.
function particlesDepthBlur(){
return Math.random(0.5)+.5;
}
smooths this out
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 $.