Monday, October 22, 2012

Compass / SASS for Placing Elements on a Circle

Need to put some elements on a circle?  Already using Compass / SASS to compile your CSS?

Woot!  Compass has you covered with trigonometric sine and cosine functions and of course the SASSy @for iterator helps, too.

This jsfiddle demonstrates the compiled result.

Your HTML document is composed of a container for the circle, an element for the circle, a container for items placed on the circle, and the items which will appear on the circle.

Your SASS file is composed of the following.  Modify the $positions, $ringSize, and $itemSize variables according to your needs:

// Use either Compass' border-radius mix-in or prefixfree plugin
@import "compass/css3/border-radius";

 * on-circle takes a radius, position on a circle, number of possible positions and returns top and left properties
 * $radius {Number}    radius of the circle in pixels
 * $ordinalPosition {Number} the position of the item on the circle, counting from 1 (North) through N
 * $positions {Number}   the number of positions on the circle
 * $originX {Number}   optional X origin point for the circle, defaults to the radius
 * $originY {Number}   optional Y origin point for the circle, defaults to the radius
 * $offsetX {Number}   optional X offset for the item, you might use 1/2 of the width of an item, default is 0
 * $offsetY {Number}   optional Y offset for the item, you might use 1/2 of the height of an item, default is 0
@mixin on-circle ($radius, $ordinalPosition, $positions, $originX: $radius, $originY: $radius, $offsetX: 0, $offsetY: 0) {

 Determine the angle for the position:
  Multiply the adjusted zero-based index of the position by the degrees-per-position (360 degrees divided by the 
  number of positions) and subtract 90 degrees (adjusting to begin at North)
 $positionAngleDegrees: ($ordinalPosition - 1)*360/$positions - 90;
 Convert the angle to radians:
  Multiply the angle by pi and then divide by 180 degrees.

 NOTE: This step is necessary because of a bug in handling of the degrees unit when doing iterations, AFAICT
 $positionAngleRadians: $positionAngleDegrees * pi() / 180;

 Apply the parametric equation of the circle,

  x = [origin x] + (r * cos angle)
  y = [origin y] + (r * sin angle)
 top: #{$originY + $offsetY + $radius * sin($positionAngleRadians)}px;
 left: #{$originX + $offsetX + $radius * cos($positionAngleRadians)}px;


// How many positions will there be on the circle?
$positions: 13;

// How big is the circle (diameter)?
$ringSize: 180;

// How big is an item?  Offsets and margins are based on this value
// such that the center of an item is located on the circle, rather than
// the top left point
$itemSize: 28;

body {
 margin: 50px;

.ring-container {
 border: solid 1px black;
 display: inline-block;

.ring {
 position: relative;
 // Use border-box so that margin and border are not included and the size of the element containing the
 // rendered circle is the circle size + border + ...
 box-sizing: border-box;
 width: #{$ringSize}px;
 height: #{$ringSize}px;
 margin: #{$itemSize/2}px;
 border: solid 1px black;

 // Make any square into a circle by setting the border radius to it's full width/height
 @include border-radius(#{$ringSize}px);

.ring-positions {
 position: relative;
 width: #{$ringSize}px;
 height: #{$ringSize}px;
 top: #{$itemSize/-2}px;
 left: #{$itemSize/-2}px;

.ring-position {
 position: absolute;
 width: #{$itemSize}px;
 height: #{$itemSize}px;
 @include border-radius(#{$itemSize}px);

 // Use box-sizing: border-box if you'll have a border
 box-sizing: border-box;
 border-color: black;
 border-style: solid;

@for $i from 1 through $positions {
 .ring-position-#{$i} {
  @include on-circle($radius: $ringSize / 2, $ordinalPosition: $i, $positions: $positions);

  // Just for demonstration of placement
  border-width: #{$i/2+3}px;

No comments:

Post a Comment