Arranging div elements in a circular formation with the help of JavaScript

As I strive to arrange 15 div elements evenly in a circle with a radius of 150px, my current code seems to produce an oddly shaped ellipse that overlaps. Can you help rectify this issue?

Check out the Fiddle for reference.

// To maintain global reference to the div#main element, initially assign it ... in a meaningful location :)
var main = document.getElementById('main');
var circleArray = [];

// Function to move a circle based on mouse approach distance
var moveCircle = function(circle, dx, dy) {

};

// Examination of all circle elements to determine any necessary movements.
var checkMove = function() {

};
var setup = function() {
  for (var i = 0; i < 15; i++) {
    // Create an element, add it to array, and trigonometrically assign its coordinates.
    // Then append to "main" div
    var circle = document.createElement('div');
    circle.className = 'circle number' + i;
    circleArray.push(circle);
    circleArray[i].posx = Math.round((150 * Math.cos(i * (2 * Math.PI / 15)))) + 'px';
    circleArray[i].posy = Math.round((150 * Math.sin(i * (2 * Math.PI / 15)))) + 'px';
    circleArray[i].style.position = "relative";
    circleArray[i].style.top = circleArray[i].posy;
    circleArray[i].style.left = circleArray[i].posx;
    main.appendChild(circleArray[i]);
  }
};
setup();
window.addEventListener('load', function() {

});
div {
  box-sizing: border-box;
}
div#main {
  position: absolute;
  left: 50%;
  top: 50%;
}
div.circle {
  position: absolute;
  width: 20px;
  height: 20px;
  border: 2px solid black;
  border-radius: 10px;
  -webkit-border-radius: 10px;
  -moz-border-radius: 10px;
}
<div id="main"></div>

If you have any insights or recommendations on what might be going wrong here, please share them. Thank you!

Answer №1

The formula for determining a point on a circle is quite simple:

(x, y) = (r * cos(θ), r * sin(θ))

where r represents the radius of the circle and θ is the angle measured in radians.


If your code is producing an elliptical shape instead of a perfect circle, it's likely due to how you are setting the CSS values for .top and .left. By taking into account that the top-left corner serves as the reference point, I was able to rectify this issue in your code.

Modifications made to your code:

  1. An array named theta has been added to store all the angles.

    var theta = [0, Math.PI / 6, Math.PI / 4, Math.PI / 3, Math.PI / 2, 2 * (Math.PI / 3), 3 * (Math.PI / 4), 5 * (Math.PI / 6), Math.PI, 7 * (Math.PI / 6), 5 * (Math.PI / 4), 4 * (Math.PI / 3), 3 * (Math.PI / 2), 5 * (Math.PI / 3), 7 * (Math.PI / 4), 11 * (Math.PI / 6)];
    

    The image below showcases all the utilized angles.

  2. A new array named colors has been included to hold various color options.

    var colors = ['red', 'green', 'purple', 'black', 'orange', 'yellow', 'maroon', 'grey', 'lightblue', 'tomato', 'pink', 'maroon', 'cyan', 'magenta', ‘blue’, 'chocolate', 'DarkSlateBlue'];
    
  3. Adjusted the trigonometric equations within your code.

    circleArray[i].posx = Math.round(radius * (Math.cos(theta[i]))) + 'px';
    circleArray[i].posy = Math.round(radius * (Math.sin(theta[i]))) + 'px';
    
  4. Altered the assignment method for .top and .left.

    circleArray[i].style.top = ((mainHeight / 2) - parseInt(circleArray[i].posy.slice(0, -2))) + 'px';
    circleArray[i].style.left = ((mainHeight / 2) + parseInt(circleArray[i].posx.slice(0, -2))) + 'px';
    

    Here, mainHeight corresponds to the height of the #main div.


[1] 16 divs

View Demo on Fiddle

**Demo Snippet Below:**

Insert demo JavaScript code snippet here
Insert corresponding CSS code snippet here
Insert HTML code snippet here


[2] 15 divs Positioned Evenly

View Demo on Fiddle

**Demo Snippet Below:**

Insert demo JavaScript code snippet here
Insert corresponding CSS code snippet here
Insert HTML code snippet here


[3] Dynamically Position any number of divs on an Ellipse/Circle

The equation for locating a point on an ellipse can be expressed as follows:

(x, y) = (rx * cos(θ), ry * sin(θ))

In this scenario, the function generate(n, rx, ry, id) accepts four parameters: n (number of divs), rx and ry (radii along X and Y axis respectively), and id (ID of the main div).

View Demo on Fiddle

**Demo Snippet Below:**

Insert demo JavaScript code snippet here
Insert corresponding CSS code snippet here
Insert HTML code snippet here


Edit[9th December 2015]:

You can access a more adaptable version with start offset, clockwise, and anti-clockwise functionalities by visiting this link.

**Javascript Snippet Below:**

Insert Javascript code snippet here
Insert corresponding CSS code snippet here

Answer №2

An Alternate Approach Without the Use of JavaScript

The answer provided by chipChocolate.py is quite comprehensive, but there exists an alternative method to achieve your goal. This approach is simpler and does not necessitate the use of JavaScript.

Instead of relying on [x,y] coordinates, the key is to think in terms of a "circle" and rotation:

To implement this, you need to nest all the elements and apply a rotation to them. Since they are nested, each n + 1 element will rotate based on its direct parent's rotation. You can see a demonstration of this concept in action here:

.circle, .circle div {
    width:24px; height:300px;
    position:absolute;
    left:50%; top:50px;
}
.circle:before, .circle div:before {
    content:'';
    display:block;
    width:20px; height:20px;
    border: 2px solid black;
    border-radius: 100%;
}
.circle div {
    top:0; left:0;
    -webkit-transform : rotate(24deg);
    -ms-transform : rotate(24deg);
    transform : rotate(24deg);
}
<div class="circle">
    <div><div><div><div><div><div><div><div><div><div><div><div><div><div>
    </div></div></div></div></div></div></div></div></div></div></div></div></div></div></div>
</div>

The size of the circle is determined by the height of the elements (in the demo height:300px). You can make this value a percentage for responsive design (as shown below).

The rotation angle should be adjusted based on the number of elements you want around the circle. In the demo with 15 elements, the rotation is calculated as 360 / 15 = 24deg.

If you have a variable number of elements, you can utilize JavaScript to add them dynamically and calculate the required rotation angle.


A Responsive Example

See Demo

.circle{
    position:relative;
    width:5%;padding-bottom:50%;
    margin-left:47.5%;
}
.circle div {
    position:absolute;
    top:0; left:0;
    width:100%; height:100%;
    -webkit-transform : rotate(24deg);
    -ms-transform : rotate(24deg);
    transform : rotate(24deg);
}
.circle:before, .circle div:before {
    content:'';
    position:absolute;
    top:0; left:0;
    width:100%; padding-bottom:100%;
    border-radius: 100%;
    border: 2px solid teal;
    background:gold;
}
<div class="circle">
    <div><div><div><div><div><div><div><div><div><div><div><div><div><div><div>
    </div></div></div></div></div></div></div></div></div></div></div></div></div></div></div>
</div>

Answer №3

Presenting a unique approach inspired by various solutions I have come across

http://jsfiddle.net/0hr1n7a2/6/

(function() {
  var radians, radius;

  radius = 150;
    
  var totalItems = 48
  var item = 0;
  function arrangeItems() 
  {
    var x, y, angle = 0, step = (2*Math.PI) / totalItems;
    var width = $('#container').width()/2;
    var height = $('#container').height()/2;
    var itemW = 20, itemH = 2;
    var deg = 0;    
 while(item <= totalItems)
 {        
  x = Math.round(width + radius * Math.cos(angle) - itemW/2);
  y = Math.round(height + radius * Math.sin(angle) - itemH/2);        
     
  $('#container').append('<div id="'+ item +'"/>')
  $('div#'+item).css('position', 'absolute')
        .css('width', itemW+'px').css('height', itemH+'px')      
        .css('left', x+'px').css('top', y+'px')
        .css('background-color', 'blue')
        .css('transform-origin', x+'px' -y+'px')        
        .css('transform', 'rotate('+ deg +'deg)')
        .css('border', 'solid 1px #000');
     
        angle += step;
        ++item;
        deg += 360/totalItems;
    }
  }

  $('#theButton').on('click', function()
  {
        arrangeItems();
  })
    
})();
#container { width: 600px; height: 600px; border: 1px solid #000; position: relative; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input type="button" id="theButton" value="Draw">    
<div id="container">
</div>

Answer №4

  1. To shift the divs to (0, 0), set the position as "absolute". Using "relative" will maintain the default layout position.

  2. Adjust the center point of the circle to a new value like (300, 300).

    circleArray[i].posx = 300 + Math.round((150*Math.cos(i*(2*Math.PI/15)))) + 'px';
    circleArray[i].posy = 300 + Math.round((150*Math.sin(i*(2*Math.PI/15)))) + 'px';
    circleArray[i].style.position = "absolute";
    

Answer №5

After a decade, here's a fresh perspective inspired by the previous replies.

FIDDLE


    <div class="circle" id="main"></div>
    // cache
    let c = document.querySelector("#main");

    // constants
    const pi = Math.PI;
    let R = 180; 
    let count = 19;
    let DELTA = 360 / count;
    let TRANSPOSE_OFFSET = R + 50;

    // helper functions
    const pxfy = (n) => `${n}px`;
    const radians = (theta) => (theta * pi) / 180;
    const make = (e) => document.createElement(e);
    const r_cosA = (r, a) => Math.cos(a) * r;
    const r_sinA = (r, a) => Math.sin(a) * r;
    const transpose = (p, delta) => {
      let x = p.x + delta.x;
      let y = p.y + delta.y;
      return { x, y };
    };

    const get_point = (r, a) => {
      let x = r_cosA(r, a);
      let y = r_sinA(r, a);
      let Dx = TRANSPOSE_OFFSET + 10;
      let Dy = TRANSPOSE_OFFSET;
      return transpose({ x, y }, { x: Dx, y: Dy });
    };

    const setup = () => {
      let text = "This is JavaScript";
      text = text.padStart(count, '*');
      let text_split = text.split('');
      
      let range = [...Array(count).keys()];
  
      range.forEach((i) => {
        let circle = make("div");
        circle.id = `c_${i}`;
        circle.textContent = `${i + 1}`;
    
        let elem = make('div');
        elem.classList.add('anno');
        elem.textContent = text_split[i];
        circle.append(elem);

        let ANGLE = i * DELTA + 60;
        let RADIUS = R;
        let p = get_point(RADIUS, radians(ANGLE));
        circle.style.left = pxfy(p.x);
        circle.style.top = pxfy(p.y);
        
        circle.style.backgroundColor = `hsl(${ANGLE * 2.3}deg, 90%, 70%)`;
        circle.style.color = `hsl(${ANGLE * 1.5 + 270}deg, 80%, 20%)`;
    
        circle.style.transform = `rotate(${ANGLE + 90}deg)`;

        c.appendChild(circle);
      });
    };

    setup();

    .circle#main {
      --rad: 36px;
      position: absolute;
      width: 500px;
      height: 500px;
      top: 240px;
      left: 50%;
      transform: translate(-50%, -28%);
    }
    .circle#main div {
      position: absolute;
      border: 1px solid black;
      width: var(--rad);
      height: var(--rad);
      border-radius: var(--rad);

      display: flex;
      justify-content: center;
      align-items: center;
      font-family: sans-serif;
      font-weight: bold;
      font-size: 11px;
      pointer-events: none;
      opacity: 1;
      transition: opacity 3s ease-in-out;
    }

    #main div > .anno {
      position: absolute;
      top: -2.8rem;
      font-size: 2rem;
      font-family: serif;
      text-align: center;
      text-transform: uppercase;
      font-weight: bold;
      text-rendering: geometricPrecision;
      border-color: white;
      border-bottom-color: currentColor; 
    }

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

updating Chart.js to dynamically draw a line chart with updated dataset

Is there a way to pass back the retrieved value into the dataset data without it returning empty? It seems that the var durationChartData is empty because the array is initialized that way. How can I update it to display the correct values after receiving ...

Avoid allowing users to accidentally double click on JavaScript buttons

I am working with two buttons in a Javascript carousel and need to prevent users from double-clicking on the buttons. Below is the code I am using: var onRightArrow = function(e) { if (unitCtr<=unitTotal) { unitCtr++; TweenLite.to(p ...

What is the best way to loop through an array of objects using oboe?

Recently, I received a JSON response structured like this: [{ "id": 425055, "title": "Foo" }, { "id": 425038, "title": "Bar" }, { "id": 425015, "title": "Narf" }] To handle this data, I decided to utilize oboe.js to generate a highland stream ...

Tips for organizing appended <li> elements using jQuery

Being a novice in the realm of programming, I ask for your understanding as I pose what may seem like a simple question. Up until now, I have been unable to find a satisfactory solution to my issue. Within my HTML Code lies an unordered list, serving sole ...

The multiplication sum is not being calculated correctly after the decimal point in JavaScript

Just dipping my toes into the world of JavaScript, so please be gentle! I'm currently working on a function that multiplies one input field by another that the user provides. However, there seems to be an issue when the user wants to multiply by 8.5 ...

Encountering a problem while moving the endpoint's location in Express.js

My journey to learn about REST API and session-based Authentication in express.js took an unexpected turn when I encountered a fascinating error while trying to relocate the endpoints. Upon moving the endpoints, I decided to send a request to the /me endp ...

Instructions for making all the cells in a Bootstrap 3 row the same height

I'm currently working on a grid of cards that can be flipped and have varying content. I'm attempting to make the height of cards with less content equal to the card with the most content. However, regardless of where I use height:100%, the small ...

Removing Multiple Object Properties in React: A Step-by-Step Guide

Is there a way in React to remove multiple object properties with just one line of code? I am familiar with the delete command: delete obj.property, but I have multiple object properties that need to be deleted and it would be more convenient to do this i ...

Retrieve the image URL from the uploads directory using CakePHP

Is it possible to use the images located in the webroot/uploads folder within an img tag? Also, what href link should be used? <img src="/webroot/uploads/1.jpg"> is not functioning correctly.=( ...

Code execution halted before AJAX/XMLHttpRequest Call was made

Below is the Javascript code I am currently using: document.getElementById("button").disabled = true; document.getElementById("button").innerHTML = "Please Wait..."; var xhttp = new XMLHttpRequest(); xhttp.open("GET", "MoodInput"+ "?moodValue=" + input, ...

Display a loading image as a placeholder while the Block is loading on the Viewport

Despite my extensive search for a solution to my problem, I have been unable to find one that addresses it directly. I am trying to display a "loading" image for 4 seconds before the content of the div is loaded. Unfortunately, I haven't been able to ...

Reformatting function transforms the structure of an array containing objects

I have been struggling with certain issues and would like to find the most effective solution. Despite using map and reduce, I have not been able to achieve the desired results. Any help would be greatly appreciated. Consider the following INPUT Array str ...

Determining season goals for teams using nested JSON data

Is it possible to retrieve a team's total goals scored for the season from the provided data by using the team's name as the input for a function? Would it be accurate to attempt mapping over the rounds and filtering matches where either team1 o ...

Tips for submitting multiple pages with identical information

The initial page visited was registration.php Then the user went to pay.php <form action="success.php" method="post"> <input type="hidden" value=<?php echo json_encode($_POST); ?> custom="Hidden Element&qu ...

Showing an individual image when a particular list item is clicked using jquery

I have created an image slider that automatically transitions between images. However, I would like to have a specific image displayed when a particular list item is clicked in the round buttons below. For example, if the slider is showing the 5th image a ...

Uploading multiple files via AJAX without any files being uploaded

I have been attempting to upload multiple files with just one AJAX request. However, I am encountering some issues, including: UPDATE No files are being successfully uploaded to the server It appears that multiple AJAX requests are being triggered due t ...

Steps for assigning distinct identifiers to information entered through an input form

I have a question that I just can't seem to figure out: So, I have this input form: <form @submit.prevent="customSubmit"> <label>Name</label> <input type="text" v-model="newUser.name" id=&quo ...

Transfer Data from Ajax Response to an Array

I am struggling to properly assign a value from an ajax call to an array and then pass that array. It seems like the variable 'options' is not being recognized as valid. This is the code I have: $.ajax({ url: url, success: function(data){ ...

What is the proper application of counter-reset when utilizing CSS counters to automatically number headings?

In my quest to automatically number headings with multiple levels of hierarchy in an HTML document using CSS, I have encountered various examples online that heavily rely on counter-reset. However, despite testing it in different configurations, I haven&ap ...

Different methods to insert data into a database without relying on mongoose

Looking for help implementing the populate() function without using mongoose within the code snippet below: ` course.students.forEach(async (student, i) => { const s = await Student.findById(student._id); console.log(s.toObject()); // ...