Text On Hover Animation Not Working Properly With Space

I followed some tutorials here http://tympanus.net/codrops/2013/08/06/creative-link-effects/

and made a nice animation on hover for navigation list items.

Here’s the HTML:

<div id="nav">
  <ul id="navbar">
      <li><a href="#" data-hover="Home">Home</a></li>
      <li><a href="#" data-hover="About">About</a></li>
      <li><a href="#" data-hover="Contact">Contact</a></li>
      <li><a href="#" data-hover="Sign Up">Sign Up</a></li>
  </ul>
</div>

And here’s the CSS:

#navbar {
  list-style: none;
}

#navbar li {
  display: inline-block;
  padding: 15px;
}

#navbar li a {
  position: relative;
  display: inline-block;
  margin: 15px 25px;
  text-decoration: none;
  letter-spacing: 1px;
  font-weight: bold;
  text-shadow: 0 0 1px rgba(255, 255, 255, 0.3);
  font-size: 24px;
  padding: 10px 0;
  border-top: 1.5px solid #0972b4;
  color: #0972b4;
}

#navbar li a:after {
  position: absolute;
  top: 0;
  left: 0;
  overflow: hidden;
  padding: 10px 0;
  max-width: 0;
  border-bottom: 1.5px solid #000;
  color: #000;
  content: attr(data-hover);
  -webkit-transition: max-width 0.5s;
  -moz-transition: max-width 0.5s;
  transition: max-width 0.5s;
}

#navbar li a:hover:after,
#navbar li a:focus:after {
  max-width: 100%;
}

It works perfectly for ‘Home’, ‘About’ and ‘Contact’ but not for ‘Sign Up’; clearly because of the space!

I understand how the code works but I can’t know why such a bug exists!

The pseudo class after puts the same text on top of the original text and then colors it and extends the bottom-border on hover according to a transition rule.

Here’s a JSFiddle: https://jsfiddle.net/rnw00/3dhcz84b/1/


solution

The reason it’s occurring is because you’re transitioning the pseudo elements width from 0 to 100%. When the width is less than 100%, the text wraps to a new line, resulting in the issue that you’re seeing.

You can prevent this by adding white-space: pre to the descendant anchor element.

Updated Example

#navbar li a {
  position: relative;
  display: inline-block;
  /* ... */
  white-space: pre;
}
#navbar {
  list-style: none;
}
#navbar li {
  display: inline-block;
  padding: 15px;
}
#navbar li a {
  position: relative;
  display: inline-block;
  margin: 15px 25px;
  text-decoration: none;
  letter-spacing: 1px;
  font-weight: bold;
  text-shadow: 0 0 1px rgba(255, 255, 255, 0.3);
  font-size: 24px;
  padding: 10px 0;
  border-top: 1.5px solid #0972b4;
  color: #0972b4;
  white-space: pre;
}
#navbar li a:after {
  position: absolute;
  top: 0;
  left: 0;
  overflow: hidden;
  padding: 10px 0;
  width: 0;
  border-bottom: 1.5px solid #000;
  color: #000;
  content: attr(data-hover);
  -webkit-transition: width 0.5s linear;
  -moz-transition: width 0.5s linear;
  transition: width 0.5s linear;
}
#navbar li a:hover:after,
#navbar li a:focus:after {
  width: 100%;
}
<div id="nav">
  <ul id="navbar">
    <li><a href="#" data-hover="Home">Home</a>
    </li>
    <li><a href="#" data-hover="About">About</a>
    </li>
    <li><a href="#" data-hover="Contact">Contact</a>
    </li>
    <li><a href="#" data-hover="Sign Up">Sign Up</a>
    </li>
  </ul>
</div>

By changing the white-space property’s value to pre or nowrap, you essentially prevent the pseudo element’s text from wrapping (if you’re wondering why I added that to the anchor element rather than the pseudo element, it’s because the value is inherited since the pseudo element is essentially a child element. It’s probably safer adding that directly to the anchor element because you don’t want one element wrapping, and the other element staying on one line. This ensures that the behavior is consistent between elements).