【问题标题】:Animate open/close of top nav bar with vanilla JS/CSS使用香草 JS/CSS 动画顶部导航栏的打开/关闭
【发布时间】:2019-03-13 13:38:56
【问题描述】:

我正在努力摆脱使用 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>

【问题讨论】:

  • 也许你可以尝试点击切换显示:首先阻止/无,然后使用带有动画样式的延迟切换类

标签: javascript html css


【解决方案1】:

在 CSS 中,display 属性无法设置动画。您可以使用height 或max-height。使用max-height 的优势在于,即使您使用媒体查询更改不同屏幕尺寸的字体大小,容器也会采用最佳高度。所以,我使用了 max-height 和 CSS 过渡。我已更新您的代码以获得所需的效果。

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>

【讨论】:

  • 这几乎是完美的,除了隐藏的链接仍然可以通过键盘导航或屏幕阅读器使用。这里有没有解决方案可以在导航关闭后隐藏它们?
  • @JesseEarley 我已经通过在菜单折叠时添加 HTML5 hidden 属性来更新代码。因此,它不会被选项卡聚焦,也不会被屏幕阅读器读取。希望它现在对你有用!
  • 谢谢!另一个问题......使用 hidden 属性,如果您将浏览器窗口向后拉伸超出媒体查询(当导航关闭时),链接现在被隐藏。我在 JS 中添加了一个“matchMedia”查询,当浏览器窗口在导航关闭的情况下伸展时,它确实将隐藏属性设置回 true……但导航项仍然隐藏。有什么想法吗?
  • 编辑:NM,我想删除该属性,而不是将其设置为 false。即使是 'hidden=false' 也会使元素隐藏在视图之外。
  • @JesseEarley 我更新了代码以添加/删除hidden 属性,而不是设置它true 或false。请检查一下。
【解决方案2】:

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

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

【讨论】:

  • 效果在这里有效,但我仍然遇到链接本身可以通过键盘导航或屏幕阅读器切换的问题。使用这种方法,动画结束后如何隐藏它们?
【解决方案3】:

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

【讨论】:

  • 使用你的方法隐藏,连同上面的解决方案(除了用最大高度交换高度),这工作......有点。在菜单栏完全关闭之前,动画结束时仍有“打嗝”。
【解决方案4】:

由于无法为显示设置动画,我通常使用 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;
}

【讨论】:

  • 能否提供有关此解决方案的更多详细信息,或更改我提供的代码以合并您的解决方案?我已尝试使用您提供的内容,但没有看到任何效果。
  • 关键位只是在激活 css 转换之前将您的元素设置为 display: block。我的代码是一个显示原理的简化示例,您的实际代码可能会更长。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-01-18
  • 1970-01-01
  • 1970-01-01
  • 2020-07-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多