I'm making a project using canvas and svg. I've drawn a pattern using canvas with 4 Circles and in each 4 circles there's one inner circle. The problem is, I now need to make those 4 circles and inner circles smaller in order to insert more of them, up to 30, on my screen. Here's my code.
function telaCirculos(x,y,r,angIn,angFim,corFundo,corLinha){
pintor.fillStyle=corFundo;
pintor.strokeStyle=corLinha;
pintor.beginPath();
pintor.arc(x,y,r,angIn,angFim);
pintor.closePath();
pintor.stroke(); pintor.fill();
}
then I just call my function in the script like so:
telaCirculos(250,500,250,Math.PI,-2*Math.PI,"#449779","#449779");
telaCirculos(250,500,200,Math.PI,-2*Math.PI,"#013D55","#013D55");
telaCirculos(500,250,250,Math.PI/2,3*Math.PI/2,"#E6B569","#E6B569");
telaCirculos(500,250,200,Math.PI/2,3*Math.PI/2,"#AA8D49","#AA8D49");
telaCirculos(0,250,250,Math.PI/2,-3*Math.PI/2,"#E6B569","#E6B569");
telaCirculos(0,250,200,Math.PI/2,-3*Math.PI/2,"#AA8D49","#AA8D49");
telaCirculos(250,0,250,0,-Math.PI,"#449779","#449779");
telaCirculos(250,0,200,0,-Math.PI,"#013D55","#013D55");
This draws the circles with my desired coordinates. Now I need to fill my screen with more of these. I'll post some screenshots.
What I have done:
What I need to do:
An alternative to GameAlchemist's solution, is to think of the pattern as rows and columns of circles. You can use nested loops to draw the rows and columns of circles. Each row partially overlaps the previous row by half a circle. Every other row offset is horizonally offset by half a circle. Since rows overlap, each row covers a vertical distance of radius. To compute number of rows, you basically divide height by radius. Since the columns do not overlap, each column covers a horizonal distance of 2 * radius. To compute number of columns, you basically divide width by radius. Since the first circle can be half outside the area being painting, you actually have to add radius to height and width before dividing by 2 * radius and radius, respectively. You can use arrays to hold the colors and offsets. Then the function to fill a rectangular area with circles could look like...
function drawCircles(x, y, width, height, outerRadius, innerRadius) {
var outerColors = ["#449779", "#E6B569"];
var innerColors = ["#013d55", "#AA8D49"];
var offsets = [0, outerRadius];
var startAngle = 0;
var endAngle = 2 * Math.PI;
var iMax = (width + outerRadius) / (outerRadius);
for (i = 0; i < iMax; i++) {
var outerColor = outerColors[i % outerColors.length];
var innerColor = innerColors[i % innerColors.length];;
var offset = offsets[i % offsets.length];
var jMax = (height + outerRadius - offset) / (2 * outerRadius);
for (j = 0; j < jMax; j++) {
var cx = x + j * 2 * outerRadius + offset;
var cy = y + height - i * outerRadius;
telaCirculos(cx, cy, outerRadius, startAngle, endAngle, outerColor, outerColor)
telaCirculos(cx, cy, innerRadius, startAngle, endAngle, innerColor, innerColor);
}
}
}
You would then call the function with the dimensions of the canvas and the desired size of circles...
var outerRadius = canvasWidth / numberOfCircles;
var innerRadius = 0.8 * outerRadius;
drawCircles(0, 0, canvasWidth, canvasHeight, outerRadius, innerRadius);
Your best take here is to create a pattern out of your circle, then to fill your main canvas with that pattern.
To do that, create a temporary canvas on which you draw your pattern then create a pattern out of it.
document.body.style.margin = 0;
var cv = document.getElementById('cv');
var mainContext = cv.getContext('2d');
var canvasWidth;
var canvasHeight;
function update() {
canvasWidth = cv.width = window.innerWidth;
canvasHeight = cv.height = window.innerHeight;
drawCircles();
}
update();
window.onresize = update;
function drawCircles() {
mainContext.save();
// full screen rect
mainContext.rect(0, 0,
mainContext.canvas.width, mainContext.canvas.height);
// scale to put more circles inside ( :-) )
mainContext.scale(1 / 10, 1 / 10);
mainContext.fillStyle = buildPattern();
mainContext.fill();
mainContext.restore();
}
function buildPattern() {
var tempCv = document.createElement('canvas');
tempCv.width = 500;
tempCv.height = 500;
var pintor = tempCv.getContext('2d');
telaCirculos(250, 500, 250, Math.PI, -2 * Math.PI, "#449779", "#449779");
telaCirculos(250, 500, 200, Math.PI, -2 * Math.PI, "#013D55", "#013D55");
telaCirculos(500, 250, 250, Math.PI / 2, 3 * Math.PI / 2, "#E6B569", "#E6B569");
telaCirculos(500, 250, 200, Math.PI / 2, 3 * Math.PI / 2, "#AA8D49", "#AA8D49");
telaCirculos(0, 250, 250, Math.PI / 2, -3 * Math.PI / 2, "#E6B569", "#E6B569");
telaCirculos(0, 250, 200, Math.PI / 2, -3 * Math.PI / 2, "#AA8D49", "#AA8D49");
telaCirculos(250, 0, 250, 0, -Math.PI, "#449779", "#449779");
telaCirculos(250, 0, 200, 0, -Math.PI, "#013D55", "#013D55");
var pattern = pintor.createPattern(tempCv, 'repeat');
return pattern;
function telaCirculos(x, y, r, angIn, angFim, corFundo, corLinha) {
pintor.fillStyle = corFundo;
pintor.strokeStyle = corLinha;
pintor.beginPath();
pintor.arc(x, y, r, angIn, angFim);
pintor.closePath();
pintor.stroke();
pintor.fill();
}
}
<canvas id='cv'></canvas>
Related
I am trying to make a square that rotates in place, however my square is spiraling inward, and I have no idea why. Here is the code, if someone could please explain what is happening as to why it is not just spinning in place.
var angle = 2 * (Math.PI / 180);
var rotate = [
[Math.cos(angle),Math.sin(angle)],
[-Math.sin(angle),Math.cos(angle)]
];
var points = [[300,0],[0,300],[-300,0],[0,-300]];
init.ctx.translate(init.canvas.width/2,init.canvas.height/2);
function loop(){
draw();
}
setInterval(loop,10);
function draw(){
init.ctx.beginPath();
init.ctx.moveTo(points[0][0],points[0][1]);
init.ctx.lineTo(points[1][0],points[1][1]);
init.ctx.lineTo(points[2][0],points[2][1]);
init.ctx.lineTo(points[3][0],points[3][1]);
init.ctx.closePath();
init.ctx.stroke();
for(let i=0;i<points.length;i++){
init.ctx.beginPath();
init.ctx.fillStyle = "red";
init.ctx.fillRect(points[i][0],points[i][1],5,5);
points[i][0] = points[i][0]*rotate[0][0] + points[i][1]*rotate[0][1];
points[i][1] = points[i][0]*rotate[1][0] + points[i][1]*rotate[1][1];
}
}
So, you are applying a small rotation each time draw is called, specifically 1/180th of a full rotation. Problem is that you are relying on floating point math to give you exact values, and it's not because it doesn't. This is compounded by the points array being calculated by iterations. I suggest calculate the new points on each step through draw by applying the correct rotate matrix for your current angle to the starting points.
var angle = 0;
var startPoints = [[300,0],[0,300],[-300,0],[0,-300]];
var points = [[300,0],[0,300],[-300,0],[0,-300]];
init.ctx.translate(init.canvas.width/2,init.canvas.height/2);
function loop(){
draw();
}
setInterval(loop,10);
function draw(){
init.ctx.beginPath();
init.ctx.moveTo(points[0][0],points[0][1]);
init.ctx.lineTo(points[1][0],points[1][1]);
init.ctx.lineTo(points[2][0],points[2][1]);
init.ctx.lineTo(points[3][0],points[3][1]);
init.ctx.closePath();
init.ctx.stroke();
angle = angle + Math.PI / 90;
var rotate = [
[Math.cos(angle),Math.sin(angle)],
[-Math.sin(angle),Math.cos(angle)]
];
for(let i=0;i<points.length;i++){
init.ctx.beginPath();
init.ctx.fillStyle = "red";
init.ctx.fillRect(points[i][0],points[i][1],5,5);
points[i][0] = startPoints[i][0]*rotate[0][0] + startPoints[i][1]*rotate[0][1];
points[i][1] = startPoints[i][0]*rotate[1][0] + startPoints[i][1]*rotate[1][1];
}
}
Some tips to improve your code.
As a beginner I can see some bad habits creeping in and as there is already an answer I thought I would just give some tips to improve your code.
Don't use setInterval to create animations. requestAnimationFrame gives much better quality animations.
Arrays were created in high level languages to make life easier, not harder.
You have painfully typed out
init.ctx.beginPath();
init.ctx.moveTo(points[0][0],points[0][1]);
init.ctx.lineTo(points[1][0],points[1][1]);
init.ctx.lineTo(points[2][0],points[2][1]);
init.ctx.lineTo(points[3][0],points[3][1]);
init.ctx.closePath();
init.ctx.stroke();
That would be a nightmare if you had 100 points. Much better to create a generic function to do that for you.
function drawShape(ctx,shape){
ctx.beginPath();
for(var i = 0; i < shape.length; i++){
ctx.lineTo(shape[i][0], shape[i][1]);
}
ctx.closePath();
ctx.stroke();
}
Now you can render any shape on any canvas context with the same code.
drawShape(init.ctx,points); // how to draw your shape.
If you use a uniform scale then you can shorten the transform a little by reusing the x axis of the transformation
var rotate = [
[Math.cos(angle),Math.sin(angle)],
[-Math.sin(angle),Math.cos(angle)]
];
Note how the second two values are just the first two swapped with the new x negated. You can also include a scale in that and just hold the first two values.
var angle = ?
var scale = 1; // can be anything
// now you only need two values for the transform
var xAx = Math.cos(angle) * scale; // direction and size of x axis
var xAy = Math.sin(angle) * scale;
And you apply the transform to a point as follows
var px = ?; // point to transform
var py = ?;
var tx = px * xAx - py * xAy;
var ty = px * xAy + py * xAx;
And to add a origin
var tx = px * xAx - py * xAy + ox; // ox,oy is the origin
var ty = px * xAy + py * xAx + oy;
But is is much better to let the canvas 2D API do the transformation for you. The example below shows the various methods described above to render your box and animate the box.
Example using best practice.
const ctx = canvas.getContext("2d");
var w = canvas.width; // w,h these are set if canvas is resized
var h = canvas.height;
var cw = w / 2; // center width
var ch = h / 2; // center height
var globalScale = 1; // used to scale shape to fit the canvas
var globalTime;
var angle = Math.PI / 2;
var rotateRate = 90; // deg per second
var points = [
[300, 0],
[0, 300],
[-300, 0],
[0, -300]
];
var maxSize = Math.hypot(600, 600); // diagonal size used to caculate scale
// so that shape fits inside the canvas
// Add path to the current path
// shape contains path points
// x,y origin of shape
// scale is the scale of the shape
// angle is the amount of rotation in radians.
function createShape(shape, x, y, scale, angle) {
var i = 0;
ctx.setTransform(scale, 0, 0, scale, x, y); // set the scale and origin
ctx.rotate(angle); // set the rotation
ctx.moveTo(shape[i][0], shape[i++][1]);
while (i < shape.length) { // create a line to each point
ctx.lineTo(shape[i][0], shape[i++][1]);
}
}
// draws fixed scale axis aligned boxes at vertices.
// shape contains the vertices
// vertSize size of boxes drawn at verts
// x,y origin of shape
// scale is the scale of the shape
// angle is the amount of rotation in radians.
function drawVertices(shape, vertSize, x, y, scale, angle) {
ctx.setTransform(1, 0, 0, 1, x, y);
const xAx = Math.cos(angle) * scale; // direction and size of x axis
const xAy = Math.sin(angle) * scale;
var i = 0;
while (i < shape.length) {
const vx = shape[i][0]; // get vert coordinate
const vy = shape[i++][1]; // IMPORTANT DONT forget i++ in the while loop
ctx.fillRect(
vx * xAx - vy * xAy - vertSize / 2, // transform and offset by half box size
vx * xAy + vy * xAx - vertSize / 2,
vertSize, vertSize
);
}
}
// draws shape outline and vertices
function drawFullShape(shape, scale, angle, lineCol, vertCol, lineWidth, vertSize) {
// draw outline of shape
ctx.strokeStyle = lineCol;
ctx.lineWidth = lineWidth / scale; // to ensure that the line with is 1 pixel
// set the width to in inverse scale
ctx.beginPath();
// shape origin at cw,ch
createShape(shape, cw, ch, scale, angle);
ctx.closePath();
ctx.stroke();
// draw the vert boxes.
ctx.fillStyle = vertCol;
drawVertices(shape, vertSize, cw, ch, scale, angle);
}
function loop(timer) {
globalTime = timer;
if (w !== innerWidth || h !== innerHeight) { // check if canvas need resize
cw = (w = canvas.width = innerWidth) / 2;
ch = (h = canvas.height = innerHeight) / 2;
globalScale = Math.min(w / maxSize, h / maxSize);
}
ctx.setTransform(1, 0, 0, 1, 0, 0); // reset transform
ctx.clearRect(0, 0, w, h);
const innerAngle = globalTime * (rotateRate * (Math.PI / 180)) / 1000;
drawFullShape(points, globalScale, angle, "black", "red", 2, 6);
drawFullShape(points, globalScale * 0.5, innerAngle, "black", "red", 2, 6);
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
canvas {
position: absolute;
top: 0px;
left: 0px;
}
<canvas id="canvas"></canvas>
I'm currently trying to create a speechbubble for my canvas using javascript.
Problem 1:
I'm adding the width in the following way: ctx.measureText(text).width + 40 to make sure the width of the speechbubble is flexible and depends on the var text = 'Speechbubble test'; input. Also adding 40 pixels to add a margin left and right!
How could I get the height of the text to make it flexible also? I've been checking but couldn't find something similare to the measureText method and it doesn't seem to exist.
Is there any way to get the actual height of the text? I'm asking because I'm planning to add a line break to it. eG: multi-line support (Break the text line after a specific amount of letters).
Problem 2:
I'm trying to add a little speech bubble direction pointer to the speechbubble. Sorry for the way I'm describing it this way, don't know the word for it. But I'm talking about the following:
How could I add the following to my speechbubble?
I appreciate any sugestions and help.
The current example:
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext("2d");
ctx.font = "13px Helvetica";
var text = 'Speechbubble test';
component(ctx, ctx.measureText(text).width, 70, "#37f", 10, 10, 16, text);
function component(ctx, width, height, color, x, y, radius, text)
{
// Variables
var width = width + 40;
var height = height;
var x = x;
var y = y;
var radius = Math.min(radius, Math.min(width, height) / 2);
var color = color;
var pi2 = Math.PI * 2;
var r = radius;
var w = width;
var h = height;
// Transparent background
//ctx.fillStyle = "rgba(255, 255, 255, 0.5)";
ctx.fillStyle = color;
ctx.setTransform(1, 0, 0, 1, x, y);
// Draw arc
ctx.beginPath();
ctx.arc(r , r , r, pi2 * 0.5 , pi2 * 0.75);
ctx.arc(w - r, r, r, pi2 * 0.75, pi2);
ctx.arc(w - r, h - r, r, 0, pi2 * 0.25);
ctx.arc(r , h - r, r, pi2 * 0.25, pi2 * 0.5);
ctx.fill();
ctx.setTransform(1, 0, 0, 1, 0, 0);
// Text fillstyle
ctx.fillStyle = "#fff";
ctx.fillText(text, x + 20, y + 40);
}
<canvas width="500" height="500" id="canvas"></canvas>
The problem is that although the specs defines a way to measure height via a the TextMetrics object, no vendor has actually implemented it at current time.
So we have to cheat a bit to get the actual size from the font.
One way to do this is to temporary insert a DOM element and get the height from that:
console.log(getTextHeight("Hello", "20px sans-serif"));
function getTextHeight(txt, font) {
var el = document.createElement('div'), height;
el.style.cssText = "position:fixed;padding:0;left:-9999px;top:-9999px;font:" + font;
el.textContent = txt;
document.body.appendChild(el);
height = parseInt(getComputedStyle(el).getPropertyValue('height'), 10);
document.body.removeChild(el);
return height
}
For use with canvas just pass in context.font as your font argument:
var fontHeight = getTextHeight("Hello", ctx.font);
As to problem two:
You can break up the bottom line path so to speak, of the rounded rectangle by inserting lineTo() calls, like:
var ctx = c.getContext("2d"), r = 20, w = 290, h = 100, pi2 = Math.PI*2;
var ap = w * 0.2, aw = 20, ah = 30; //arrow position, width, height
ctx.translate(1.5,0.5);
// Rounded rectangle
ctx.beginPath();
ctx.arc(r , r , r, pi2 * 0.5 , pi2 * 0.75);
ctx.arc(w - r, r, r, pi2 * 0.75, pi2);
ctx.arc(w - r, h - r, r, 0, pi2 * 0.25); // bottom right corner
ctx.lineTo(w - ap, h); // inserts an arrow (following clock-wise)
ctx.lineTo(w - ap, h + ah);
ctx.lineTo(w - ap - aw, h);
ctx.arc(r , h - r, r, pi2 * 0.25, pi2 * 0.5); // bottom left corner
ctx.closePath();
ctx.stroke();
<canvas id=c></canvas>
Problem: Im drawing a spaceship on the canvas. Upon hovering over it's x/y, im drawing an arc on the canvas, indicating the starships weapons angle and range (considering the starships current Baring/facing). Currently the determined angle is being drawn in green and extends as far as the weapons range value allows.
However, i would like to use a gradiant to fill the determined arc to indicate a drop-off in accuracy (i.e. gradiant begins at green, moves to orange, turns red the further away from the starships Position the angle is).
However, i dont know how i could replace my stock ctx.fill() on the drawn arc with a gradiant.
var ship {
loc: {x, y}, // lets say 100, 100
facing: facing // lets say facing 0, i.e. straight right
weapons: objects (range, startArc, endArc) // lets say 50, 300, 60 -> 120 degree angle, so -60 and +60 from facing (0/360)
}
for (var i = 0; i < weapon.arc.length; i++){
var p1 = getPointInDirection(weapon.range, weapon.arc[i][0] + angle, pos.x, pos.y);
var p2 = getPointInDirection(weapon.range, weapon.arc[i][1] + angle, pos.x, pos.y)
var dist = getDistance( {x: pos.x, y: pos.y}, p1);
var rad1 = degreeToRadian(weapon.arc[i][0] + angle);
var rad2 = degreeToRadian(weapon.arc[i][1] + angle);
fxCtx.beginPath();
fxCtx.moveTo(pos.x, pos.y);
fxCtx.lineTo(p1.x, p1.y);
fxCtx.arc(pos.x, pos.y, dist, rad1, rad2, false);
fxCtx.closePath();
fxCtx.globalAlpha = 0.3;
fxCtx.fillStyle = "green";
fxCtx.fill();
fxCtx.globalAlpha = 1;
}
is it possible to replace the arc/globalalpha/fill so use a gradiant flow instead of it being colored fixed and if so, how ?
thanks
To fill an arc with a gradient, animated just for the fun.
Uses a radial gradient and set colour stops as a fraction of distance.
The function createRadialGradient takes 6 numbers the position x,y and start radius and the position x,y and end radius of the gradient.
Colour stops are added via the gradient object addColorStop function that takes a value 0 inner to 1 outer part of the gradient and the colour as a CSS color string. "#F00" or "rgba(200,0,0,0.5)" or "RED"
Then just use the gradient as the fill style.
var canvas = document.createElement("canvas");
document.body.appendChild(canvas);
var ctx = canvas.getContext("2d");
function update(time) {
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
// position of zones in fractions
var posRed = 0.8 + Math.sin(time / 100) * 0.091;
var posOrange = 0.5 + Math.sin(time / 200) * 0.2;
var posGreen = 0.1 + Math.sin(time / 300) * 0.1;
var pos = {
x: canvas.width / 2,
y: canvas.height / 2
};
var dist = 100;
var ang1 = 2 + Math.sin(time / 1000) * 0.5;
var ang2 = 4 + Math.sin(time / 1300) * 0.5;
var grad = ctx.createRadialGradient(pos.x, pos.y, 0, pos.x, pos.y, dist);
grad.addColorStop(0, "#0A0");
grad.addColorStop(posGreen, "#0A0");
grad.addColorStop(posOrange, "#F80");
grad.addColorStop(posRed, "#F00");
grad.addColorStop(1, "#000");
ctx.fillStyle = grad;
ctx.beginPath();
ctx.moveTo(pos.x, pos.y);
ctx.arc(pos.x, pos.y, dist, ang1, ang2);
ctx.fill();
requestAnimationFrame(update);
}
requestAnimationFrame(update);
I'm creating a color wheel (picker) and I want to know the fastest most efficient way to display the color wheel. I'm currently using JavaScript to generate it with a canvas. I think the other options are an actual image or data URI. If there is a faster way please let me know.
What's the fastest most efficient way to show the color picker?
Color Wheel using JavaScript / canvas
JSFiddle
var colorDisc = document.getElementById('surface'),
colorDiscRadius = colorDisc.offsetHeight / 2;
var drawDisk = function(ctx, coords, radius, steps, colorCallback) {
var x = coords[0] || coords, // coordinate on x-axis
y = coords[1] || coords, // coordinate on y-axis
a = radius[0] || radius, // radius on x-axis
b = radius[1] || radius, // radius on y-axis
angle = 360,
rotate = 0,
coef = Math.PI / 180;
ctx.save();
ctx.translate(x - a, y - b);
ctx.scale(a, b);
steps = (angle / steps) || 360;
for (; angle > 0; angle -= steps) {
ctx.beginPath();
if (steps !== 360) ctx.moveTo(1, 1); // stroke
ctx.arc(1, 1, 1, (angle - (steps / 2) - 1) * coef, (angle + (steps / 2) + 1) * coef);
if (colorCallback) {
colorCallback(ctx, angle);
} else {
ctx.fillStyle = 'black';
ctx.fill();
}
}
ctx.restore();
},
drawCircle = function(ctx, coords, radius, color, width) { // uses drawDisk
width = width || 1;
radius = [
(radius[0] || radius) - width / 2, (radius[1] || radius) - width / 2
];
drawDisk(ctx, coords, radius, 1, function(ctx, angle) {
ctx.restore();
ctx.lineWidth = width;
ctx.strokeStyle = color || '#000';
ctx.stroke();
});
};
if (colorDisc.getContext) {
drawDisk( // HSV color wheel with white center
colorDisc.getContext("2d"), [colorDisc.width / 2, colorDisc.height / 2], [colorDisc.width / 2 - 1, colorDisc.height / 2 - 1],
360,
function(ctx, angle) {
var gradient = ctx.createRadialGradient(1, 1, 1, 1, 1, 0);
gradient.addColorStop(0, 'hsl(' + (360 - angle + 0) + ', 100%, 50%)');
gradient.addColorStop(1, "#FFFFFF");
ctx.fillStyle = gradient;
ctx.fill();
}
);
drawCircle( // gray border
colorDisc.getContext("2d"), [colorDisc.width / 2, colorDisc.height / 2], [colorDisc.width / 2, colorDisc.height / 2],
'#555',
3
);
}
<canvas id="surface" width="500" height="500"></canvas>
I think an image would be faster, but it would be difficult to resize it without getting all kinds of scaling artifacts. So I would go with canvas.
However, there is a third option that I think is worth considering: angular gradient in CSS. Here is a way to do it with existing features: https://css-tricks.com/conical-gradients-css/
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>