DEV Community

Cover image for The interactive gear-shaped object made with CSS and JavaScript.
Yuriy Markov
Yuriy Markov

Posted on • Updated on • Originally published at scipios.netlify.com

The interactive gear-shaped object made with CSS and JavaScript.

Buy Me A Coffee

A gear or cogwheel is a rotating machine part having cut teeth or, in the case of a cogwheel, inserted teeth (called cogs), which mesh with another toothed part to transmit torque.

In this article, I will show how to build an interactive gear-shaped object.

To catch the idea, let's consider the gear as a circularly placed set of teeth.

Each tooth has its characteristics, such as shape and height.

Having the above data in mind, let's build such an object.

HTML

The static part of the layout is simple. We will only define the container which we will set up and fill with objects.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Gear</title>
  </head>
  <body>
    <div id="container"></div>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

The dynamic part will contain tooth:

<div
  class="tooth"
  style="height: 5vmin; width: 14.5vmin; transform: rotateZ(315deg) translateX(15vmin);"
></div>
Enter fullscreen mode Exit fullscreen mode

And a cover of the central part:

<div class="cover"></div>
Enter fullscreen mode Exit fullscreen mode

Part of teeth parameters is calculated by JavaScript, while the rest of the settings are defined by CSS.

CSS

First of all, we will define basic settings, to have an ability to adjust our object by altering data in a single place.

:root {
  --smokey: #f5f5f5;
  --darky: #262625;
  --thickness: 0.1vmin;
  --half: 50%;
  --border: var(--thickness) solid var(--smokey);
  --border-radius: var(--half);
}
Enter fullscreen mode Exit fullscreen mode

Container

The container not only contains teeth but also acts as an outer fringe of the gear's main body.

#container {
  position: relative;
  display: flex;
  border: var(--border);
  justify-content: center;
  align-items: center;
  border-radius: var(--border-radius);
}
Enter fullscreen mode Exit fullscreen mode

To form a circular shape of the container, we will set the border-radius to 50%. Also, we will apply the border rule.

Cover

The cover helps us to create a single gear outline. To get the idea, let's take a look at the layout layer by layer.

The first layer is a container with the border.

Alt Text

The next layer contains a set of teeth. The inner half of each tooth is placed inside the container. Thus, creating a single outline.

Alt Text

The last layer contains the cover element, which hides the inner part of the teeth.

Alt Text

So, by placing objects in corresponding layers, and by setting the correct background-color, we are creating a single outline via hiding unnecessary parts.

Alt Text

Since the gear is rebuilt anew after any of the parameters alterations, it is worth to mention that the cover element requires to be set of the proper z-index value.

Let's wrap it up:

#container .cover {
  position: relative;
  display: flex;
  justify-content: center;
  align-items: center;
  width: 100%;
  height: 100%;
  background: var(--darky);
  border-radius: var(--border-radius);
  z-index: 1;
}
Enter fullscreen mode Exit fullscreen mode

It is assumed that gear must be mounted on a shaft.

So, next, we will add the landing hole.

To keep the layout simple let's use a pseudo-element before of the cover element:

#container .cover::before {
  width: var(--half);
  height: var(--half);
  border-radius: var(--border-radius);
  content: "";
  border: var(--border);
}
Enter fullscreen mode Exit fullscreen mode

To center our landing hole we use the flex properties of the cover element.

Tooth

Last but not least element of our shape is a tooth.

While most of the set up is happening in the JavaScript part, there still are some CSS rules.

First of all, the tooth element has an absolute position. Secondly, we leverage the box-sizing CSS rule to not to break the layout.

#container .tooth {
  position: absolute;
  box-sizing: border-box;
}
Enter fullscreen mode Exit fullscreen mode

Just for fun, I've added three types of shapes of teeth: square, circle, and triangle.

Each type of shape is built via the before pseudo-element.

Square

This is the default type, so it doesn't have a separate class name.

This is a bordered square with an absolute position:

#container .tooth::before {
  position: absolute;
  width: 100%;
  height: 100%;
  border: var(--border);
  content: "";
  background: var(--darky);
}
Enter fullscreen mode Exit fullscreen mode

Circle

In the case of the circle, we will apply a border-radius trick:

#container .tooth.circle::before {
  border-radius: var(--border-radius);
}
Enter fullscreen mode Exit fullscreen mode

Triangle

To turn the square into the triangle let's just rotate it 45 degrees:

#container .tooth.triangle::before {
  transform: rotateZ(45deg);
}
Enter fullscreen mode Exit fullscreen mode

JavaScript

The core variables are stored globally. They are defining all of the parameters of our object: DOM reference to the container, the radius of the gear, number of teeth, height and shape of a tooth, outline thickness, and the angle of the gear rotation.

The API consists of the set of functions. Some of them are very basic and aimed to handle user input: setTeeth, setHeight, setShape, and setAngle. Here is an example of such function:

/**
 * set modifier for tooth height
 * @param {number} value tooth height modifier
 */
function setHeight(value) {
  height = value;
  update();
}
Enter fullscreen mode Exit fullscreen mode

It is worth to mention the setThickness function because it alters the value of the CSS variable --thickness:

/**
 * set thickness
 * @param {number} value thickness value
 */
function setThickness(value) {
  document.documentElement.style.setProperty(
    "--thickness",
    `${value / 10}vmin`
  );
}
Enter fullscreen mode Exit fullscreen mode

The heavy-duty function that builds the gear has name update.

We will break it into steps to understand what happens.

Before actions, we will calculate the base settings.

Firstly, we need to know the dimensions of the container. Next, we will find out the values of the teeth' basic parameters.

// calculate the container dimensions
const size = `${radius * 3}vmin`;
// calculate the angle between teeth
const step = 360 / teeth;
// calculate the base dimension of the tooth
const side = (2 * Math.PI * radius) / (teeth * (Math.PI / 2));
// calculate the tooth displacement
const displacement = radius * 1.5;
// calculate the height multiplier
const multiplier = (height - 1) / 10;
Enter fullscreen mode Exit fullscreen mode

Next, let's set up the container:

// setup container
container.style.width = size;
container.style.height = size;
container.style.margin = `${radius * 2}vmin`;
container.style.transform = `rotate(${angle}deg)`;
container.innerHTML = null;
Enter fullscreen mode Exit fullscreen mode

Now we will draw teeth:

  • create the element.
  • apply proper class names.
  • set the width and height following the current shape.
  • rotate the tooth and place it on the rim.
  • add tooth to the container.
// create tooth
const tooth = document.createElement("div");
tooth.className = `tooth ${shape}`;
// set size for the triangle-shaped tooth
if (shape === "triangle") {
  const length = `${(side / 2) * multiplier}vmin`;
  tooth.style.height = length;
  tooth.style.width = length;
} else {
  // set size for the square and circle-shaped teeth
  tooth.style.height = `${side}vmin`;
  tooth.style.width = `${side * multiplier}vmin`;
}
// place the tooth
tooth.style.transform = `rotateZ(${i *
  step}deg) translateX(${displacement}vmin)`;
// append tooth to the container
container.appendChild(tooth);
Enter fullscreen mode Exit fullscreen mode

When we set up the width and height of a tooth, we rely on the side constant. The point here is to draw teeth in strict proportion to their count to avoid overlay. So, the more teeth you have, the smaller they are. Another point is that this calculation is also lead to the proportional reduction of the tooth height to keep it's looking more balanced.

Finally, add the cover element:

// restore cover
const cover = document.createElement("div");
cover.className = "cover";
container.appendChild(cover);
Enter fullscreen mode Exit fullscreen mode

Let's wrap it up:

/**
 * update the gear
 */
function update() {
  if (container) {
    // calculate the container dimensions
    const size = `${radius * 3}vmin`;
    // calculate the angle between teeth
    const step = 360 / teeth;
    // calculate the base dimension of the tooth
    const side = (2 * Math.PI * radius) / (teeth * (Math.PI / 2));
    // calculate the tooth displacement
    const displacement = radius * 1.5;
    // calculate the height multiplier
    const multiplier = (height - 1) / 10;
    // setup container
    container.style.width = size;
    container.style.height = size;
    container.style.margin = `${radius * 2}vmin`;
    container.style.transform = `rotate(${angle}deg)`;
    container.innerHTML = null;
    // draw teeth
    for (var i = 0; i < teeth; i++) {
      // create tooth
      const tooth = document.createElement("div");
      tooth.className = `tooth ${shape}`;
      // set size for the triangle-shaped tooth
      if (shape === "triangle") {
        const length = `${(side / 2) * multiplier}vmin`;
        tooth.style.height = length;
        tooth.style.width = length;
      } else {
        // set size for the square and circle-shaped teeth
        tooth.style.height = `${side}vmin`;
        tooth.style.width = `${side * multiplier}vmin`;
      }
      // place the tooth
      tooth.style.transform = `rotateZ(${i *
        step}deg) translateX(${displacement}vmin)`;
      // append tooth to the container
      container.appendChild(tooth);
    }
    // restore cover
    const cover = document.createElement("div");
    cover.className = "cover";
    container.appendChild(cover);
  }
}
Enter fullscreen mode Exit fullscreen mode

CodePen

Conclusion

Now you know how to build a gear-shaped object.

Though I did not cover the controls in this article, you can use an API to dynamically modify the number of teeth, the rotation angle of the object, set the height of the tooth, choose from three shapes of the tooth, and set up the thickness of the outline.

Top comments (5)

Collapse
 
cloudychris profile image
Pufu Andrei Cristian

Except that is not a real cog... It may be ok for a settings button, but to be honest, there are icons for that already. There is an online gear generator that does a pretty good job. ( I study mechanical engineering, sorry )

Collapse
 
peacefullatom profile image
Yuriy Markov

Thank you for your reply!

But, if you will thoroughly read the title of the post, it says "gear-shaped", so it "looks-alike" rather than "represents". :)

One more thing - this article is not a gear generator. Instead, it is written to show how to use CSS and JavaScript to build interactive objects, so it is very simplified.

If you have any further questions/comments/improvements, please, feel free to get in touch with me.

Collapse
 
cloudychris profile image
Pufu Andrei Cristian

I do actually. Did you consider using svg, as it integrates well with HTML, css and js? I kinda wanted to play around with interactive SVG for a while, but didn't get that much time...

Thread Thread
 
peacefullatom profile image
Yuriy Markov • Edited

SVG is on my list, but I didn't yet publish all of the non-SVG lessons I'm using to teach others to build the web.

Thread Thread
 
cloudychris profile image
Pufu Andrei Cristian

Cool! If you get around to making an interactive SVG project, it'd be really nice to see it! Have a nice day _^