I tried to make a program using HTML canvas. I don't know what I did wrong, but my code does not work. My program is supposed to work like follows: The program will draw a square. Next, it will change the values of the variables used to create the square. I put a setInterval to run my function more than once. Yes, I am aware there is no clearRect. But still, the square should grow. How it actually works is like follows: It draws a red square and nothing else happens.
How can I resolve this problem?
var x = 75;
var y = 75;
var w = 150;
var h = 150;
var c = document.getElementById("canvas");
var ctx = c.getContext("2d");
var dx = -1
var dy = -1
var dw = 2
var dh = 2
var col = "#f00"
function size(v, min, max, d) {
if (v > max || v < min) {
d = -d;
}
v += d;
}
function draw() {
ctx.fillStyle = col;
ctx.fillRect(x, y, w, h);
size(x, 0, 150, dx);
size(y, 0, 150, dy);
size(w, 0, 300, dw);
size(h, 0, 300, dh);
}
setInterval(draw, 25)
<canvas id="canvas" width="300px" height="300px" style="border:1px solid #000"></canvas>
The problem is that function parameters are passed by value, not reference. The size() function gets a local copy of number parameters. You need to return a value from the function and assign it to a variable in the calling scope.
The other issue is you'd like to change both d and v inside of one function. That requires global variables (bad idea), returning a data structure of some sort, or breaking up your logic into multiple functions. Here's one way to go:
function changeDir(d, v, min, max) {
return v > max || v < min ? -d : d;
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = col;
ctx.fillRect(x, y, w, h);
// Change direction if necessary
dx = changeDir(dx, x, 0, 150);
dy = changeDir(dy, y, 0, 150);
dw = changeDir(dw, w, 0, 300);
dh = changeDir(dh, h, 0, 300);
// Accumulate
x += dx;
y += dy;
w += dw;
h += dh;
}
Related
I'm not proficient in js to say the very least.
I have a bouncy ball in a container in js. When the ball hits a certain part of its container, I want a box to light up briefly. I want to control the duration of the blink. I want the blink to happen once.
So far this works but the blink is incredibly brief:
I have a canvas object
<Canvas id="button1" width="100px" height="50px"></canvas>
And this code in place. Let's assume for simplicity's sake that the ball being in the right place sets ball_is_in_area to T for a very short duration.
var button1;
var button1ctx;
var ball_is_in_area = F;
button1 = document.getElementById("button1");
button1ctx = button1.getContext("2d");
button1ctx.fillStyle = "white";
button1ctx.fillRect(0, 0, canvas.width, canvas.height);
if( ball_is_in_area == T){
snd.play();
button1ctx.fillStyle = "red";
button1ctx.fillRect(0, 0, canvas.width, canvas.height);
}
"Don't use a canvas object use X" type answers are also appreciated!
Using requestAnimationFrame and based on this sample, you can use the timestamp parameter provided and, whenever you hit a wall, extend the timer for when the blink will end. Then use this timer to determine the color of the background.
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var x = canvas.width / 2, y = canvas.height / 2;
var ballRadius = 10, dx = 2, dy = -2;
var blinkDuration = 60; // Duration in milliseconds
var endBlink = 0;
function drawBall() {
ctx.beginPath();
ctx.arc(x, y, ballRadius, 0, Math.PI * 2);
ctx.fillStyle = "#0095DD";
ctx.fill();
ctx.closePath();
}
function draw(timeStamp) {
if (x + dx > canvas.width - ballRadius || x + dx < ballRadius) {
endBlink = timeStamp + blinkDuration; // Hit a wall
dx = -dx;
}
if (y + dy > canvas.height - ballRadius || y + dy < ballRadius) {
endBlink = timeStamp + blinkDuration;
dy = -dy;
}
// If endBlink > timeStamp, change the color
ctx.fillStyle = (endBlink > timeStamp ? "#338" : "#114");
ctx.fillRect(0, 0, canvas.width, canvas.height);
drawBall();
x += dx;
y += dy;
requestAnimationFrame(draw);
}
requestAnimationFrame(draw);
body { margin: 0 }
<canvas id="canvas" width="630" height="190"></canvas>
I have this awesome piece of code.
The idea, as you can imagine,is to draw a grid of rectangles. I want a big grid, let's say 100 X 100 or more.
However, when i run the awesome piece of code for the desired size (100X 100), my browser crashes.
How can i achieve that?
* please note: when i say 100X100 i mean the final number of rectangles (10k) not the size of the canvas.
thank u
function init() {
var cnv = get('cnv');
var ctx = cnv.getContext('2d');
var ancho = 12; // ancho means width
var alto = 12; // alto means height
ctx.fillStyle = randomRGB();
for (var i = 0; i < cnv.width; i+= ancho) {
for (var j = 0; j < cnv.height; j+= alto) {
//dibujar means to draw, rectangulo means rectangle
dibujarRectangulo(i+ 1, j+1, ancho, alto, ctx);
}
}
}
function dibujarRectangulo(x, y, ancho, alto, ctx) {
ctx.rect(x, y, ancho, alto);
ctx.fill();
ctx.closePath();
}
The dibujarRectanglo() function calls rect() function which adds a closed rectanglar subpath to the current path. Then calls fill() function to fill the current path. Then calls closePath() function to close the subpath, which does nothing since the subpath is already closed.
In other words, the first dibujarRectanglo() function call is painting a path that contains 1 rectangle subpath. The second call is painting a path that contains 2 rectangle subpaths. The third call is painting a path that contains 3 rectangle subpaths. And so on. If the loop calls dibujarRectanglo() function 10000 times then a total of 1+2+3+...+10000 = 50005000 (i.e. over 50 million) rectangle subpaths will be painted.
The dibujarRectangle() function should be starting a new path each time. For example...
function dibujarRectangulo(x, y, ancho, alto, ctx) {
ctx.beginPath();
ctx.rect(x, y, ancho, alto);
ctx.fill();
}
Then 10000 calls will only paint 10000 rectangle subpaths which is a lot faster that painting 50 million rectangle subpaths.
16,384 boxes on the wall
As I said in the comment its easy to draw a lot of boxes, it is not easy to have them all behave uniquely. Anyways using render to self to duplicate boxes exponential there are 128 * 128 boxes so that's 16K, one more iteration and it would be 64K boxes.
Its a cheat, I could have just drawn random pixels and called each pixel a box.
Using canvas you will get upto 4000 sprites per frame on a top end machine using FireFox with each sprite having a location, center point, rotation, x and y scale, and an alpha value. But that is the machine going flat out.
Using WebGL you can get much higher but the code complexity goes up.
I use a general rule of thumb, if a canva 2D project has more than 1000 sprites then it is in need of redesign.
var canvas = document.getElementById("can");
var ctx = canvas.getContext("2d");
/** CreateImage.js begin **/
var createImage = function (w, h) {
var image = document.createElement("canvas");
image.width = w;
image.height = h;
image.ctx = image.getContext("2d");
return image;
}
/** CreateImage.js end **/
/** FrameUpdate.js begin **/
var w = canvas.width;
var h = canvas.height;
var cw = w / 2;
var ch = h / 2;
var boxSize = 10;
var boxSizeH = 5;
var timeDiv = 1.2;
var bBSize = boxSize * 128; // back buffer ssize
var buff = createImage(bBSize, bBSize);
var rec = createImage(boxSize, boxSize);
var drawRec = function (ctx, time) {
var size, x, y;
size = (Math.sin(time / 200) + 1) * boxSizeH;
ctx.fillStyle = "hsl(" + Math.floor((Math.sin(time / 500) + 1) * 180) + ",100%,50%)";
ctx.strokeStyle = "Black";
ctx.setTransform(1, 0, 0, 1, 0, 0)
ctx.clearRect(0, 0, boxSize, boxSize);
x = Math.cos(time / 400);
y = Math.sin(time / 400);
ctx.setTransform(x, y, -y, x, boxSizeH, boxSizeH)
ctx.fillRect(-boxSizeH + size, -boxSizeH + size, boxSize - 2 * size, boxSize - 2 * size);
ctx.strokeRect(-boxSizeH + size, -boxSizeH + size, boxSize - 2 * size, boxSize - 2 * size);
}
function update(time) {
var fw, fh, px, py, i;
time /= 7;
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, w, h);
drawRec(rec.ctx, time);
time /= timeDiv;
buff.ctx.clearRect(0, 0, bBSize, bBSize)
buff.ctx.drawImage(rec, 0, 0);
buff.ctx.drawImage(rec, boxSize, 0);
fw = boxSize + boxSize; // curent copy area width
fh = boxSize; // curent copy area height
px = 0; // current copy to x pos
py = boxSize; // current copy to y pos
buff.ctx.drawImage(buff, 0, 0, fw, fh, px, py, fw, fh); // make square
for (i = 0; i < 6; i++) {
drawRec(rec.ctx, time);
time /= timeDiv;
buff.ctx.drawImage(rec, 0, 0);
fh += fh; // double size across
px = fw;
py = 0;
buff.ctx.drawImage(buff, 0, 0, fw, fh, px, py, fw, fh); // make rec
drawRec(rec.ctx, time);
time /= timeDiv;
buff.ctx.drawImage(rec, 0, 0);
fw += fw; // double size down
px = 0;
py = fh;
buff.ctx.drawImage(buff, 0, 0, fw, fh, px, py, fw, fh);
}
// draw the boxes onto the canvas,
ctx.drawImage(buff, 0, 0, 1024, 1024);
requestAnimationFrame(update);
}
update();
.canv {
width:1024px;
height:1024px;
}
<canvas id="can" class = "canv" width=1024 height=1024></canvas>
I have a simple canvas animation: two rectangles move in two different directions. However, I feel this could be simplified more.
http://jsfiddle.net/tmyie/R5wx8/6/
var canvas = document.getElementById('canvas'),
c = canvas.getContext('2d'),
x = 10,
y = 15,
a = 20,
b = 50;
function move() {
c.clearRect(0, 0, 500, 300);
c.fillRect(0, y, 5, 5),
c.fillRect(b, 5, 15, 15);
x++;
y++;
b++
if (y > canvas.height || x > canvas.width) {
y = 0;
x = 0;
}
}
setInterval(move, 100);
For example, what happens if I wanted to create another three shapes? At the moment, I'd have to create more variables for each coordinate:
x++;
y++;
b++
Is there a way I could turn each rectangle into its own object?
You can certainly turn them into objects, for example:
function Rect(x, y, w, h, dltX, dltY, color) {
var me = this;
this.x = x;
this.y = y;
this.width = w;
this.height = h;
this.deltaX = dltX || 0; /// and deltas can be optional
this.deltaY = dltY || 0;
this.color = color || '#000'; /// color is optional
this.update = function(ctx) {
me.x += me.deltaX;
me.y += me.deltaY;
ctx.fillStyle = me.color;
ctx.fillRect(me.x, me.y, me.width, me.height);
}
}
The deltaX and deltaY are how much you want to move the rectangle for each update. If you set these to for example 1 then x and y will be increased with 1 each time update() is called.
Using deltas makes it easy to create bounces (see demo below) by simply reversing the delta value (ie. delta = -delta) as well as things such as acceleration, variate speed, you can feed them through trigonometric functions to have the object move in a specific angle and so forth.
You can used fixed values if you desire but you will discover that deltas are beneficial in the long run (ref. comment: it's actually a very classic method used in for instance the first Pong games :-) ).
Online demo here
Now that we have defined the object we can simply create instances of it and store them in an array:
var rects = [
new Rect(10, 10, 100, 100, 1, -2),
new Rect(100, 1, 50, 50, 2, 1, '#f00'),
...
]
From here it's simply a matter of iterating the array to update each object:
function move() {
ctx.clearRect(0, 0, width, height);
for(var i = 0, r; r = rects[i]; i++) {
/// check any conditions here
r.update(ctx);
}
requestAnimationFrame(move);
}
requestAnimationFrame(move); /// start loop
Here's a slightly simpler version, though in the long term I'd recommend Ken's. In mine the rects are still just property bags, with no behavior on their own.
var canvas = document.getElementById('canvas'),
c = canvas.getContext('2d'),
rects = [{x:0, y:15, w:5, h:5, vx:0, vy:1},
{x:50, y:5, w:15, h:15, vx:1, vy:0}];
function move() {
c.clearRect(0, 0, 500, 300);
for (var i=0; i < rects.length; i++) {
var rect = rects[i];
c.fillRect(rect.x, rect.y, rect.w, rect.h),
rect.x += rect.vx;
rect.y += rect.vy;
}
}
setInterval(move, 100);
(function (canvasID, imgID) {
"use strict";
var canvas, ctx, myImg;
var initialize = function (){
canvas = document.getElementById(canvasID);
myImg = document.getElementById(imgID);
ctx = canvas.getContext('2d');
};
var renderImg = function (x, y, w, h, img, mixImg, filter){
if(ctx) {
ctx.drawImage(img, x, y, w, h);
mixImg(x, y, w, h, filter);
}
};
var mixImg = function (x, y, w, h, filter){
var r, g, b, a, v;
var canvasData = ctx.getImageData(x, y, w, h);
if(filter) {
switch(filter) {
case 'grayscale':
for (var i = 0; i < canvasData.data.length; i+=4){
r = canvasData.data[i];
g = canvasData.data[i+1];
b = canvasData.data[i+2];
v = 0.2126*r + 0.7152*g + 0.0722*b;
canvasData.data[i] = canvasData.data[i+1] = canvasData.data[i+2] = v;
}
break;
case 'retro':
for (var i = 0; i < canvasData.data.length; i+=4){
r = canvasData.data[i];
g = canvasData.data[i+1];
b = canvasData.data[i+2];
a = canvasData.data[i+3];
canvasData.data[i] = r-40;
canvasData.data[i+1] = g-50;
canvasData.data[i+2] = b+23;
canvasData.data[i+3] = 200;
}
break;
case 'instagram':
for (var i = 0; i < canvasData.data.length; i+=4){
r = canvasData.data[i];
g = canvasData.data[i+1];
b = canvasData.data[i+2];
canvasData.data[i] = r+63;
canvasData.data[i+1] = g+41;
canvasData.data[i+2] = 60;
}
break;
} // end of switch
} // end of if
ctx.putImageData(canvasData, x, y);
};
window.onload = function () {
initialize();
if(canvas && canvas.getContext) {
renderImg(0, 0, 250, 250, myImg, mixImg);
ctx.save();
renderImg(250, 0, 250, 250, myImg, mixImg, 'grayscale');
ctx.save();
renderImg(0, 250, 250, 250, myImg, mixImg, 'retro');
ctx.save();
renderImg(250, 250, 250, 250, myImg, mixImg, 'instagram');
ctx.save();
ctx.translate(0, 500);
ctx.restore();
}
};
})('collage', 'img');
How can I mirror the whole canvas(the 4 four images I rendered) I drew and place it on x:0 y:500?
I tried to save each image I drew and then translate it on different point and then restore it.
But nothing shown except then 4 images I drew. What did I do wrong??
translate only change reference point.
After ctx.translate(0, 500) x = 0 is upper left corner while y = 0 is 500 down.
You have to paint or put data into the canvas as well.
However getImageData and putImageData is not affected by transformation matrix so one have to say e.g.:
var trans = [0, 500];
ctx.putImageData(
canvasData,
x + trans[0],
y + trans[1]
);
or put the data into a memory canvas and use drawImage().
Ref.:
The current path, transformation matrix, shadow attributes, global alpha, the clipping region, and global composition operator must not affect the getImageData() and putImageData() methods.
As your code is now you also do a draw + read + redraw onto same region. You could use one draw and then use that as source for all other. There is also no need for the save and restore.
Edit:
Memory canvas as in document.createElement('CANVAS'). It all depends on how and for what purpose etc. A possible set-up could be.
var img = {}, memc = {}, domc = {};
img.src = document.getElementById(imgId);
img.w = img.src.width;
img.h = img.src.height;
/* DOM Canvas */
domc.can = document.getElementById(canvasId);
domc.ctx = domc.can.getContext('2d');
domc.width = img.w * 2; // Going to duplicate image 2 columns.
domc.height = img.h * 4; // Going to duplicate image 4 rows.
/* Memory Canvas */
memc.can = document.createElement('CANVAS');
memc.ctx = memc.can.getContext('2d');
memc.width = img.w;
memc.height = img.h;
I'm writing a program that will draw the sine curve with canvas.
HTML:
<canvas id="mycanvas" width="1000" height="100">
Your browser is not supported.
</canvas>
JavaScript:
var canvas = document.getElementById("mycanvas");
if (canvas.getContext) {
var ctx = canvas.getContext("2d");
ctx.lineWidth = 3;
var x = 0,
y = 0;
var timeout = setInterval(function() {
ctx.beginPath();
ctx.moveTo(x, y);
x += 1;
y = 50 * Math.sin(0.1 * x) + 50;
ctx.lineTo(x, y);
ctx.stroke();
if (x > 1000) {
clearInterval(timeout);
}
}, 10);
}
This works really nice: http://jsfiddle.net/HhGnb/
However, now I can only offer say 100px for the canvas width, so only the leftest 100px of the curve could be seen. http://jsfiddle.net/veEyM/1/
I want to archive this effect: when the right point of the curve is bigger than the width of canvas, the whole curve could move left, so I can see the rightest point of the curve, it's a bit like the curve is flowing to left. Can I do that?
One of the basic ideas of the <canvas> element is that the computer 'forgets' the drawing commands and only saves the pixels, like a bitmap. So to move everything to the left, you need to clear the canvas and draw everything again.
There is also one thing I'd like to advise you - you always start with x = 0 and y = 0, but obviously at x = 0 then y is not necessarily equal to 0 as well. EDIT: implemented this.
Anyway, I ended up with this code: http://jsfiddle.net/veEyM/5/
var canvas = document.getElementById("mycanvas");
var points = {}; // Keep track of the points in an object with key = x, value = y
var counter = 0; // Keep track when the moving code should start
function f(x) {
return 50 * Math.sin(0.1 * x) + 50;
}
if (canvas.getContext) {
var ctx = canvas.getContext("2d");
ctx.lineWidth = 3;
var x = 0,
y = f(0);
var timeout = setInterval(function() {
if(counter < 100) { // If it doesn't need to move, draw like you already do
ctx.beginPath();
ctx.moveTo(x, y);
points[x] = y;
x += 1;
y = f(x);
ctx.lineTo(x, y);
ctx.stroke();
if (x > 1000) {
clearInterval(timeout);
}
} else { // The moving part...
ctx.clearRect(0, 0, 100, 100); // Clear the canvas
ctx.beginPath();
points[x] = y;
x += 1;
y = f(x);
for(var i = 0; i < 100; i++) {
// Draw all lines through points, starting at x = i + ( counter - 100 )
// to x = counter. Note that the x in the canvas is just i here, ranging
// from 0 to 100
ctx.lineTo(i, points[i + counter - 100]);
}
ctx.stroke();
}
counter++;
}, 10);
}