Creating a Circular Pinwheel in CSS

pinwheel step 3

In this post, I’ll show you how to create a circular pinwheel that’s easily customizable using Jade (HTML preprocessor) and Sass (CSS preprocessor). By using them for the HTML and CSS, we’ll easily be able to change how many colors the pinwheel uses and the number of times to repeat the pattern of colors with just a couple variables.

If you’re not familiar with Jade or Sass, SitePoint has a great tutorial covering some Jade basics, and there is a Sass one on the language’s own site. To see the code results yourself, you can start a new Pen in CodePen and change your HTML preprocessor to Jade and your CSS preprocessor to SCSS. We also have HTML2jade and SassMeister if you’re interested.

To see a few more examples of the kind of pinwheel we’ll create in this tutorial, have a look at the last two rainbow cursors from Mac OS X in my CodePen demo “Busy Cursors” below.

If you’re wondering how I got the sectors to overlap each other and not have a “top” one, that’s a trick I’ll be covering here.

Jade

First, let’s begin the Jade part the pinwheel by setting variables for the colors, number of times to repeat the pattern of colors, and the number of sectors. Since we’ll use three colors and repeat the pattern 3 times, we’ll need 9 sectors. Below those variables, we’ll create a div with a class name of container.

- var colors = 3;
- var repeat = 3;
- var sectors = colors * repeat;

.container

Then under the container, we write the @for loop shown that creates divs with a class name of sector, which will be for the sectors of the pinwheel.

.container
  - for (var i = 1; i <= sectors; ++i) {
    .sector
  - }

Sass

For the Sass, let’s set variables for the color array ($color), times to repeat the color pattern ($repeat), length of one side of the container ($size), index of the last color ($lastColorIndex), sectors needed ($sectors), sector rotation direction ($ccw), and increment angle ($angle). When $ccw is set to true, the direction is counterclockwise and false if clockwise.

$color: #f44 #8af #8f8;
$repeat: 3;
$size: 150px;
$lastColorIndex: length($color);
$sectors: $lastColorIndex * $repeat;
$ccw: true;
$dirVal: if($ccw == true, -1, 1);
$angle: (360deg / $sectors) * $dirVal;

To determine whether to rotate the sectors clockwise or counterclockwise, $dirVal will use -1 if $ccw is true and 1 if false. Then, $angle will either be positive or negative depending on what $dirVal is. In this case, we’re going to rotate them counterclockwise.

Next, we’ll set styles for the body, .container, and .sector divs.

body {
  margin: 0;
}
.container {
  margin: 10px auto;
  position: relative;
  height: $size;
  width: $size;
}
.sector {
  border-radius: 50%;
  position: absolute;
  top: 50%;
  left: 50%;
  width: $size;
  height: $size;
  margin-left: -$size / 2;
  overflow: hidden;
  transform-origin: 50% 0%;
  @for $i from 1 through $sectors {
    &:nth-of-type(#{$i}) {
      transform: rotate($angle * ($i - 1));
      z-index: $i;
    }
  }
}

Adding Sector Colors

The previous code may not have resulted in anything at the moment, but adding this second @for loop will give the background colors to the sectors:

.sector {
  . . .
  @for $i from 1 through $lastColorIndex {
    &:nth-of-type(#{$lastColorIndex}n + #{$i}) {
      background: nth($color, $i);
    }
  }
}

We do this using an :nth-of-type() expression for (index of last color)n + interval, which is in the code as #{$lastColorIndex}n + #{$i}. Since we’re using three colors, this results in 3n + 1, 3n + 2, and 3n + 3, selecting every third sector starting with the first, second and third sector respectively. With this in mind, now we see something like this:

pinwheel step 1

If you see a part of the top cut off, you didn’t do anything wrong. I temporarily used more top margin just to show what the entire pinwheel looks like so far.

With the output we currently have, notice how the top (last) green sector is obscuring areas of the bottom red, blue, and first green sectors and slightly a part of the second red one. If we raise the z-index level of the bottom red sector higher than that of the top green one, then we would have to do the same to blue to be on top of the red and so and so forth. Then it becomes impossible to determine which element has the highest or lowest z-index value. Consider the arrangement of these three pieces of square paper:

Pinwheel made with square pieces of paper

Assuming that the yellow piece of paper was at the top, it took a bit of effort to tuck the top left corner of the white one on top of the yellow one. Because of how absolute positioning in CSS works, we cannot do this just by relying on z-index.

So, to get the partially-covered sectors of the pinwheel to appear on top, we’ll create “fake” versions inside of the green sector covering them. Then, these will seamlessly redraw the covered portions of their real counterparts.

Getting Number of Sectors to be Redrawn

To start redrawing the parts that should be visible, we’ll first use a simple formula to use in the Jade and Sass which determines how many sectors we need to redraw (“fake” sectors in other words). The formula we’ll use here is sectors / 2 – 1 rounded to the next whole number. So for sectors 1 through 9, we get the following:

Sectors Fake Sectors Needed
1 0
2 0
3 1
4 1
5 2
6 2
7 3
8 3
9 4

We see there is a pattern in which another fake sector is needed for every odd number of real ones starting at 3.

Fake Sector Markup for Jade

Now let’s go back to the Jade and add a fakeSectors variable for the formula after angle and add the if statement with a for loop under .sector.

- var fakeSectors = Math.ceil(sectors / 2 - 1);

.container
  - for (var i = 1; i <= sectors; ++i) {
    .sector
      - if (i == sectors) {
        - for (var j = 1;j <= fakeSectors;++j) {
          .fake-sector
        - }
      - }
  - }

Sass for Fake Sectors

In our Sass code, we’ll create another variable for the fake sectors practically the same way we did for the Jade.

$fakeSectors: ceil($sectors / 2 - 1);

To begin styling the fake sectors, we’ll give both .sector and .fake-sector the same border radius, position, and transform origin. Then, we can remove them from .sector itself.

.sector, .fake-sector {
  border-radius: 50%;
  position: absolute;
  transform-origin: 50% 0%;
}
.sector {
  top: 50%;
  left: 50%;
  width: $size;
  height: $size;
  margin-left: -$size / 2;
  overflow: hidden;
  . . .
}

Drawing and Rotating the Fake Sectors

The fake sectors will have the top and right positions and width and height as shown, and then they’ll be rotated in the same manner as before except the increments of -40° will start at -40° instead of 0.

.fake-sector {
  top: 0%;
  right: 0%;
  width: 100%;
  height: 100%;
  @for $i from 1 through $fakeSectors {
    &:nth-of-type(#{$i}) {
      transform: rotate($angle * $i);
      z-index: $i;
    }
  }
}

Adding Colors to Fake Sectors

Since the @for loop setting the background colors will work the same way for the fake sectors, we can move it to where we select both .sector and .fake-sector.

.sector, .fake-sector {
  border-radius: 50%;
  position: absolute;
  transform-origin: 50% 0%;
  @for $i from 1 through $lastColorIndex {
    &:nth-of-type(#{$lastColorIndex}n + #{$i}) {
      background: nth($color, $i);
    }
  }
}

After that, we should now get the output we want:

pinwheel step 2

Finally, let’s make the pinwheel round by adding overflow: hidden; to .container and making it have the same border radius as .sector and .fake-sector. .sector and .fake-sector should only share the same position and transform origin.

.container {
  margin: 10px auto;
  overflow: hidden;
  position: relative;
  height: $size;
  width: $size;
}
.container, .sector, .fake-sector {
  border-radius: 50%;
}
.sector, .fake-sector {
  position: absolute;
  transform-origin: 50% 0%;
  . . .
}

And now we have a complete pinwheel!

To change the pattern to whatever you like, play around with colors and repeat in the Jade and the $color array and $repeat in the Sass (SCSS). Just be sure they match each other to avoid unexpected results.

Conclusion

Although this method is good for one pinwheel pattern, making a mixin out of this to use for a single element would either be extremely complicated or impossible. I thought about using multiple radial gradients or box shadows, but there is no way possible to create a background image inside of another background image or box shadow inside of another box shadow to redraw those covered. For the most part, this is an easy working approach to this sort of challenge.

I hope you found this post inspiring or useful for adding amazing patterns like these in your own projects!


Posted in: CSS