Generate Random Element Position and Prevent Overlapping in JavaScript

Prevent overlap of randomly placed elements

There's a few different ways you can do to achieve this. I find it easiest to try to define the problem in one sentence:

New square's position must be at least X distance from current square positions

Using this sentence, we can make some simple theories as to how the code will work.

Assuming all squares are 50x50 pixels, we can write some checks.

Here are some pseudo code steps we could follow:

  • Generate a random position for newSquare
  • Compare the x and y positions of newSquare to all existing squares
  • If either of the x and y positions of newSquare are further away from the other squares, newSquare can be placed
  • Otherwise, try again

var container = $('#container');var squareSize = 50;var containerSize = 500;
for (var i = 0; i < 20; i++) { var foundSpace = false;
while (!foundSpace) { // Generate random X and Y var randX = Math.floor(Math.random() * (containerSize - squareSize)); var randY = Math.floor(Math.random() * (containerSize - squareSize)); var hitsSquare = false; var squares = container.children();
squares.each(function(index, square) { var square = $(square); // parseInt() because .css() returns a string var left = parseInt(square.css('left')); var top = parseInt(square.css('top')); // Check boundaries var hitsSquareX = Math.abs(left - randX) < squareSize; var hitsSquareY = Math.abs(top - randY) < squareSize;
// Will overlap a square if (hitsSquareX && hitsSquareY) { hitsSquare = true;
// jQuery break .each() return false; } });
// If doesn't overlap any square if (!hitsSquare) { foundSpace = true;
var newSquare = $('<div class="square">');
newSquare.offset({ left: randX, top: randY });
container.append(newSquare); } }}
#container {  position: relative;}
.square { position: absolute; background-color: red; width: 48px; /* border adds 2px */ height: 48px; /* border adds 2px */ border: 1px solid black;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script><div id="container">
</div>

Generate random element position and prevent overlapping in JavaScript

I guess there is no plugin. I will implement this like Sander suggested by making predefined div grid and just loading random images to those divs. It's easier and faster than calculating image dimenssions/positions and does pretty much the same job. Not random but visually looks good :)

How to avoid overlapping of random numbers?

You need to loop over the random number generator for the amount of times you wish to display the created number element in the circle. You can create and return an array of values in randomValue() function, then loop over that array in randNum() function.

Also, I assume you want the output of the appended element to spread out over the entirety of the circle element yes? If so, you need to randomize the position of the element relative to the size of the circle, not the randomValue functions returned value.

EDIT:

OP asked after posting answer: There are some numbers at circles border side which are not properly visible how to solve that?

Because of the box model of elements, the border of the circle element is square, not round though the viewable content of the circle element is round due to its border-radius css.

You can use a helper function that will determine if the randomly generated numbers for x and y, or left and top, are within the border-radius of the circle element using the size of the radius and the center point of the x and y coords of the circle.

Credit for helper function: Simon Sarris:

function pointInCircle(x, y, cx, cy, rad) {
const distancesquared = (x - cx) * (x - cx) + (y - cy) * (y - cy);
return distancesquared <= rad * rad;
}

We wrap everything inside a while loop that checks if a counter has reached 50. Inside the loop we run the function that creates the randomly generated positions -> randomPos(). Then we run a conditional that will check to see if the randomly generated points are within the circle using the function pointInCircle(x, y, cx, cy, rad). If this conditional returns true we create the number elements, style and append the number elements to the circle and iterate our counter value by one.

const circle = document.querySelector(".circle");
const addNumBtn = document.querySelector('#addNumBtn');
// we set the width of the circle element in JS and pass
// it to CSS using a css variable and the root element
const circleWidth = 350;
document.documentElement.style.setProperty('--circleWidth', `${circleWidth}px`);

// NOTE: '~~' -> is quicker form of writing Math.floor()
// runs faster on most browsers.

function randomPos(min, max) {
return ~~(Math.random() * (max - min + 1) + min)
}

// return a random value between 1 and 50
function randomValue(){
return ~~(Math.random() * 50) + 1;
}

function randomColor() {
let r = ~~(Math.random() * 255) + 1;
let g = ~~(Math.random() * 255) + 1;
let b = ~~(Math.random() * 255) + 1;
return `rgba(${r},${g},${b})`
}

function randomSize() {
let size = ~~(Math.random() * 30) + 8;
return `${size}px`
}

// add a helper function to find the center of the
// circle and calculate distance to the edge of circle
// x and y are the randomly generated points
// cx, cy are center of circle x and y repsectively
// rad is circle radius
function pointInCircle(x, y, cx, cy, rad) {
const distancesquared = (x - cx) * (x - cx) + (y - cy) * (y - cy);
return distancesquared <= rad * rad;
}

// you can remove valArray array if you do not want the 50 numbers to not be repeated
const valArray = [];

function randNum() {
// add a counter
let count = 1;
// while loop to check if the counter has reached the target 50
while (count <= 50) {
// set the randomly generated val
let val = randomValue();
// random size = string with px added
let randSize = randomSize();
// random size = the exact number => typeof number
let posSize = randSize.split('px')[0];
// an array to hold our randomly positioned number values
// uses helper function randonPos
// pass in the 1 and the circle width minus the font size
let ran = [randomPos(1, circleWidth - posSize), randomPos(1, circleWidth - posSize)];
// conditional to check bounds of circle using helper function
// we pass in the ran array, center points and radius
// we add buffers to accommodate for the font size
// you can remove !valArray.includes(val) if you do not want the 50 numbers to not be repeated
if (pointInCircle(ran[0], ran[1], circleWidth/2-posSize, circleWidth/2-posSize, circleWidth / 2 - posSize) && !valArray.includes(val)) {
// you can remove valArray.push(val); if you do not want the 50 numbers to not be repeated
valArray.push(val);
// if the conditional returns true,
// create the element, style and append
let randnum = document.createElement("p");
randnum.classList.add('numStyle');
randnum.style.color = randomColor();
randnum.style.fontSize = randSize;
randnum.textContent = val;
randnum.style.position = 'absolute';
randnum.style.top = `${ran[0]}px`;
randnum.style.left = `${ran[1]}px`;
circle.append(randnum);
// iterate the counter
count++;
}
}
}

addNumBtn.addEventListener('click', randNum)
html {
--circle-width: 300px;
}

.circle {
aspect-ratio: 1;
width: var(--circleWidth);
background-color: black;
border-radius: 50%;
position: relative;
overflow: hidden;
padding: .5rem;
}

#addNumBtn {
position: absolute;
top: 0;
left: 0;
}

.numStyle {
font-family: "roboto", sans-serif;
}
//On Clicking button Click Me! I want to place all the random numbers inside the circle //together not one by one, at a time all numbers should be placed inside the circle by //clicking the button Click Me!
<div id="container"></div>
<div class="circle">
</div>
<button id="addNumBtn">Click Me!</button>

Generate numbers in side div at random position without overlapping

You've got most of it figured out. You just need to think of the .container div as a grid to avoid any overlap or outlying items.

Just check out this fiddle.

Here's what the code looks like:

var tilesize = 18, tilecount = 15;
var gRows = Math.floor($(".container").innerWidth()/tilesize);
var gCols = Math.floor($('.container').innerHeight()/tilesize);

var vals = _.shuffle(_.range(tilecount));
var xpos = _.shuffle(_.range(gRows));
var ypos = _.shuffle(_.range(gCols));

_.each(vals, function(d,i){
var $newdiv = $('<div/>').addClass("tile");
$newdiv.css({
'position':'absolute',
'left':(xpos[i] * tilesize)+'px',
'top':(ypos[i] * tilesize)+'px'
}).appendTo( '.container' ).html(d);
});

PS:I have used underscore in my fiddle to make things easier for me and because I personally hate writing for loops.

How to make sure randomly generated Divs don't overlap

Interestingly you have all of the code that you need to achieve this already. That is two things:

  • A function to check if rectangles overlap. That's already in the code: getOverlap
  • A way to check if a new rectangle placement would overlap with an existing one. That's essentially the same as the final loop.

So all I've done is add some code into the div placement loop, which makes it look at every previous div, to see if they would overlap. If they would overlap, it just generates a new position. This continues until a free location is found.

If no location is found after 50 attempts, the rectangle will be randomly placed anywhere, even if it overlaps. This ensures that we don't end up in an infinite loop, which would occur if the window was tiny and there wasn't enough room to place all rectangles in a non-overlapping way.

I've also increased the amount of rectangles to five, which increases the chance for a collision.

That's here:

(I've made the divs smaller in this version, because the snippet window is very small.)

 // Returns largest div's width and height
function getMaxDimension(arr) {
var maxWidth = 0;
for (var i = 0; i < div_selection.length; i++) {
if (div_selection[i].offsetWidth > maxWidth) {
maxWidth = div_selection[i].offsetWidth;
}
}
var maxHeight = 0;
for (var i = 0; i < div_selection.length; i++) {
if (div_selection[i].offsetHeight > maxHeight) {
maxHeight = div_selection[i].offsetHeight;
}
}
var values = {
maxWidth: maxWidth,
maxHeight: maxHeight
};
return values;
}

// Retruns a random number x; min < x < max
function getRandomNumber(min, max) {
return Math.random() * (max - min) + min;
}

// returns the position in xy-space of every corner of a rectangular div
function getOffset(element) {
var position_x = element.offsetLeft;
var position_y = element.offsetTop;
var height_x = element.offsetWidth;
var height_y = element.offsetHeight;
var tolerance = 0; // will get doubled
return {
A: {
y: position_y - tolerance,
x: position_x - tolerance
},
B: {
y: position_y + height_x + tolerance,
x: position_x - tolerance
},
C: {
y: position_y + height_x + tolerance,
x: position_x + height_y + tolerance
},
D: {
y: position_y - tolerance,
x: position_x + height_y + tolerance
}
};
}

// Returns true if any corner is inside the coordinates of the other div
function getOverlap(div1, div2) {
coor_1 = getOffset(document.getElementById(div1));
coor_2 = getOffset(document.getElementById(div2));
return (
(coor_1.A.x <= coor_2.A.x && coor_2.A.x <= coor_1.D.x) && (coor_1.A.y <= coor_2.A.y && coor_2.A.y <= coor_1.B.y) ||
(coor_1.A.x <= coor_2.B.x && coor_2.B.x <= coor_1.D.x) && (coor_1.A.y <= coor_2.B.y && coor_2.B.y <= coor_1.B.y) ||
(coor_1.A.x <= coor_2.C.x && coor_2.C.x <= coor_1.D.x) && (coor_1.A.y <= coor_2.C.y && coor_2.C.y <= coor_1.B.y) ||
(coor_1.A.x <= coor_2.D.x && coor_2.D.x <= coor_1.D.x) && (coor_1.A.y <= coor_2.D.y && coor_2.D.y <= coor_1.B.y)
);
}

// Number to Letter
function getChar(n) {
var ordA = 'a'.charCodeAt(0);
var ordZ = 'z'.charCodeAt(0);
var len = ordZ - ordA + 1;

var s = "";
while (n >= 0) {
s = String.fromCharCode(n % len + ordA) + s;
n = Math.floor(n / len) - 1;
}
return s;
}

var div_selection = document.getElementsByClassName("random");

maxDimensions = getMaxDimension(div_selection);
var widthBoundary = maxDimensions.maxWidth;
var heightBoundary = maxDimensions.maxHeight;

for (var i = 0; i < div_selection.length; i++) {
var isOverlapping = false;
var attemptCount = 0;
do {
randomLeft = getRandomNumber(0, window.innerWidth - widthBoundary);
randomTop = getRandomNumber(0, window.innerHeight - heightBoundary);
div_selection[i].style.left = randomLeft + "px";
div_selection[i].style.top = randomTop + "px";
isOverlapping = false;
for (var j = 0; j < i; j++) {
if (getOverlap(getChar(i), getChar(j))) {
isOverlapping = true;
break;
}
}
} while (++attemptCount < 50 && isOverlapping);
}

// check every element
for (var i = 0; i < div_selection.length; i++) {
for (var j = i + 1; j < div_selection.length; j++) {
console.log(i, j)
console.log(getChar(i), getChar(j))
console.log(getOverlap(getChar(i), getChar(j)))
}
}
div {
width: 60px;
height: 60px;
position: absolute;
}

#a {
background-color: pink;
}

#b {
background-color: lightblue;
}

#c {
background-color: lightgreen;
}

#d {
background-color: silver;
}

#e {
background-color: yellow;
}
<html>

<body>
<div class="random" id="a">Div1</div>
<div class="random" id="b">Div2</div>
<div class="random" id="c">Div3</div>
<div class="random" id="d">Div4</div>
<div class="random" id="e">Div5</div>
</body>

</html>

How to prevent randomly generated images from overlapping using Javascript

The area you want to exclude in the centre of the screen is rather large, and on my little laptop, there isn't even any room to still show the random positioned images.

But anyway, this piece of code should do the job -- I added some infinite loop protection which on my PC was a life-saver:

HTML (added style attribute)

<div id="targetsDiv">
<img src="target2.png" alt="target_shot" class="target"
style="visibility:hidden"/>
</div>

JavaScript:

window.addEventListener('load', function () {
var targetCollection = document.getElementById("targetsDiv");
var template = document.getElementsByClassName("target")[0];

var targetWidth = template.offsetWidth;
var targetHeight = template.offsetHeight;
var x_pixels = window.innerWidth - targetWidth;
var y_pixels = window.innerHeight - targetHeight;
var x_midScreen = window.innerWidth / 2;
var y_midScreen = window.innerHeight / 2;
function spawnTargets (numberOfSpawns) {
var targets = [];
var count = 0; // infinite recursion protection
for (var i = 0; i < numberOfSpawns; i++) {
do {
do {
var x = Math.floor(Math.random()*x_pixels);
var y = Math.floor(Math.random()*y_pixels);
if (count++ > 200) {
console.log('give up');
return;
}
} while ((x >= x_midScreen-450 && x <= x_midScreen+300) && (y >= y_midScreen-350 || y <= y_midScreen+200));

for (var j = 0; j < i; j++) {
if (x >= targets[j].x - targetWidth && x <= targets[j].x + targetWidth &&
y >= targets[j].y - targetHeight && y <= targets[j].y + targetHeight) break; // not ok!
}
} while (j < i);
targets.push({x, y});

img = document.createElement('img');
img.src = template.src;
img.setAttribute('width', targetWidth + 'px');
img.setAttribute('height', targetHeight + 'px');
img.className = template.className;
targetCollection.appendChild(img);
img.style.left = x + "px";
img.style.top = y + "px";
}
}
spawnTargets(3);
});

Randomly positioned divs with no overlapping

Ammend your JavaScript to the following. This stores each box's dimensions and checks each new box against overlaps.

var boxDims = new Array();

function setRandomPos(elements,x,dx){
elements.each(function(){
var conflict = true;
while (conflict) {
fixLeft=(Math.floor(Math.random()*x)*10) + dx;
fixTop = (Math.floor(Math.random()*40)*10) + 180;
$(this).offset({
left: fixLeft,
top: fixTop
});
var box = {
top: parseInt(window.getComputedStyle($(this)[0]).top),
left: parseInt(window.getComputedStyle($(this)[0]).left),
width: parseInt(window.getComputedStyle($(this)[0]).width),
height: parseInt(window.getComputedStyle($(this)[0]).height)
}
conflict = false;
for (var i=0;i<boxDims.length;i++) {
if (overlap(box,boxDims[i])) {
conflict = true;
break;
} else {
conflict = false;
}
}
}
boxDims.push(box)

});
}

function overlap(box1,box2) {
var x1 = box1.left
var y1 = box1.top;
var h1 = box1.height;
var w1 = box1.width;
var b1 = y1 + h1;
var r1 = x1 + w1;
var x2 = box2.left;
var y2 = box2.top;
var h2 = box2.height;
var w2 = box2.width;
var b2 = y2 + h2;
var r2 = x2 + w2;

var buf = 24;

if (b1 + buf < y2 || y1 > b2 + buf || r1 + buf < x2 || x1 > r2 + buf) return false;
return true;
}

setRandomPos($(".boxes"),40,40);

Positioning multiple, random sized, absolutely positioned elements so they don't overlap

I'm not sure if you also want to position the words randomly inside a container, but i've written a fiddle that does just that. You can modify the code to position one word right after the other if you want to though. I think the key part is the method to check if there's a collision.

see http://jsfiddle.net/fZtdt/13/

EDIT: Be aware that this is very simple and unoptimized code. If for example you would add to many words, chances are that the script won't be able to fit all words inside the container, and get into an endless loop.

Javascript Randomly Positioned Div's without overlapping them

The algorithm would look something like this; I might have made a mistake though

  1. Get the document's height and width y = docH, x = docW
  2. Subtract the <div>'s height and width, y = y - divH, x = x - divH
  3. Choose random co-ordinates curX, curY between 0..x, 0..y
  4. newX = curX, newY = curY
  5. For each previous <div>
    1. Call it's info prevX, prevY, prevW, prevH
    2. If prevX < curX then newX = newX + prevW
    3. If prevY < curY then newY = newY + prevH
  6. Append <div> at newX, newY
  7. Save <div> info curX, curY, divW, divH
  8. If there is another <div>, go to step 2.


Related Topics



Leave a reply



Submit