变形标签

Avatar of Chris Coyier
Chris Coyier

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 200美元免费额度!

标签是一种简单的设计模式,其中一行链接显然是可点击的导航,当点击一个链接时,会显示新的内容。当然,有很多变体,但它是那里最普遍的导航设计模式之一。当以水平行排列时,它也是那里最不适合小屏幕的设计模式之一。

不过,我们可以让它工作。

在不采取任何措施帮助小屏幕上的标签的情况下,您会遇到某种问题

  • 您让页面“缩小”,标签变成了微小的点击目标
  • 您让页面宽度等于设备宽度,然后…
    • 标签没有足够的空间,因此被截断了。
    • 标签换行,看起来很奇怪,并占用太多空间。

基本上:我们需要在这里做一些事情来使标签更适合小屏幕。

之前已经做过

流行的解决方案包括

我想用一些特定的目标再次实现它。

目标

这是计划

  • 当有空间时显示正常的“标签”,当没有空间时显示下拉菜单。
  • 始终显示并突出显示“当前”标签。
  • 适用于所有内容都在页面上且内容面板隐藏/显示的 #hash 标签。
  • 适用于链接标签,其中标签链接到不同的 URL。
  • HTML 语义化。
  • HTML 版本不变。
  • JavaScript 版本不变。
  • 您可以链接到特定的标签。

因此,我们不仅要解决设计问题,还要解决功能问题。

像“画布外”样式这样的模式在这里不起作用,因为我们试图显示当前标签,而不是隐藏它。转换为 <select> 下拉菜单不起作用,因为那是不同的 HTML 和不同的 JavaScript。

HTML

标签是导航,因此使用 <nav>role 应该由标签隐含,但您不能总是依赖它,因此我们添加了 role。我们在最外层元素(<nav>)上使用一个类用于 CSS。导航项本身位于列表中 因为这是最好的。每个链接都有一个 #hash 目标或一个有效的 URL。

<nav role='navigation' class="transformer-tabs">
    <ul>
      <li><a href="#tab-1">Important Tab</a></li>
      <li><a href="#tab-2" class="active">Smurfvision</a></li>
      <li><a href="#tab-3">Monster Truck Rally</a></li>
      <li><a href="http://google.com">Go To Google &rarr;</a></li>
    </ul>
</nav>

没有多余的东西。

标签视图 CSS

每个“状态”(标签或下拉菜单)的标签有一组特定的 CSS。您可以通过先设置下拉菜单的样式,然后使用最小宽度媒体查询重新排列以使其看起来像标签,或者通过先设置标签的样式,然后使用最大宽度媒体查询重新排列为下拉菜单,来“优先考虑移动设备”;或者您可以“优先考虑桌面设备”。它们差异很大,我个人觉得没有太大优势,但我建议您与网站其他地方的设计保持一致。

本演示使用桌面优先和 SCSS。

.transformer-tabs {
  ul {
    list-style: none;
    padding: 0;
    margin: 0;
    border-bottom: 3px solid white;
  }
  li {
    display: inline-block;
    padding: 0;
    vertical-align: bottom;
  }
  a {
    display: inline-block;
    color: white;
    text-decoration: none;
    padding: 0.5rem;
    &.active {
      border-bottom: 3px solid black;
      position: relative;
      bottom: -3px;
    }
  }
}

只是一行链接,下方有一条线。带有“active”类的锚链接会获得不同的颜色边框,该边框与边缘到边缘的边框重叠。vertical-align 使所有链接在同一基线上对齐,当活动链接获得边框而其他链接没有时。

下拉视图 CSS

我们需要找到一个视口宽度,在该宽度下标签的外观会失效,并在那里放置一个媒体查询。本演示使用 700px。

.transformer-tabs {
  ...
  @media (max-width: 700px) {
    ul {
      border-bottom: 0;
      overflow: hidden;
      position: relative;
      background: linear-gradient(#666, #222);
      &::after {
        content: "☰"; /* "Three Line Menu Navicon" shows up */
        position: absolute;
        top: 8px;
        right: 15px;
        z-index: 2;
        pointer-events: none;
      }
    }
    li {
      display: block; /* One link per "row" */
    }
    a {
      position: absolute; /* Stack links on top of each other */
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      &.active {
        border: 0;
        z-index: 1; /* Active tab is on top */
        background: linear-gradient(#666, #222);
      }
    }
  }
}

当媒体查询生效时,会得到以下结果

活动标签仍然很明显(它是唯一显示的标签),并且显示了一个 三线菜单导航图标,它正逐渐成为显示更多导航的通用符号。

JavaScript 结构

我们需要的功能

  • 如果它是 #hash 链接,则选择它会显示具有该 ID 的面板并隐藏当前显示的面板。
  • 更改 URL 以指示该 #hash,但不影响历史记录(没有恼人的后退按钮)。
  • 直观地指示现在处于活动状态的标签(适当地切换类)。
  • 如果是链接标签,则允许该链接工作。
  • 如果页面加载时带有与标签匹配的哈希值,则转到该标签。
  • 在小屏幕的下拉状态下,允许在选择下拉菜单时将其打开/关闭。

基于这些需求的一些结构

var Tabs = {

  init: function() {
    this.bindUIfunctions();
    this.pageLoadCorrectTab();
  },

  bindUIfunctions: function() {
  },

  changeTab: function(hash) {
  },

  pageLoadCorrectTab: function() {
  },

  toggleMobileMenu: function(event, el) {
  }

}

Tabs.init();

选择时更改标签

我们唯一需要在选择时更改标签的时间是当所选标签是 #hash 链接时。否则,它是一个链接标签,应该只遵循该链接。(并且“选择”是指单击或点击或使用 Tab 键选择并激活或任何操作。)因此,我们的 changeTab 函数可以只接受该哈希值并使用它。

  changeTab: function(hash) {
    
    // find the link based on that hash
    var anchor = $("[href=" + hash + "]");

    // find the related content panel
    var div = $(hash);

    // activate correct anchor (visually)
    anchor.addClass("active").parent().siblings().find("a").removeClass("active");

    // activate correct div (visually)
    div.addClass("active").siblings().removeClass("active");

    // update URL, no history addition
    window.history.replaceState("", "", hash);

    // Close menu, in case in dropdown state
    anchor.closest("ul").removeClass("open");

  },

处理标签点击

我们将在这里使用标准事件委托以提高效率。任何“点击”(对点击操作也适用),假设它不是已经激活的标签,并且它是一个 #hash 链接,都只会将该哈希值传递给 changeTab 函数。

    // Delegation
    $(document)
      .on("click", ".transformer-tabs a[href^='#']:not('.active')", function(event) {
        Tabs.changeTab(this.hash);
        event.preventDefault();
      })

… 以及 preventDefault(),以防止页面尴尬地跳动。

切换下拉菜单

如果媒体查询生效,并且标签处于其下拉状态,则当点击 .active 标签时,我们可以切换下拉菜单的“打开”和“关闭”。由于我们的样式,我们知道活动标签覆盖了整个可点击区域。

    $(document)
      // ... first click handler, chaining for efficiency 
      .on("click", ".transformer-tabs a.active", function(event) {
        Tabs.toggleMobileMenu(event, this);
        event.preventDefault();
      });

toggleMobileMenu 函数非常简单。但我仍然喜欢我们将其抽象成自己的函数,以防有一天它需要做更多的事情,我们不会让代码变得杂乱无章。

  toggleMobileMenu: function(event, el) {
    $(el).closest("ul").toggleClass("open");
  }

.open 类通过一些 CSS 更改直观地打开菜单。

.transformer-tabs {
  ...
  @media (max-width: 700px) {
    ul {
      ...
      &.open {
        a {
          position: relative;
          display: block;
        }
      }
    }
  }
  ...
}

删除这些标签上的绝对定位并将它们设为块级元素,使菜单向下扩展并将下面的内容也向下推。这就是使其成为下拉菜单的原因。

页面加载时使用 #hash 加载正确的标签

事实证明这非常容易。只需查看 URL 中的哈希值并将其传递给 changeTab 函数即可。

  pageLoadCorrectTab: function() {
    this.changeTab(document.location.hash);
  },

这就是为什么我们将功能抽象成我们可以重复使用的函数,而不是让代码变得杂乱无章。

这不仅仅是理论

我在修复了CodePen 上的标签之后写下这篇文章,之前这些标签有点糟糕,直到我做了这个改进。CodePen 上既有 #hash 链接标签,也有真正的链接标签,因此需要满足这些需求。

想改进它吗?

也许一个菜单不会向下推挤下方内容的版本会很酷,展开的下拉菜单只需置于内容之上即可。也许一个可以处理大量链接(超过小屏幕所能容纳的)的版本,并带有一些滚动或分页功能。也许一个带有一些动画/过渡效果的版本。

我们在此构建的演示的一个问题是,除非你选择一个标签,否则无法关闭下拉菜单。你可以选择当前标签来关闭它而无需执行任何操作,但你不能只点击三线菜单来关闭它。这是因为你无法(据我所知)将点击事件绑定到伪元素上。在 CodePen 上,我只是为三线菜单使用了<span>,以便你可以这样切换它,但这确实意味着需要额外的标记。

演示

注意:“转到 Google →” 是一个链接标签,仅用于测试。它在 CodePen 上由于沙盒 iframe 的原因而无法工作。在正常情况下,它可以工作。

查看 Chris Coyier 在 CodePen 上的笔 变形标签 (@chriscoyier)