How to place elements along the circumference of an element using SASS and trigonometry.

Calculating the location of items with the help of Sine and Cosine given an angle.

by: Ricardo Henriquez

Placing elements relative to another one is a simple task we've all been doing in CSS for a long time. The use of position absolute and a set of coordinates like top: X and left: y allows us to place our elements anywhere in relationship to a parent element.

But what if we could place elements along the circumference of a circle without having to manually calculate their X and Y coordinates? That was the task I was given, I need to create a set of elements all placed within a circle shape and all equidistant from each other, regardless of the number of elements or the size of them.

Using trigonometry to calculate the coordinates of the elements

Suddenly I was transported back to high school where I remembered learning about Sine, Cosine, and Tangent functions that I thought I would never need to use (I wish I had paid more attention) but are the solution to this problem.

Given the size of the parent element (or circle in this case), we can obtain the radius of the circle. To get the radius we just divide the diameter of the parent element by 2.

radius = diameter / 2

The next thing we need to calculate is the angle in which each child element will be. We can do this by simply dividing the 360deg by the number of child elements.

angle = 360deg / $n

With these 2 variables, we can then calculate the Sine (or sin) and the Cosine (or cos) to get our x and y coordinates. Now, most calculators have a sin and cos buttons that make it simple for us to calculate those values, so I'm not going to bore you with the math behind it, but if you want to learn a little more you can check out this video created by Khan Academy.

For our SASS solution, I will borrow the Plain SASS Trigonometry Algorithm in Taylor Expansion functions I found. These functions will help our calculations and provide us with the sin and cos for our coordinates.

scss
@function pow($base, $exp) {
  $value: $base;
  @if $exp > 1 {
    @for $i from 2 through $exp {
      $value: $value * $base;
    }
  }
  @if $exp < 1{
    @for $i from 0 through -$exp {
      $value: $value / $base;
    }
  }
  @return $value;
}

@function fact($num) {
  $fact: 1;
  @if $num > 0{
    @for $i from 1 through $num {
      $fact: $fact * $i;
    }
  }
  @return $fact;
}

@function _to_unitless_rad($angle) {
  $pi: 3.14159265359;
  @if unit($angle) == "deg" {
    $angle: $angle / 180deg * $pi;
  }
  @if unit($angle) == "rad" {
    $angle: $angle / 1rad;
  }
  @return $angle;
}

@function sin($angle){
  $a: _to_unitless_rad($angle);
  $sin: $a;
  @for $n from 1 through 10 {
    $sin: $sin + (pow(-1, $n) / fact(2 * $n + 1) ) * pow($a, (2 * $n + 1));
  }
  @return $sin;
}

@function cos($angle){
  $a: _to_unitless_rad($angle);
  $cos: 1;
  @for $n from 1 through 10 {
    $cos: $cos + ( pow(-1,$n) / fact(2*$n) ) * pow($a,2*$n);
  }
  @return $cos;
}

So now that we can get the sin and cos, we can use those values to start plotting our coordinates. So far the first child element looks something like this:

scss
// Width and height of the parent element
$diameter: 300px;
$radius: $diameter / 2;

// The number of child elements
$elements: 6;

// The angle of the first child element
$angle: (360deg / $elements)

// Calculating the sin
@debug $radius*sin($angle); // 129.90381

// Calculating the cos
@debug $radius*cos($angle); // 75

One important thing to know is that we are taking the origin of our angle in the top, left coordinates of our parent element, so if we were to use the sin and cos to plot the top and left properties of or child elements, they would all be misplaced outside the circle.

We can solve this by simply adding our radius to our left property and subtracting our radius from our top property. This will place the origin in the center of the parent element.

The next thing we do is to also subtract half the width (or radius) of our child elements to make sure we place the children in the line of our parent element and apply Our properties look something like this:

scss
// Width and height of the parent element
$diameter: 300px;
$radius: $diameter / 2;

// The number of child elements
$elements: 6;

// The size of the child element
$element-diameter: 72px;
$element-radius: $element-diameter / 2;

// The angle of the first child element
$angle: (360deg / $elements)

// Getting the left and top coordinates
left:  round($radius + ($radius*sin($angle)) - $element-radius);
top:  round($radius - ($radius*cos($angle)) - $element-radius);

And that should do it. The last thing is to make a SASS mixin that automatically assigns top and left properties to multiple child elements.

scss
@mixin position() {
  @for $n from 1 through $elements {
    span:nth-of-type(#{$n}) {
      $angle: (360deg / $elements) * $n;
      left:  round($radius + ($radius*sin($angle)) - $element-radius);
      top:  round($radius - ($radius*cos($angle)) - $element-radius);
    }
  }
}