Positioning divs in a circle using JavaScript

First of all, the equation for a co-ordinate on a circle is simply:

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

where, r is the radius of a circle and θ is the angle in radians.


The reason why your code is creating an eccentric ellipse is because when you assign the .top and .left CSS values, you are not considering that it will actually take the top-left corner as its reference. I’ve fixed your code and now it creates a perfect circle.

Changes made to your code:

  1. Added an array theta that holds 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 shows all the angles I’ve used.

    enter image description here

  2. Added an array colors that holds different colors.

    var colors = ['red', 'green', 'purple', 'black', 'orange', 'yellow', 'maroon', 'grey', 'lightblue', 'tomato', 'pink', 'maroon', 'cyan', 'magenta', 'blue', 'chocolate', 'DarkSlateBlue'];
    
  3. Made changes to your trigonometric equations.

    circleArray[i].posx = Math.round(radius * (Math.cos(theta[i]))) + 'px';
    circleArray[i].posy = Math.round(radius * (Math.sin(theta[i]))) + 'px';
    
  4. Changed the way .top and .left are assigned.

    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';
    

    where mainHeight is the height of the #main div.


[1] 16 divs

Demo on Fiddle

var setup = function() {
  var radius = 150;
  var main = document.getElementById('main');
  var mainHeight = parseInt(window.getComputedStyle(main).height.slice(0, -2));
  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)];
  var circleArray = [];
  var colors = ['red', 'green', 'purple', 'black', 'orange', 'yellow', 'maroon', 'grey', 'lightblue', 'tomato', 'pink', 'maroon', 'cyan', 'magenta', 'blue', 'chocolate', 'DarkSlateBlue'];
  for (var i = 0; i < 16; i++) {
    var circle = document.createElement('div');
    circle.className="circle number" + i;
    circleArray.push(circle);
    circleArray[i].posx = Math.round(radius * (Math.cos(theta[i]))) + 'px';
    circleArray[i].posy = Math.round(radius * (Math.sin(theta[i]))) + 'px';
    circleArray[i].style.position = "absolute";
    circleArray[i].style.backgroundColor = colors[i];
    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';
    main.appendChild(circleArray[i]);
  }
};
setup();
div#main {
  height: 300px;
  width: 300px;
  position: absolute;
  margin: 0 auto;
  transform: translate(-50%, -50%);
  top: 50%;
  left: 50%;
}
div.circle {
  position: absolute;
  width: 20px;
  height: 20px;
  border: 2px solid black;
  border-radius: 50%;
}
body {
  margin: 0 auto;
  background: papayawhip;
}
<div id="main"></div>

[2] 15 divs Positioned Evenly

Demo on Fiddle

var setup = function() {
  var radius = 150;
  var main = document.getElementById('main');
  var mainHeight = parseInt(window.getComputedStyle(main).height.slice(0, -2));
  var theta = [0, (2 * (Math.PI / 15)), (4 * (Math.PI / 15)), (2 * (Math.PI / 5)), (8 * (Math.PI / 15)), (2 * (Math.PI / 3)), (4 * (Math.PI / 5)), (14 * (Math.PI / 15)), (16 * (Math.PI / 15)), (6 * (Math.PI / 5)), (4 * (Math.PI / 3)), (22 * (Math.PI / 15)), (8 * (Math.PI / 5)), (26 * (Math.PI / 15)), (28 * (Math.PI / 15))];
  var circleArray = [];
  var colors = ['red', 'green', 'purple', 'black', 'orange', 'yellow', 'maroon', 'grey', 'lightblue', 'tomato', 'pink', 'maroon', 'cyan', 'magenta', 'blue', 'chocolate', 'DarkSlateBlue'];
  for (var i = 0; i < 15; i++) {
    var circle = document.createElement('div');
    circle.className="circle number" + i;
    circleArray.push(circle);
    circleArray[i].posx = Math.round(radius * (Math.cos(theta[i]))) + 'px';
    circleArray[i].posy = Math.round(radius * (Math.sin(theta[i]))) + 'px';
    circleArray[i].style.position = "absolute";
    circleArray[i].style.backgroundColor = colors[i];
    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';
    main.appendChild(circleArray[i]);
  }
};
setup();
div#main {
  height: 300px;
  width: 300px;
  position: absolute;
  margin: 0 auto;
  transform: translate(-50%, -50%);
  top: 50%;
  left: 50%;
}
div.circle {
  position: absolute;
  width: 20px;
  height: 20px;
  border: 2px solid black;
  border-radius: 50%;
}
body {
  margin: 0 auto;
  background: papayawhip;
}
<div id="main"></div>

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

The equation for a co-ordinate on an ellipse is:

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

where, rx is the radius along X-axis and ry is the radius along Y-axis.


In this case, the function generate(n, rx, ry, id) takes four arguments, where n is the number of divs, rx and ry are the radii along the X and Y-axis respectively and finally id is the id of the div that you want to append your elliptically arranged divs in.

Demo on Fiddle

var theta = [];

var setup = function(n, rx, ry, id) {
  var main = document.getElementById(id);
  var mainHeight = parseInt(window.getComputedStyle(main).height.slice(0, -2));
  var circleArray = [];
  var colors = ['red', 'green', 'purple', 'black', 'orange', 'yellow', 'maroon', 'grey', 'lightblue', 'tomato', 'pink', 'maroon', 'cyan', 'magenta', 'blue', 'chocolate', 'darkslateblue', 'coral', 'blueviolet', 'burlywood', 'cornflowerblue', 'crimson', 'darkgoldenrod', 'olive', 'sienna', 'red', 'green', 'purple', 'black', 'orange', 'yellow', 'maroon', 'grey', 'lightblue', 'tomato', 'pink', 'maroon', 'cyan', 'magenta', 'blue', 'chocolate', 'darkslateblue', 'coral', 'blueviolet', 'burlywood', 'cornflowerblue', 'crimson', 'darkgoldenrod', 'olive', 'sienna'];
  for (var i = 0; i < n; i++) {
    var circle = document.createElement('div');
    circle.className="circle number" + i;
    circleArray.push(circle);
    circleArray[i].posx = Math.round(rx * (Math.cos(theta[i]))) + 'px';
    circleArray[i].posy = Math.round(ry * (Math.sin(theta[i]))) + 'px';
    circleArray[i].style.position = "absolute";
    circleArray[i].style.backgroundColor = colors[i];
    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';
    main.appendChild(circleArray[i]);
  }
};

var generate = function(n, rx, ry, id) {
  var frags = 360 / n;
  for (var i = 0; i <= n; i++) {
    theta.push((frags / 180) * i * Math.PI);
  }
  setup(n, rx, ry, id)
}
generate(16, 150, 75, 'main');
div#main {
  height: 300px;
  width: 300px;
  position: absolute;
  margin: 0 auto;
  transform: translate(-50%, -50%);
  top: 50%;
  left: 50%;
}
div.circle {
  position: absolute;
  width: 20px;
  height: 20px;
  border: 2px solid black;
  border-radius: 50%;
}
body {
  margin: 0 auto;
  background: papayawhip;
}
<div id="main"></div>

Edit[9th December 2015]:

Here’s a more flexible version with start offset, clock wise and anti-clock wise functionality.

/*
Usage: Position.ellipse(n, rx, ry, so, wh, idd, cls, cw);

where n = number of divs,
      rx = radius along X-axis,
      ry = radius along Y-axis,
      so = startOffset,
      wh = width/height of divs,
      idd = id of main div(ellipse),
      cls = className of divs;
      cw = clockwise(true/false)
*/

var Position = {
  ellipse: function(n, rx, ry, so, wh, idd, cls, cw) {
    var m = document.createElement('div'),
      ss = document.styleSheets;
    ss[0].insertRule('#' + idd + ' { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); border-radius: 50%; box-shadow: inset 0 0 ' + wh + 'px ' + wh / 4 + 'px black; background: rgba(0, 0, 0, 0.2); width: ' + String((rx * 2) + wh) + 'px; height: ' + String((ry * 2) + wh) + 'px; }', 1);
    ss[0].insertRule('.' + cls + '{ position: absolute; background: black; color: papayawhip; text-align: center; font-family: "Open Sans Condensed", sans-serif; border-radius: 50%; transition: transform 0.2s ease; width: ' + wh + 'px; height: ' + wh + 'px; line-height: ' + wh + 'px;}', 1);
    ss[0].insertRule('.' + cls + ':hover { transform: scale(1.2); cursor: pointer; background: rgba(0, 0, 0, 0.8); }', 1);
    m.id = idd;
    for (var i = 0; i < n; i++) {
      var c = document.createElement('div');
      c.className = cls;
      c.innerHTML = i + 1;
      c.style.top = String(ry + -ry * Math.cos((360 / n / 180) * (i + so) * Math.PI)) + 'px';
      c.style.left = String(rx + rx * (cw ? Math.sin((360 / n / 180) * (i + so) * Math.PI) : -Math.sin((360 / n / 180) * (i + so) * Math.PI))) + 'px';
      m.appendChild(c);
    }
    document.body.appendChild(m);
  }
}

Position.ellipse(20, 150, 150, 0, 42, 'main', 'circle', true);
@import url(http://fonts.googleapis.com/css?family=Open+Sans+Condensed:300);
 body {
  margin: 0 auto;
  background: rgb(198, 193, 173);
}

Leave a Comment