【问题标题】:javascript and css button doesn't work on first click & Flexboxjavascript 和 css 按钮在第一次单击和 Flexbox 时不起作用
【发布时间】:2018-07-09 14:31:23
【问题描述】:

背景: 我正在为一位艺术家制作一个页面,我在网站上遇到了一些有趣的行为。

我使用 flexbox 而不是 bootstrap 进行布局,因为该站点非常小并且没有大量元素。 Flexbox 非常适合对网站进行布局,尤其是针对不同媒体尺寸的更改。

然而,在最小的媒体尺寸中,移动布局,我遇到了一些麻烦。我喜欢 bootstrap 中可折叠菜单的外观,但是因为我没有使用 bootstrap,所以我从 font awesome 中获得了图标,现在我正在尝试使用 JavaScript 和 CSS 手动处理可折叠菜单属性。但是,如果不使用引导程序,我不确定为什么我的可扩展菜单仅在重复点击后才有效,而不是在第一次点击时有效。

期望: 在最小的媒体查询(窗口

问题: 这种情况会发生并切换,但只有在多次单击后才会发生。

问题: 1. 是什么导致可点击行为仅在点击几下后才起作用? 2. 为什么'none'的显示属性不能应用于'nav-left'项? 3.菜单显示时,除了使用margin来抵消出现的菜单项,有没有办法保证它们出现在汉堡按钮区域下方? (即我希望链接 1-3 在它们出现时显示在汉堡菜单下方)。

jsfiddle here

function menuToggle() {
  var x = document.getElementById('nav-right');
  var y = document.getElementsByClassName("nav-left");
  if (x.style.display == "none") {
    x.style.display = "flex";
    y.style.display = "none";
  } else {
    x.style.display = "none";
    y.style.display = "flex";
  }
}
* {
  margin: 0;
  padding: 0;
}

html,
body {
  background-color: gray;
  height: 100%;
  width: 100%;
}

.nav-left {
  font-family: 'Kristi', cursive;
  font-size: 5.5em;
  color: #e319b6;
  letter-spacing: 2px;
  margin: 0;
  padding: 0 0 0 50px;
}

.nav-left a {
  text-decoration: none;
  color: #e319b6;
}

.hamburger {
  display: none;
}

li a {
  text-decoration: none;
  color: white;
}

li a:hover {
  text-decoration: none;
  color: #e319b6;
}

li {
  padding: 50px 10px;
  list-style: none;
  font-family: 'Montserrat', sans-serif;
  font-size: 1.7em;
  font-weight: 100;
  color: white;
  display: inline-block;
}

@media (max-width:767px) {
  .hamburger {
    width: 100%;
    padding: 2% 0;
    display: flex;
    color: white;
    font-size: 2em;
    justify-content: center;
    background: rgba(0, 0, 0, .1);
    position: absolute;
    cursor: pointer;
    border: none;
  }
  .pagenav {
    width: 100%;
    display: flex;
    align-items: center;
    flex-direction: column;
  }
  .nav-left {
    width: 100%;
    display: flex;
    justify-content: center;
    order: 2;
    margin-top: 10%;
    padding: 0;
  }
  #nav-right {
    display: none;
    justify-content: center;
    width: 100%;
  }
  .nav {
    width: 100%;
  }
  nav ul {
    display: flex;
    flex-direction: column;
    width: 100%;
  }
  nav ul li {
    display: flex;
    justify-content: center;
    width: 100%;
  }
  nav ul li {}
  nav ul li:hover {
    background: rgba(0, 0, 0, .8);
  }
  nav ul li:hover a {
    color: #e319b6;
  }
}
<head>
  <meta charset="utf-8">
  <title></title>


  <!-- Font Awesome -->
  <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.1.0/css/all.css" integrity="sha384-lKuwvrZot6UHsBSfcMvOkWwlCMgc0TaWr+30HWe3a4ltaBwTZhyTEggF5tJv8tbt" crossorigin="anonymous">


</head>

<body>

  <div class="pagenav">

    <div class="nav-left">
      placeholder
    </div>

    <button class="hamburger" onclick="menuToggle()">
                  <i class="fas fa-bars"></i>
              </button>

    <nav id="nav-right">
      <ul>
        <li><a href="reel.html">Link 1</a></li>
        <li><a href="">Link 2</a></li>
        <li><a href="">Link 3</a></li>
      </ul>

    </nav>

  </div>


</body>

【问题讨论】:

  • 您的脚本有一个错误:y 得到一个 HTML 集合,而不是一个元素。你可能想要y[0]
  • 您是否建议我改为给 div 一个“id”来定位,并将 y 目标替换为该目标“id”的 getElementById?无论出于何种原因,它似乎甚至可以针对我指定的类集合,但不是在第一次点击时。
  • chbchb55 解释了它在第一次点击时不起作用的原因,因为在函数的第一次传递中它读取x.style.display,这等于"",而不是"none"。如果它确实等于"none",那么它将在第一遍中起作用
  • 然后使用第一个索引var y = document.getElementsByClassName("nav-left")[0];@PanayiotisSpanos 获取参考
  • @PanayiotisSpanos 关于它被分配"none":实际上没有,但你的逻辑很好。问题是 JavaScript 在样式中修改的 DOM 元素属性默认情况下未设置为任何内容。您的逻辑是:好吧,它应该读取 CSS 值或元素显示具有的任何值。但它没有,默认情况下它没有(""),只有当你通过 JavaScript 更改它时它才有任何东西

标签: javascript css flexbox


【解决方案1】:

问题在于 HTML,而不是你的函数

您遇到的问题是if (x.style.display == "none") 应该第一次通过,因为它被隐藏了对吧?嗯,它没有通过,因为根据style 属性,display 不等于none,因此,您无法判断它是隐藏的。要解决这个问题,只需:

<nav id='nav-right' style='display: none'>

现在你的代码知道#nav-right 是隐藏的并且会在第一次点击时起作用。

方案二,修改代码:

您可以使用一次性切换变量。这样,第一次单击按钮时,它将显示菜单,然后将切换变量设置为 false。像这样:

let firstMenuClickToggle = true

function menuToggle() {
  ...
  if (x.style.display == 'none' || firstMenuClickToggle) {
    firstMenuToggle = false
    ...

选项3,同时检查空字符串和'none'

正如@flen 所说,您可以在'none' 旁边搜索一个空字符串,但为什么不能同时进行。

例如,尝试将您的 if 语句替换为以下内容:

if (!'none'.indexOf(x.style.display)) {

实际上,根据我对 Benchmark.js 的测试,这比 @flen 的单独比较快得多,当每个运行 10,000 次时几乎是 5 秒的差异。

这是因为'' 和'none' 这两个字符串在技术上都存在于'none' 中,因此,如果其中任何一个值存在,它们将返回0。因此,我使用! 运算符来检查这一点,!0 将始终返回 true,但任何其他数字(即 !-1)将返回 false。

编辑:使其更具可读性

String.prototype.emptyOrEqualTo = function(string) {
  return string.indexOf(this)
}

// in your function
if (x.style.display.emptyOrEqualTo('none')) {

虽然通常不会修改String.prototype,但它确实比大多数其他提高可读性的方法产生了更高的可读性。事实上,你甚至可以在上面加上一个is,它就是一个句子。

display.isEmptyOrEqualTo('none')

现在你可以实际阅读了:

如果显示为空或等于“无”


仅供参考,在您的 JSFiddle 中,y.style 是 undefined,因为您使用了 .getElementsByClassName,但它返回一个数组并且您没有获得返回数组的第一个索引。

另外几点说明:

  • 我建议使用比x 和y 更具描述性的变量名称,因为它们没有传达其含义,您必须查找它们的定义行才能理解,这并不是一个“最佳实践”。
  • 您可能应该将您的 javascript 移动到一个单独的文件中,就像您已经对 CSS 所做的那样。
  • 可选:我建议同时学习使用const 和let。
  • 继续坚持,你做得很好。

【讨论】:

  • 等等,这个属性是在@media下的css中设置的(max-width:767px)。如果我在您建议的地方内联应用它,菜单不会以其他媒体尺寸显示吗?
  • 您可以使用!important 覆盖此处的内联样式。
  • 或者使用一个变量来确保如果这是第一次按下按钮,显示它,而不是隐藏。
  • 恕我直言,我认为创建另一个变量 (fisrtMenuClickToggle) 只是为了使第一次通过成为可能,因为您可以简单地在 if 条件中指定 || x.style.display === ""。我认为这提高了可读性,并且是一种更好的编码实践,因为如果您在代码中多次执行此操作,您会创建一堆不必要的变量,这会降低代码的可读性。只是我的 2c
  • 另外,如果你多次运行代码,JS 编译器就会开始优化,所以你在运行代码 10000 次时得到的结果不一定是一次执行需要 10000 次的时间。即使是这样,0.7ms 对 1.2ms 基本上也不算什么。
【解决方案2】:

chbchb55 已经解释了问题所在,但我认为您不应该更改 HTML 来修复 JavaScript 代码中的错误。相反,在此处将您的 menuToggle 函数更改为:

function menuToggle() {
  var x = document.getElementById('nav-right');
  var y = document.getElementsByClassName("nav-left")[0]; //changed to get the first element of the HTMLCollection
  if (x.style.display == "none" || x.style.display === "") { //changed to also accept the original value, which is "" instead of "none"
    x.style.display = "flex";
    y.style.display = "none";
  } else {
    x.style.display = "none";
    y.style.display = "flex";
  }
}

为了清楚起见,让我重新表述一下解决方案:第一次调用menuToggle 函数时,它读取x.style.display,它的值实际上是"",而不是您似乎期望的"none"。由于它不是"none",它运行else 子句并继续将x.style.display 分配给"none" 和y.style.display 分配给"flex"。这不应该发生,因为这表示您的菜单已关闭。所以只有在第二次调用时它才会打开它,因为函数的第一次调用运行了else 子句并关闭了(已经关闭的)菜单。


附录:
chbchb55 还提出了一个巧妙的优化,使用if (!'none'.indexOf(x.style.display)) 甚至创建一个原型作为语法糖以使其更具可读性。我仍然反对使用它有两个原因。

首先,它的可读性仍然不如if (x.style.display == "none" || x.style.display === "")。在这里,任何阅读代码的人都可以立即看到我们正在检查 2 个条件,并且只有这 2 个。使用 indexOf,并不清楚我们是否也在检查 "",因此我们必须在评论。在我看来,它的可读性更差。

其次,如果速度是一个问题,这实际上是最快的解决方案,将if 替换为else 子句:if (x.style.display === "flex"),然后执行else 子句中的内容。这样,else 将捕获 x === '' 和 x == 'none' 以及其他任何内容。如果代码长度有问题,我们可以使用三元运算符进一步改进:

function menuToggle() {
  var x = document.getElementById('nav-right');
  var y = document.getElementsByClassName("nav-left")[0]; //changed to get the first element of the HTMLCollection
  x.style.display === "flex" ? (x.style.display = "none", y.style.display = "flex") : (x.style.display = "flex", y.style.display = "none");     
}

不过,速度和长度在这里不是问题,就像现在的代码一样。如果它提高了 1 毫秒,我会印象深刻。我认为原始解决方案是最易读的,这就是为什么我仍然建议它作为答案。尽管如此,考虑使用indexOf 还是很聪明的,如果我没记错的话,它比ES6 快includes

【讨论】:

  • 这确实有效,但是否再次解释了为什么它在第二次点击而不是第一次点击时有效?我觉得奇怪的是,它在第​​一次点击时没有“看到”媒体查询显示属性,但在第二次点击时突然“看到”了它。
  • @PanayiotisSpanos 我现在提供了解释,但如果您仍有疑问,请告诉我,我会尽量说得更清楚
  • 你的解释很有道理。
  • 接受作为答案,因为您是第一个解释正在发生的事情的人,并且您建议的更改可以解决问题。
  • @PanayiotisSpanos 我很乐意提供帮助,但我们应该避免在不相关的事情上做很多 cmets。我稍后会创建一个聊天室并邀请你加入
猜你喜欢
  • 2015-03-21
  • 1970-01-01
  • 2021-02-14
  • 2020-06-25
  • 2013-06-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-07-09
相关资源
最近更新 更多