记由css定位引起的乌龙

1,101 阅读4分钟

前言

事情要从我写的练手小案例开始说起——案例是一个仿小米官网的二级联动分类菜单栏+轮播图。使用了less预编译和vue框架。效果图如下:

image.png

预期效果应该是:当鼠标悬浮在左边分类菜单栏时会对应显示二级菜单栏,鼠标移动到二级菜单栏时可以点击对应的连接进行跳转。

但是实际上,当我的鼠标从悬浮在菜单栏上,移动到二级菜单栏上时,二级菜单栏会从显示变成隐藏。根据js代码的逻辑,应该是不会隐藏才对的,由此我开始了找bug之路。

html结构+css样式

分类菜单栏和二级菜单栏的html结构如下:

<!-- 分类菜单 -->
<div class="menu-content" @mouseout="hideSubMenu">
  <div class="menu-item" v-for="(item, index) in menu_list" @mouseover="showSubMenu(index)">
    <a href="">
      <span>{{ item.title }}</span>
      <i class="fa fa-angle-right"></i>
    </a>
  </div>
</div>
<!-- 二级菜单 -->
<div class="sub-menu" v-show="subMenuShow" @mouseover="showSubMenu" @mouseout="hideSubMenu">
  <div class="inner-box" v-for="(item, index) in menu_list">
    <div class="sub-inner-box" v-show="index === menu_item_index">
      <div class="title">{{ item.title }}</div>
      <div class="sub-row" v-for="(item1) in item.list">
        <span class="bold">{{ item1.menu_title}}:</span>
        <a href="" class="menu" v-for="(item2) in item1.menus">{{ item2 }}</a>
      </div>
    </div>
  </div>
</div>

其中:

  • subMenuShow是布尔值类型的,用于控制二级菜单的显示和隐藏;
  • showSubMenu是方法,用于修改subMenuShow值为true,使二级菜单显示;
  • hideSubMenu是方法,用于修改subMenuShow值为false,使二级菜隐藏;
  • menu_item_index是当前所展示的二级菜单类别的索引值
  • menu_list是二级菜单中的菜单内容

less样式编写如下:

// 分类菜单
.menu-content {
  position: relative;
  width: 244px;
  height: 100%;
  background-color: rgba(7,17,27,0.25);
  padding: 6px 0px 0;
  box-sizing: border-box;
  .menu-item {
    width: 100%;
    height: 64px;
    padding:0 26px;
    box-sizing: border-box;
    a {
      position: relative;
      display: flex;
      align-items: center;
      width: 100%;
      height: 100%;
      color: #fff;
      padding: 0 5px;
      border-bottom: 1px solid rgba(255,255,255,0.2);
      box-sizing: border-box;
      i {
        position: absolute;
        right: 18px;
      }
    }
  }
  .menu-item:last-child a {
    border: none;
  }
}

查找问题

内外边距

经过初步调试,发现当鼠标移动到下图方框内的位置时会隐藏

image.png

而这个位置刚好是menu-item所设置的内边距,于是开始思考是不是内外边距不能触发mouseover,经过查找资料及验证:

image.png image.png

验证外边距margin:给该盒子设置20px的外边距、0px的内边距和mouseover事件,当鼠标从外面进入到margin区域时,不会触发mouseover事件,只有进入到盒子内部(背景色为粉色)才触发mouseover事件。

验证内边距padding:给该盒子设置20px的内边距、0px的外边距和mouseover事件,当鼠标从外面进入刚进入到粉色区域时,就触发mouseover事件。

由此得出结论:padding可以触发mouseover事件,margin事件不可以。

但是由于我做css布局时使用的就是padding内边距,所以按道理来说是可以正常触发mouseover事件的,所以可以得知并不是内外边距的问题

定位层级提升

根据控制台的输出情况,可以发现每次移动到方框位置时,就会调用hideSubMenu方法(该方法的作用是隐藏二级菜单)。而绑定了该方法的地方只有分类菜单中的最外层类名为menu-content的元素和二级菜单中的最外层类名为sub-menu的元素(若忘记可到上面html代码中查看)。

在hideSubMenu方法中打印触发该事件的目标元素可以发现输出的是a元素

image.png

由此可以得知是由a元素触发的mouseout事件,根据事件冒泡机制,触发了绑定在类名为menu-content元素上的mouseout事件监听器,从而调用了隐藏二级菜单的方法hideSubMenu

而根据控制台中的选择指定元素进行检查发现,当鼠标放在a标签外、menu-item上(左图中黑色圆点处)时,所指向的元素并不是我们所期待的menu-item(右图),而是最外层的banner

image.png image.png

原来这是因为在我编写的css样式中,给bannera设置了绝对定位,但没给menu-item设置定位。导致banner的层级提升覆盖了menu-item,而abanner在同一层级,但abanner后,所以a居上。

因此当鼠标从a移动到menu-item上时,鼠标所在的层级是banner的那个层级,而不是menu-item的那个层级。所以鼠标在离开a标签时触发mouseout事件调用hideSubMenu方法,二级菜单被隐藏。鼠标进入menu-item时触发的是banner的mouseover事件,而banner并未绑定mouseover事件的处理函数,所以不调用回调函数。

解决方法

menu-item或它外层的menu-content设置定位:position: relative使得层级得到提升。

层级提升后,鼠标移入移出menu-item时,就能正常触发mouseover和mouseout事件——在鼠标移出a时触发mouseout事件隐藏二级菜单,移入menu-item时触发mouseover事件显示二级菜单,以此达到一直显示二级菜单的效果。

注:案例来自(xhslink.com/UYC8jh)