Can anyone help me make my JavaScript faster? - javascript

In my program, I am making divs boxes with JavaScript and it seems to be taking quite a bit of time making a 50X50 grid.It even takes some time to make 20x20 grid. I have looked up how to make my code faster , but none of the suggestions have made an insignificance difference.
This an Odin Project
https://jsfiddle.net/Mulk/yc5rsf1m/#&togetherjs=dVAh1FK7On
$(document).ready(function(){
// Defalut Grid is 16x 16 Grid
CreateBox(16);
CallMeMaybe();
$("#gridbtn").click(function(){
$(".odd").remove();
var numbox =parseInt(prompt("How many boxes would like?"));
CreateBox(numbox);
});
function CreateBox(a) {
var wh = (500/a), i , j ;
for(i=0;i<a;i++){
for(j=0;j<a;j++){
$div = $('<div/>').appendTo('#container').addClass(".odd").attr('class','odd').width(wh).height(wh);
CallMeMaybe();
}}
};
// Play with me
function CallMeMaybe(a){
$(".odd").hover(function(){
var r = Math.floor(Math.random() * (256 - 0) + 0);
var g = Math.floor(Math.random() * (256 - 0) + 0);
var b = Math.floor(Math.random() * (256 - 0) + 0);
var color = "rgb("+r+","+g+","+b+")"
$(this).css("background-color", color);
});
};
// Play with me
});

Instead of appending each new element to the DOM, which is very expensive, append all the elements to a document fragment first, then append the whole thing to the DOM.

This should be faster. It creates an array of the divs and appends them to the DOM all at once. The divs are created with the attributes instead of changing their attributes after they have been added to the DOM. It also attaches the hover handler at the time of creation instead of attaching it after all the boxes have been created. This should significantly reduce the number of DOM manipulations that need to happen.
$(function(){
function createBox(boxesInRow) {
var wh = (500/boxesInRow), i , j, divs = [];
for(i = 0; i < boxesInRow; i++){
for(j = 0; j < boxesInRow; j++){
divs.push($('<div/>', {
"class":'odd',
height: wh + 'px',
width: wh + 'px',
hover: hoverCallback
}));
}
}
$('#container').empty().append(divs);
}
function hoverCallback() {
var r = Math.floor(Math.random() * 256),
g = Math.floor(Math.random() * 256),
b = Math.floor(Math.random() * 256);
$(this).css("background-color", "rgb("+r+","+g+","+b+")");
}
// Default Grid is 16x16 Grid
createBox(16);
$("#gridbtn").click(function() {
var numbox = parseInt(prompt("How many boxes would you like per row?"));
createBox(numbox);
});
});

Related

Resize a group of variably sized images both horizontally & vertically - need help optimising

WARNING CODE CRASHES IN EVERYTHING EXCEPT GOOGLE CHROME
I'm trying to create a feature on our website that takes 8 random images and places them in two rows and dynamically resizes the images to take up the full width of the page.
I've created a jsbin for this to try and demonstrate the issue.
https://jsbin.com/yijemazovi/edit?html,css,js,output
The comments in the code should give you an good idea of what I'm doing. What seems to be happening for everything but Google Chrome is that the while condition is never satisfied so it goes on infinitely and crashes the browser.
Perhaps it is something as simple as I am doing the do/while loop incorrectly or I should just be using a while loop???
Any help is appreciated!
/*****
* Get the overall width of the container that we want to match
**********/
var ContainerWidth = $('.feature-inim-collage .col.span_1_of_1').width();
/*****
* Increase the height of the images until the total sum of the width
* if the 4 images + the gutters is larger than ContainerWidth - then
* stop
**********/
/*****
* Increment in jumps of 10px until we get within 80% of the width of
* the ContainerWidth and then go to a more precise increment of 1px.
* We can increase the px from 10 to 20 or 30 so there are less loops
* but this can cause issues when we look at mobile and there is less
* overall width in the containers and jumping by 30px will be too much
**********/
var i = 0;
do {
$('.feature-inims-top-row .growable-container').css('height', i);
var RowWidth1 = CalculateTotalWidth(1);
if(RowWidth1 < (ContainerWidth*0.8)){
i = i+10;
}else{
i++;
}
}
while (RowWidth1 < (ContainerWidth - 3));
/*****
* Repeat above for the 2nd row
**********/
var i = 0;
do {
$('.feature-inims-bottom-row .growable-container').css('height', i);
var RowWidth2 = CalculateTotalWidth(2);
if(RowWidth2 < (ContainerWidth*0.8)){
i = i+10;
}else{
i++;
}
}
while (RowWidth2 < (ContainerWidth - 3));
/*********
* Calculate the combined width of the images + the gutters
****/
function CalculateTotalWidth(Row) {
var Image1Width = $('.growable-container-1').width();
var Image2Width = $('.growable-container-2').width();
var Image3Width = $('.growable-container-3').width();
var Image4Width = $('.growable-container-4').width();
var Image5Width = $('.growable-container-5').width();
var Image6Width = $('.growable-container-6').width();
var Image7Width = $('.growable-container-7').width();
var Image8Width = $('.growable-container-8').width();
var GutterSize = 24; // (3 gutters # 8px each)
if(Row == 1){
var RowWidth = GutterSize + Image1Width + Image2Width + Image3Width + Image4Width;
}else{
var RowWidth = GutterSize + Image5Width + Image6Width + Image7Width + Image8Width;
}
return RowWidth
}
It turns out the issue with this was that in the CalculateTotalWidth() function I was checking the width of the container the image was in rather than the image itself. As soon as I changed this it worked perfectly.
var Image1Width = $('.growable-container-1 img').width();
instead of
var Image1Width = $('.growable-container-1').width();

algorithm to randomly & efficiently place 100 circles without any overlap?

I am trying to write a script to place 100 circles of varying sizes onto a stage. I've outlined the concise requirements below.
Given the following:
var stage; // contains a "width" and "height" property.
var circle; // the circle class. contains x, y, radius & a unique id property.
var circleArray; // contains 100 circle instances
requirements:
write a function to place 100 circles of varying radius onto the stage.
placements must be random but evenly distributed (no clumping).
placement must be performant - this will be executing on a mobile web browser.
circles must not intersect/overlap other circles.
circle.x >= 0 must be true.
circle.y >= 0 && circle.y <= stage.height must be true.
circles may have any of the following radius sizes (assigned at creation):
150
120
90
80
65
My current attempt is a brute-force method, which does not operate efficiently. If I attempt to insert any more than ~10 circles, the browser hangs. Below is my current implementation, which I am completely OK with throwing away in favor of a more performant / better one.
Here is a live demo (NOTE: there is no actual drawing code, just the logic, but it will still lock up the browser so be warned!!) http://jsbin.com/muhiziduxu/2/edit?js,console
function adjustForOverlap (circleArray) {
// a reference to the circle that is invoking this function.
var _this = this;
// remove this circle from the array we are iterating over.
var arr = circleArray.filter(function (circle){
return circle.id !== _this.id;
});
// while repeat == true, the circle may be overlapping something.
var repeat = true;
while(repeat) {
var hasOverlap = false;
for (var i=0; i<arr.length; i++) {
var other = arr[i];
var dx = _self.x - other.x;
var dy = _self.y - other.y;
var rr = _self.radius + other.radius;
if (dx * dx + dy * dy < rr * rr) {
// if here, then an overlap was detected.
hit = true;
break;
}
}
// if hit is false, the circle didn't overlap anything, so break.
if (hit === false) {
repeat = false;
break;
} else {
// an overlap was detected, so randomize position.
_self.x = Math.random() * (stage.width*2);
_self.y = Math.random() * stage.height;
}
}
}
There are lots of efficient collision detection algorithms. Many of them work by dividing up the space into cells and maintaining a separate data structure with efficient lookup of other objects in the cell. The basic steps are:
Identify a random spot for your new circle
Determine which cells it's in
Look in each of those cells for a collision
If there's a collision, goto 1.
Else, add the new circle to each of the cells it overlaps.
You can use a simple square grid (i.e. a 2-d array) for the cell data structure, or something else like a quadtree. You can also in some cases get a bit of extra speed by trying a cheap-but-coarse collision check first (do the bounding boxes overlap), and if that returns true try the slightly more expensive and exact check.
Update
For quadtrees, check out d3-quadtree, which ought to give you a pretty good implementation, with examples.
For a (very quick, untested) 2-d array implementation:
function Grid(radius, width, height) {
// I'm not sure offhand how to find the optimum grid size.
// Let's use a radius as a starting point
this.gridX = Math.ceil(width / radius);
this.gridY = Math.ceil(height / radius);
// Determine cell size
this.cellWidth = width / this.gridX;
this.cellHeight = height / this.gridY;
// Create the grid structure
this.grid = [];
for (var i = 0; i < gridY; i++) {
// grid row
this.grid[i] = [];
for (var j = 0; j < gridX; j++) {
// Grid cell, holds refs to all circles
this.grid[i][j] = [];
}
}
}
Grid.prototype = {
// Return all cells the circle intersects. Each cell is an array
getCells: function(circle) {
var cells = [];
var grid = this.grid;
// For simplicity, just intersect the bounding boxes
var gridX1Index = Math.floor(
(circle.x - circle.radius) / this.cellWidth
);
var gridX2Index = Math.ceil(
(circle.x + circle.radius) / this.cellWidth
);
var gridY1Index = Math.floor(
(circle.y - circle.radius) / this.cellHeight
);
var gridY2Index = Math.ceil(
(circle.y + circle.radius) / this.cellHeight
);
for (var i = gridY1Index; i < gridY2Index; i++) {
for (var j = gridX1Index; j < gridX2Index; j++) {
// Add cell to list
cells.push(grid[i][j]);
}
}
return cells;
},
add: function(circle) {
this.getCells(circle).forEach(function(cell) {
cell.push(circle);
});
},
hasCollisions: function(circle) {
return this.getCells(circle).some(function(cell) {
return cell.some(function(other) {
return this.collides(circle, other);
}, this);
}, this);
},
collides: function (circle, other) {
if (circle === other) {
return false;
}
var dx = circle.x - other.x;
var dy = circle.y - other.y;
var rr = circle.radius + other.radius;
return (dx * dx + dy * dy < rr * rr);
}
};
var g = new Grid(150, 1000, 800);
g.add({x: 100, y: 100, radius: 50});
g.hasCollisions({x: 100, y:80, radius: 100});
Here's a fully-functional example: http://jsbin.com/cojoxoxufu/1/edit?js,output
Note that this only shows 30 circles. It looks like the problem is often unsolvable with your current radii, width, and height. This is set up to look for up to 500 locations for each circle before giving up and accepting a collision.

Javascript onclick event on newly created elements

I've got several functions all linked so it will ...
create new elements and set their properties and stuff
once elements are in place they should trigger function.
And they do! Kind of...
More like they trigger half of a function that's attached to them upon creation. The part where they onclick trigger a function that starts a loading on my progress bar (which is their purpose) is alright. But the much simpler part, where they hide after click, doesn't.
As the code is quite complex I'll place here larger part of it, so don't panic. Problem might be somewhere else then I expect. Here it is...
// defines function for checkpoint
function checkpointed() {
this.style.display = 'none'; // !here dwells the douch part!
if (toLoad < 100) {
toLoad += 100/$(this).attr('numButs');
var sim = setInterval(progressSim, 50);
}
// defining creation of checkpoints
function checkpoints(num) {
var angle = 4.72,
step = (2 * Math.PI) / num;
for (i = 1; i < num + 1; i++) {
var x = $('#progressBar').width()/2 + radius * Math.cos(angle) ;
var y = $('#progressBar').height()/2 + radius * Math.sin(angle);
angle += step;
var newContent = document.createElement('IMG');
var newCheckpoint = document.createElement('SPAN');
var numButs = document.createAttribute('numButs');
numButs.value = num;
var Class = document.createAttribute('class');
Class.value = 'checkpoint';
var img = document.createAttribute('src');
img.value = 'img/lock.png';
newContent.setAttributeNode(img);
newCheckpoint.setAttributeNode(numButs);
newCheckpoint.setAttributeNode(Class);
$(newCheckpoint).append(newContent);
$('.projectBar').append(newCheckpoint);
x -= 24;
y -= 24;
$(newCheckpoint).offset({top:y, left: x});
newCheckpoint.onclick = checkpointed;
};
};
// creates checkpoints upon clicking on create button
document.getElementById('create').onclick = function(){
checkpoints(document.getElementById('numCheckpoint').value);
$(this).hide();
$('#numCheckpoint').hide();
};
I should probably sum up what is this all about.
I have circular progressBar that measures progression of users project. User says "Hey, my project has like 5 steps (or 20 idc)" and "create" button will make 5 checkpoints placed on the progressBar evenly. By clicking on checkpoints you load the progress bar by 20% per clicked checkpoint.
Don't worry though, I've already figured out the code for loading and the geometrics.
However I'm bit stuck here... on simple onclick functions. Please if you have an idea try achieve it with plain JavaScript or jQuery (trying to do this without other frameworks, libraries or plugins).
EDIT: Just found out that checkpoint are set alright, as they really hide after clicking. Problem is in creation of checkpoints as the loop creates about 15 more checkpoints stacked one on another. So you have to click each of them to hide them all... So problem is in the loop.
EDIT2: Figured it out. The loop for (i = 1; i < num + 1; i++) had the numparameter as a String coming from input field. So simple parseInt() did the trick.
The mixed Jquery and plain Javascript is messing with my head... Any way how about when you create a new element, give it some sort of class. Instead of giving setting onclick, use jQuery's on selector to bind click events to those dynamic elements. Try The Following:
$(document).on("click", ".Checkpoint", function(event) {
$(event.target).hide();
if (toLoad < 100) {
toLoad += 100 / $(this).attr('numButs');
var sim = setInterval(progressSim, 50);
}
});
// defining creation of checkpoints
function checkpoints(num) {
var angle = 4.72,
step = (2 * Math.PI) / num;
for (i = 1; i < num + 1; i++) {
var x = $('#progressBar').width() / 2 + radius * Math.cos(angle);
var y = $('#progressBar').height() / 2 + radius * Math.sin(angle);
angle += step;
var newContent = $('<img></img>');
var newCheckpoint = $('<span></span>');
$("body").append(newCheckpoint);
newContent.attr("numButs", num);
newContent.attr("src", 'img/lock.png');
newContent.addClass("Checkpoint");
$(newCheckpoint).append(newContent);
$('.projectBar').append(newCheckpoint);
x -= 24;
y -= 24;
$(newCheckpoint).offset({
top: y,
left: x
});
}
}
// creates checkpoints upon clicking on create button
$(document).on("click","#create",function(e) {
checkpoints($('#numCheckpoint').val());
$(e.target).hide();
$('#numCheckpoint').hide();
});
Changed stuff to work more in jQuery, hope you don't mind...

javascript - How to insert an img several times into a div using a loop

Hello I am trying to insert an img several times into a div, each time my img will have different properties. My Html code will have a div with an example id called ID_DIV.
I will have a style section into the html code like this img position absolute.
I will preset the variable NumberOfTimes according to the number of img I need into the div.
But I can't manage to make the loop work. Every time I refresh my browser my img appears in a different position, but it wont appear a "NumberOfTImes" times.
I have edited my code into this. But the loop wont work anyways. Still without knowing what it is.
function generateImg(){
var numberOfTimes=prompt("how many times?");
var div_id1=document.getElementById("div_id1");
do{
var oImg=document.createElement("img"); // Creates an oimg node
oImg.setAttribute('src', 's0me_s0urce'); // sets the source for img file
div_id1.appendChild(oImg); // append the img to #id Div.
document.querySelector('img').style.top = Math.floor(Math.random()*401)+'px';
document.querySelector('img').style.left = Math.floor(Math.random()*401)+'px';
numberOfTimes--;
}while(numberOfTimes>0);
}
Please Help. I can't find where is the mistake in this logic.
you need to declare "i" outside of the loop otherwise it will not decrement and will only ever be "NumberOfTimes - 1" and therefore your loop will loop repeadeteldy with the image in the same location. This is also assuming that "NumberOfTimes" is in in the same scope to be used in the first place. Note that I have moved the location of "i" and added a decrement to it in the loop.
function RandomPositionImgs(){
var i=NumberOfTimes;
do{
var oImg=document.createElement("img"); // Creates an oimg node
oImg.setAttribute('src', 'some_source'); // sets the source for img file
document.getElementById("ID_DIV").appendChild(oImg); // append the img to leftSide Div.
document.querySelector('img').style.top = Math.floor(Math.random()*401)+'px'; // random positioning
document.querySelector('img').style.left = Math.floor(Math.random()*401)+'px'; // random positioning
i--;
}while(i>0);
}
Thank you all for your answers you made me realize what I was doing wrong. This is the correct code I did after your comments, its working perfect.
function generateImgs(){
var numberOfTimes=prompt("how many times?");
for(i=0;i<numberOfFaces;i++){
var oImg=document.createElement("img"); // Creates an oimg node
oImg.setAttribute('src', 'valid source'); // sets the source for img file
document.getElementById("div id").appendChild(oImg); // append the img to Div.
oImg.style.top = Math.floor(Math.random()*401)+'px';
oImg.style.left = Math.floor(Math.random()*401)+'px';
}
}
My answer is obsolete now. Anyway, here is a way to deal with overlapping images:
var img, i, x, y;
var size = 48, w = 5, h = 3;
var grid = new Array(w * h).map(x => false);
var n = 5; // n > grid.length => infinite loop
var src = 'https://i.stack.imgur.com/6gwfY.gif?s=48&g=1';
var ct = document.getElementById('ct');
ct.style.width = size * w + 'px';
ct.style.height = size * h + 'px';
while (n-- > 0) {
i = next(), x = i % w, y = (i - x) / w;
img = document.createElement('img');
img.setAttribute('src', src);
img.style.left = size * x + 'px';
img.style.top = size * y + 'px';
ct.appendChild(img);
}
function next () {
var l = grid.length;
var i = Math.floor(Math.random() * l);
// as long as this position is not free, try the next one
while (grid[i]) i = (i + 1) % l;
return grid[i] = true, i;
}
img{position:absolute}
#ct{background:#eee}
#ct{position:relative}
<div id="ct"></div>

Constrained random numbers always total the constraint with javascript/jquery

I'm a JS amateur. I'm looking to randomly set the width and opacity of a bunch of span elements to create an animation effect.
At the moment, the width is set and re-set using setInterval every second which is almost working fine...
$(function () {
setInterval(function () {
// Variables for background colour
var minFloat = 0.3,
maxFloat = 0.9,
randFloat = Math.floor(Math.random() * (maxFloat - minFloat + 1)) + minFloat;
// Set random width
$('.footerbars span').css('width', Math.random() * 10 + '%');
// Set random alpha
$('.footerbars span').css('background-color', 'rgba(58,130,255,' + randFloat + ')');
}, 1000);
});
What I need is:
the widths of the spans to be each be a different percentage, and for all of those percentages to always total 100%.
and the background alpha channel to be random for each span
Any help is awesome!! Thanks in advance
The first problem is that all widths and backgrounds will be set to be the same as the random numbers are only generated once. You need something like:
$('.footerbars span').each(function(i, e) {
$(e).css('width', (Math.random() * 10) + '%')
.css('background-color', 'rgba('58,130,255,' + ((Math.random() * 0.6) + 0.3) +')');
});
The problem with this is that the widths may not all add up to 100%. To solve this we need to first generate the set of random numbers, then scale them so they add up to 100, and then apply them to the spans.
var numSpans = $('.footerbars span').length;
var widths = [];
var total = 0;
for(var i = 0; i < numSpans; i++) {
widths[i] = Math.random()+1; // generate a new random width for this span - and make it definitely not zero
total += widths[i]; // and update the total width so far;
}
// Now we know what the total random number is (something between numSpans and 2*numSpans)
// we can scale these so the sum actually is 100
for(var i = 0; i < numSpans; i++)
widths[i] = Math.floor(widths[i] * (100 / total));
Now widths[i] contains the % width of the ith span in .footerbars, so amend the second line of the first bit of code to be:
$(e).css('width', widths[i])
Full code:
var numSpans = $('.footerbars span').length;
var widths = [];
var total = 0;
for(var i = 0; i < numSpans; i++) {
widths[i] = Math.random()+1; // generate a new random width for this span - and make it definitely not zero
total += widths[i]; // and update the total width so far;
}
// Now we know what the total random number is (something between numSpans and 2*numSpans)
// we can scale these so the sum actually is 100
for(var i = 0; i < numSpans; i++)
widths[i] = Math.floor(widths[i] * (100 / total));
$('.footerbars span').each(function(i, e) {
$(e).css('width', widths[i])
.css('background-color', 'rgba('58,130,255,' + ((Math.random() * 0.6) + 0.3) +')');
});

Categories