使用香草JS / CSS对顶部导航栏的打开/关闭进行动画处理

时间:2019-03-13 13:38:57

标签: javascript html css

我正在尝试不使用Bootstrap,但是遇到了一些我一直无法弄清的事情。

对于响应式顶部导航栏,当我单击汉堡菜单时,我希望它为打开和关闭动画(取决于它是打开还是关闭)。

我遇到的问题是在导航中使用display: none会杀死所有/所有动画。 .nav-top__nav-list是动画需要发生的位置,其显示从“无”切换为“块”。我这样做是为了实现可访问性(这意味着除非扩展导航,否则屏幕阅读器不会进入该区域)。我应该采取另一种方式来保持隐藏状态的可访问性,同时仍然能够对导航菜单的打开/关闭进行动画处理吗?

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>

4 个答案:

答案 0 :(得分:1)

您无法在显示的两个值之间设置动画,因此使用另一种隐藏/显示扩展菜单的方式是正确的。这是一个使用高度的示例,另一个示例是不透明度。

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

答案 1 :(得分:1)

display: none无法设置动画。相反,您可以使用visibility: hiddenvisibility: visible

答案 2 :(得分:1)

在CSS中,无法设置display属性的动画。您可以使用heightmax-height。使用max-height的好处是,即使您通过媒体查询在不同屏幕尺寸下更改了字体大小,该容器也将保持最佳高度。因此,我在CSS过渡中使用了max-height。我已经更新了您的代码以获得预期的效果。

function topNavToggle() {
    var topNavList = document.getElementById("topNavList");
    var topNavToggle = document.getElementById("topNavToggle");
    var topNavListLi = document.querySelectorAll("#topNavList li");
    
    if (topNavList.className === "nav-top__nav-list") {
        topNavList.className += " responsive";
        topNavToggle.className += " toggled";
        for (let i = 0; i < topNavListLi.length; i++) {
          var hiddenAttribute = document.createAttribute("hidden");
          //topNavListLi[i].setAttribute('hidden',false);
          topNavListLi[i].setAttributeNode(hiddenAttribute);
        }
    } else {
        topNavList.className = "nav-top__nav-list";
        topNavToggle.className = "nav-top__toggle-button";
        setTimeout(function(){
          for (let i = 0; i < topNavListLi.length; i++) {
            //topNavListLi[i].setAttribute('hidden',true);
            topNavListLi[i].removeAttribute('hidden');
          }
        },300);
    }
}
.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 ul li {
    float: none;
  }
  .nav-top__nav-list.responsive {
    max-height: 170px;
    transition: max-height .3s ease;
  }
  .nav-top__nav-list.responsive ul li {
    display: block;
  }
}
<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>

答案 3 :(得分:1)

由于无法对显示进行动画处理,因此我通常使用setTimeout或requestAnimationFrame进行两步操作。这也是某些框架(例如ng-animate)在内部工作的方式。像这样:

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');
        }
    })
}

还有一些示例CSS:

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

.navVisible.in {
    opacity: 1;
}