Animate open/close of top nav bar with vanilla JS/CSS

问题: I'm trying to work myself off from using Bootstrap, but I've encountered something I've been unable to figure out. For a responsive top nav bar, when I click on the hambu...

问题:

I'm trying to work myself off from using Bootstrap, but I've encountered something I've been unable to figure out.

For a responsive top nav bar, when I click on the hamburger menu, I'd like it to animate open and closed (depending on whether it was open or closed to begin with).

The issue I'm running into is the use of display: none on the nav killing any/all animations. The .nav-top__nav-list is what the animation needs to occur on, which has the display toggled from "none" to "block". I'm doing this for accessibility (meaning a screen reader won't hit this area unless the navigation is expanded). Is there another way I should be doing it to preserve the accessibility nature of being hidden, while still being able to animate the opening/closing of the nav menu?

function topNavToggle() {
    var topNavList = document.getElementById("topNavList");
    var topNavToggle = document.getElementById("topNavToggle");
    if (topNavList.className === "nav-top__nav-list") {
        topNavList.className += " responsive";
        topNavToggle.className += " toggled";
    } else {
        topNavList.className = "nav-top__nav-list";
        topNavToggle.className = "nav-top__toggle-button";
    }
}
.nav-top,
.navbar-nav {
  grid-column-start: 1;
  grid-column-end: 13;
}

.wrapper__1170-max-width {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  max-width: 1170px;
  margin: 0 auto;
  padding: 0 1rem;
}

.display-desktop {
  display: block;
}
.display-mobile {
  display: none;
}

.nav-top {
  background: #466a62;
  min-height: 40px;
}
.nav-top__header {
  grid-column-start: 1;
  grid-column-end: 13;
}
.nav-top__header button {
  background: none;
  border: none;
  cursor: pointer;
  grid-column-start: 12;
  grid-column-end: 13;
  grid-row-start: 1;
  justify-self: right;
}
.nav-top__header button .bar-1,
.nav-top__header button .bar-2,
.nav-top__header button .bar-3 {
  width: 35px;
  height: 5px;
  background-color: #333;
  margin: 6px 0;
  transition: 0.4s;
}
.nav-top__header button.change {
  background: red;
}
.nav-top__header .toggled .bar-1 {
  transform: rotate(-45deg) translate(-9px, 6px);
}
.nav-top__header .toggled .bar-2 {
  opacity: 0;
}
.nav-top__header .toggled .bar-3 {
  transform: rotate(45deg) translate(-8px, -8px);
}
.nav-top__header .nav-brand {
  grid-column-start: 1;
  grid-column-end: 12;
  grid-row-start: 1;
}
.nav-top__nav-list {
  grid-column-start: 1;
  grid-column-end: 13;
}
.nav-top__nav-list ul {
  list-style: none;
  margin: 0;
  padding: 0;
}
.nav-top__nav-list ul li {
  float: left;
}
.nav-top__nav-list ul li a {
  color: #FFF;
  display: block;
  padding: 11px;
  text-decoration: none;
}
.nav-top__nav-list ul li a:hover {
  background: #FFF;
  color: #18453b;
}

@media only screen and (max-width: 992px) {
  .display-desktop {
    display: none;
  }
  .display-mobile {
    display: block;
  }
  .nav-top__header {
    display: grid;
  }
  .nav-top__nav-list {
    display: none;
  }
  .nav-top__nav-list.responsive {
    display: block;
  }
  .nav-top__nav-list.responsive ul li {
    display: block;
    float: none;
  }
}
<nav class="nav-top" aria-label="primary">
    <div class="wrapper__1170-max-width">
        <div class="nav-top__header display-mobile">
            <span class="nav-top__brand">Menu</span>
            <button type="button" id="topNavToggle" class="nav-top__toggle-button" aria-label="Expand and collapse primary site navigation" data-toggle="collapse" data-target="#topNavList" onclick="topNavToggle()">
                 <div class="bar-1"></div>
                 <div class="bar-2"></div>
                 <div class="bar-3"></div> 
             </button>
         </div>
         <div class="nav-top__nav-list" id="topNavList">
             <ul>
                 <li class="active"><a href="#">Home</a></li>
                 <li><a href="#">Page 1</a></li>
                 <li><a href="#">Page 2</a></li>
                 <li><a href="#">Page 2</a></li>
             </ul>
         </div>
     </div>
</nav>


回答1:

You can't animate between two values of display, so you're intuition was correct to use another way of hiding/showing the expanded menu. Here's an example using height, another example would be opacity.

  .nav-top__nav-list {
    height: 0;
    transition: height 0.15s ease-in;
  }
  .nav-top__nav-list.responsive {
    display: block;
    height: 180px;
  }

回答2:

display: none can not be animated. Instead you can use visibility: hidden and visibility: visible.


回答3:

In CSS, display property cannot be animated. You can use height or max-height. The advantage in using max-height will be that, the container will take optimal height even if you change font-size at different screen sizes with media query. So, I have used max-height with CSS transition. I have updated your code to get desired effect.

function topNavToggle() {
    var topNavList = document.getElementById("topNavList");
    var topNavToggle = document.getElementById("topNavToggle");
    if (topNavList.className === "nav-top__nav-list") {
        topNavList.className += " responsive";
        topNavToggle.className += " toggled";
    } else {
        topNavList.className = "nav-top__nav-list";
        topNavToggle.className = "nav-top__toggle-button";
    }
}
.nav-top,
.navbar-nav {
  grid-column-start: 1;
  grid-column-end: 13;
}

.wrapper__1170-max-width {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  max-width: 1170px;
  margin: 0 auto;
  padding: 0 1rem;
}

.display-desktop {
  display: block;
}
.display-mobile {
  display: none;
}

.nav-top {
  background: #466a62;
  min-height: 40px;
}
.nav-top__header {
  grid-column-start: 1;
  grid-column-end: 13;
}
.nav-top__header button {
  background: none;
  border: none;
  cursor: pointer;
  grid-column-start: 12;
  grid-column-end: 13;
  grid-row-start: 1;
  justify-self: right;
}
.nav-top__header button .bar-1,
.nav-top__header button .bar-2,
.nav-top__header button .bar-3 {
  width: 35px;
  height: 5px;
  background-color: #333;
  margin: 6px 0;
  transition: 0.4s;
}
.nav-top__header button.change {
  background: red;
}
.nav-top__header .toggled .bar-1 {
  transform: rotate(-45deg) translate(-9px, 6px);
}
.nav-top__header .toggled .bar-2 {
  opacity: 0;
}
.nav-top__header .toggled .bar-3 {
  transform: rotate(45deg) translate(-8px, -8px);
}
.nav-top__header .nav-brand {
  grid-column-start: 1;
  grid-column-end: 12;
  grid-row-start: 1;
}
.nav-top__nav-list {
  grid-column-start: 1;
  grid-column-end: 13;
}
.nav-top__nav-list ul {
  list-style: none;
  margin: 0;
  padding: 0;
}
.nav-top__nav-list ul li {
  float: left;
}
.nav-top__nav-list ul li a {
  color: #FFF;
  display: block;
  padding: 11px;
  text-decoration: none;
}
.nav-top__nav-list ul li a:hover {
  background: #FFF;
  color: #18453b;
}

@media only screen and (max-width: 992px) {
  .display-desktop {
    display: none;
  }
  .display-mobile {
    display: block;
  }
  .nav-top__header {
    display: grid;
  }
  .nav-top__nav-list {
    max-height: 0;
    overflow: hidden;
    transition: max-height .3s ease;
  }
  .nav-top__nav-list.responsive {
    max-height: 170px;
  }
  .nav-top__nav-list ul li {
    display: block;
    float: none;
  }
}
<nav class="nav-top" aria-label="primary">
    <div class="wrapper__1170-max-width">
        <div class="nav-top__header display-mobile">
            <span class="nav-top__brand">Menu</span>
            <button type="button" id="topNavToggle" class="nav-top__toggle-button" aria-label="Expand and collapse primary site navigation" data-toggle="collapse" data-target="#topNavList" onclick="topNavToggle()">
                 <div class="bar-1"></div>
                 <div class="bar-2"></div>
                 <div class="bar-3"></div> 
             </button>
         </div>
         <div class="nav-top__nav-list" id="topNavList">
             <ul>
                 <li class="active"><a href="#">Home</a></li>
                 <li><a href="#">Page 1</a></li>
                 <li><a href="#">Page 2</a></li>
                 <li><a href="#">Page 2</a></li>
             </ul>
         </div>
     </div>
</nav>


回答4:

Since display can't be animated, I normally do a 2-step approach using setTimeout or requestAnimationFrame. This is also the way some frameworks like ng-animate work internally. Something like:

var state = Enums.NavState.Closed;

function openNav() {
    state = Enums.NavState.Opening;
    var nav = $('.topNavList')
    nav.addClass('navVisible');

    requestAnimationFrame(function() {
        // This is async, so make sure we're still opening;
        if (state === Enums.NavState.Opening) {
            state = Enums.NavState.Open;
            nav.addClass('in');
        }
    })
}

And some sample css:

.navVisible {
    display: block;
    opacity: 0;
    transition: opacity 300ms;
}

.navVisible.in {
    opacity: 1;
}
  • 发表于 2019-03-14 18:14
  • 阅读 ( 215 )
  • 分类:sof

条评论

请先 登录 后评论
不写代码的码农
小编

篇文章

作家榜 »

  1. 小编 文章
返回顶部
部分文章转自于网络,若有侵权请联系我们删除