Create animated navigation indicator with HTML, CSS and JS
Tags: javascript, webdev, html, css
Published: 2021-01-11 07:03:13

I've never had the chance to use this kind of menu on my regular projects, so I decided to make one, just for fun. I was really happy with the final result, so I decided to share it with the community. Even though I created this one using React, I wanted to make one with vanilla JavaScript so you can use it on any project.

# HTML setup

Let's start off by creating a simple navigation markup with a simple list with a few links. We'll add `id` attributes to the root `nav` element and a `div` element that we'll use as a pointer. We'll need a few classes for the nav, list, and pointer elements so we can style them.

```html
<nav class="nav" id="js-nav">
  <div id="js-pointer" class="nav__pointer"></div>
  <ul class="nav__list">
    <li><a href="#">Overview</a></li>
    <li><a href="#">Goals</a></li>
    <li><a href="#">Inspiration</a></li>
    <li><a href="#">Profile</a></li>
  </ul>
</nav>
```

# CSS markup

Let's add some styles. The following code snippet shows only required styles.

We need to position `nav` element relatively and add some padding. We'll need that value for JavaScript. We'll position `nav__pointer` absolutely with a `z-index` value that is lower than `nav__list` so the indicator is positioned under the links.

We need to position the links in a `4 * 1fr` column grid so all link containers are equal width.

```css
.nav {
  position: relative;
  padding: 1em;
}

.nav__pointer {
  z-index: 1;
  position: absolute;
  top: 0.6em;
  left: 1em;
  background-color: #bada55;
  height: 1.8em;
  transition: transform 0.25s ease-in-out;
  border-radius: 0.3em;
  will-change: transform;
  backface-visibility: hidden;
}

.nav__list {
  position: relative;
  z-index: 2;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
}

```

## JavaScript

We need to select our navigation, indicator, and link elements with JavaScript. Remember that `1em` value padding from CSS? We'll use half of that value so we can position the pointer appropriately.

To avoid using magic numbers, we'll dynamically calculate the indicator's width depending on the number of columns in the grid (how many links there are).

```js
var CONTAINER_PADDING_HALF = "0.5em";

pointer.style.width = "calc(100% /"+links.length+" - "+CONTAINER_PADDING_HALF+")"
```

For each selected link within the `nav` element, we'll add a `data` attribute that stores a percentage value based on the order. If the link is first in the list, it will have a `0%` value, if it's a second, it will have a `100%` value, etc. We'll use those values for transforms.

We're also attaching a click event listener for each link.

```js
for(var i=0; i<links.length; i++){
  var current = links[i];
  current.dataset.order = i * 100 + "%";  
  current.addEventListener("click", movePointer);
}
```

Our link click event handler is very simple - it only applies a CSS transform attribute to the navigation indicator. The value that is being applied depends on `data-order` attribute that we've set. 


```js
function movePointer(e) {
  var order = e.currentTarget.dataset.order;
  pointer.style.transform = "translate3d("+order+",0,0)"
}
```
Since the width of the indicator matches the width of the each navigation link in the grid, and we've positioned the indicator absolutely and to the start of the first link, we only have to apply transforms in `100%` increments. First link will have the `0%` value, second link will have the `100%` value, third link will have the `200%` value, etc.

By using 3D CSS transforms, this animation will be GPU-powered and it will be smooth and performant.


## Final Result

Here is the CodePen link to the final result.

{% codepen https://codepen.io/AdrianBece/pen/vYXjeed default-tab=result %}

<hr />

These articles are fueled by coffee. So if you enjoy my work and found it useful, consider buying me a coffee! I would really appreciate it.

<a href="https://www.buymeacoffee.com/ubnZ8GgDJ" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/default-red.png" alt="Buy Me A Coffee" style="height: 51px !important;width: 217px !important;" ></a>

Thank you for taking the time to read this post. If you've found this useful, please give it a ❤️ or 🦄, share and comment.