Cylon Centurion

By Your Command

The Cylon Centurion Fansite

Recently one of my students had the interesting concept of a menu with a sliding, animated highlight, but was unsure of how to create it in code. After messing around with a few possibilities after class, this is my response.

The markup is fairly straightforward:

<header id="cylons">
    <img src="cylon-centurion.png" alt>
    <h1>By Your Command</h1>
    <h2>The Cylon Centurion Fansite</h2>
    <nav id=cylon-nav>
        <a href="#">Base</a>
        <a href="#">Codex</a>
        <a href="#">Interface</a>
        <a href="#">Operations</a>
    </nav>
</header>

One particular challenge was making the red highlight, visible on mouseover on each navigation element, both responsive and easily animated. Traditionally, this would be tackled by using JavaScript to read the getComputedStyle of the navigation elements, inclucing their current position and width, and moving another element behind them to match a changing location and dimension. After playing around with different techniques, I realised that I could solve the problem elegantly by using a background linear gradient with background-position.

If each of the links takes up an equal amount of space:

#cylons a {
    color: currentColor;
    text-align: center;
    text-decoration: none;
    display: inline-block;
    width: 20%;
    padding: 0 1rem;
    font-size: 1.2rem;
}

Then the highlight behind them (created out of a solid linear gradient, meaning that the start and end colors are exactly the same) can use the same horizontal size:

#cylonnav {
    font-size: 0;
    text-align: right;
    background-image: linear-gradient(90deg, #f00, #f00);
    background-position: 0% 0;
    background-size: 20% 100%;
    background-repeat: no-repeat;
    transition: .5s background-position linear;
}

A Confusion of Percentages

Testing background-position by hard-coding changes into the CSS, I realised something a little confusing: the starting location of the gradient is measured from the background's left. This lead to the following script:

function woosh(e) {
    e.preventDefault();
    var index = links.indexOf(e.target);
    nav.style.backgroundPosition = (basePercentage * (index + 1)) + "% 0%";
}
var nav = document.getElementById("cylonnav"),
links = Array.prototype.slice.call(nav.getElementsByTagName("a"), 0),
basePercentage = 100 / links.length;
nav.addEventListener("mouseover", woosh, false);

The JavaScript looks at the navigation and uses the number of links found inside it as a percentage multiplier to locate the background. Note that the script looks at mouseover activity on the navigation element in general, then determines which link is actually being hovered over; a far more efficient process than trying to track activity on each link.

You can find the full code for the UI on the associated CodePen demo; the one small issue with this approach is that the movement between any links is always constant in time, making the highlight appear to move twice as fast between “base” and “interface” as it does between “base” and “codex”.

Enjoy this piece? I invite you to follow me at twitter.com/dudleystorey to learn more.
Check out the CodePen demo for this article at https://codepen.io/dudleystorey/pen/YXjPgP