以下文章由 James Nowland 撰写,他是 Headjam 的前端开发人员,Headjam 是一家位于澳大利亚纽卡斯尔的创意机构。James 在这里创建了一个相当简单的效果,但您可能会认为它需要一点 JavaScript。相反,它使用了一些巧妙的选择器用法。
在这篇文章中,我将介绍使用 兄弟选择器 和 伪元素 来创建仅使用 CSS 的菜单指示器(通常使用 JavaScript 实现)的创造性方法。
以下是我们将要制作的内容
查看 CodePen 上 CSS-Tricks (@css-tricks) 编写的 步骤 3。
我们将将其分解为三个步骤
- 基本结构和样式
- 构建指示器
- 使指示器移动
我们还将在整个示例中利用 SCSS,以利用 Sass 提供的变量和函数,从而使长期维护变得更加容易。
步骤 1:基本结构和样式
首先,让我们使用基本的无序列表结构设置菜单的 HTML。我们还可以标记基本类名称以开始。
<ul class="PrimaryNav">
<li class="Nav-item">Home</li>
<li class="Nav-item">About</li>
<li class="Nav-item is-active">Writing</li>
<li class="Nav-item">Clients</li>
<li class="Nav-item">Contact</li>
</ul>
到目前为止,没什么特别的。我们有带有 <ul>
元素的 PrimaryNav
类名称,它充当其内部列表项的容器,每个列表项都有一个 Nav-item
类。
定义变量
此导航的关键功能之一是最大宽度,该宽度根据其中的菜单项数量填充容器的空间。在这种情况下,我们将在 SCSS 中设置一个 $menu-items
变量,然后将其用于计算标记中每个 .Nav-item
的 $width
值。
我们还添加了一个 $indicator-color
变量来定义(您猜对了)菜单悬停指示器将使用的颜色。
// Menu Item Variables
// The number of items in the menu
$menu-items: 5;
// We multiply it by 1% to get the correct % unit
$width: (100/$menu-items) * 1%;
// Colors
$background-color: #121212;
$indicator-color: #e82d00;
设置样式
从这里,我们可以为菜单创建基本样式
// The parent container
.PrimaryNav {
// Remove the bullet points by default
list-style: none;
// Center all the things!
margin: 50px auto;
// The nav will never exceed this width and what our calculated percentages related back to
max-width: 720px;
padding: 0;
width: 100%;
}
// The menu items
.Nav-item {
background: #fff;
display: block;
float: left;
margin: 0;
padding: 0;
text-align: center;
// Our current calculation of 5 items will generate 20%
width: $width;
// The first item in the menu
&:first-child {
border-radius: 3px 0 0 3px;
}
// The last item in the menu
&:last-child {
border-radius: 0 3px 3px 0;
}
// If the menu item is active, give it the same color as the indicator
&.is-active a {
color: $indicator-color;
}
a {
color: $background-color;
display: block;
padding-top: 20px;
padding-bottom: 20px;
text-decoration: none;
&:hover {
color: $indicator-color;
}
}
}
查看 CodePen 上 CSS-Tricks (@css-tricks) 编写的 步骤 1。
步骤 2:构建指示器
我们将以一种使用多个类的方式来标记它。我们可以仅使用 .PrimaryNav
类来完成相同的事情,但添加另一个类名称将在将来提供更大的灵活性。
我们已经拥有包含主要导航样式的 .PrimaryNav
类。现在让我们创建 .with-indicator
来构建指示器
<ul class="PrimaryNav with-indicator">
</ul>
在这里,我们可以使用 CSS 来代替通常在 JavaScript 中完成的操作。我们知道,在悬停时向元素添加类属于 JavaScript 的领域,但让我们看看如何在 CSS 中单独完成此操作。
棘手的部分是让菜单项相互通信。在无序列表中,第一个列表项 (:first-child
) 可以通过兄弟选择器 +
或 ~
与第二个子项通信,但第二个子项列表项无法与第一个子项通信(在 CSS 中无法像那样在 DOM 中向后移动)。
查看 CodePen 上 CSS-Tricks (@css-tricks) 编写的 步骤 2。
事实证明,列表项中最好的监听器是 :last-child
。最后一个子项可以听到其所有兄弟姐妹的所有:hover
和 :active
状态。这使其成为设置指示器的完美候选者。
我们使用最后一个子项的 :before
和 :after
元素创建红色指示器。:before
元素将使用 CSS 三角形 和 负边距将其居中。
// The hover indicator
.with-indicator {
// The menu is "relative" to the absolute position last-child pseudo elements.
position: relative;
.Nav-item:last-child {
&:before, &:after {
content: '';
display: block;
position: absolute;
}
// The CSS Triangle
&:before {
width: 0;
height: 0;
border: 6px solid transparent;
border-top-color: $color-indicator;
top: 0;
left: 12.5%;
// Fix the offset - may vary per use
margin-left: -3px;
}
// The block that sits behind the text
&:after {
width: $width;
background: $indicator-color;
top: -6px;
bottom: -6px;
left: 0;
z-index: -1;
}
}
}
步骤 3:使指示器移动
现在指示器已设置好,需要在光标悬停在菜单项上时能够四处移动。见证 ~
选择器的强大功能,它将用于匹配标记中第一个和最后一个子项之间的任何元素。
现在,默认情况下在 <ul>
元素上设置了 position:relative
,这意味着指示器与第一个项目齐平。我们可以通过修改 left
位置将指示器从一个项目移动到另一个项目,并且由于所有菜单的宽度都相等,因此我们知道要将其向下移动一个位置,:last-child
选择器对于 :before
和 :after
必须具有等于 .Nav-item
宽度 的偏移量。还记得我们方便的 $width
变量吗?我们可以在 left
属性上使用它。
这是我们在原生 CSS 中设置它的方式
.with-indicator .Nav-item:nth-child(1).is-active ~ .Nav-item:last-child:after {
left: 0;
}
.with-indicator .Nav-item:nth-child(2).is-active ~ .Nav-item:last-child:after {
left: 20%;
}
.with-indicator .Nav-item:nth-child(3).is-active ~ .Nav-item:last-child:after {
left: 40%;
}
.with-indicator .Nav-item:nth-child(4).is-active:after {
left: 60%;
}
.with-indicator .Nav-item:nth-child(5).is-active:after {
left: 80%;
}
让我们使用 Sass 使其动态化
// Menu Item Variables
// The number of items in the menu, plus one for offset
$menu-items: 5;
// The actual number of items in the menu
$menu-items-loop-offset: $menu-items - 1;
// We multiply it by 1% to get the correct % unit
$width: (100/$menu-items) * 1%;
.with-indicator {
@for $i from 1 through $menu-items-loop-offset {
// When the .Nav-item is active, make the indicator line up with the navigation item.
.Nav-item:nth-child(#{$i}).is-active ~ .Nav-item:last-child:after {
left:($width*$i)-$width;
}
.Nav-item:nth-child(#{$i}).is-active ~ .Nav-item:last-child:before {
left:($width*$i)+($width/2)-$width; /* this ensures the triangle lines up to the menu. */
}
} // end @for loop
值得注意的是,三角形 :before
在此 left
偏移量的基础上还有额外的半宽偏移量。
现在让我们添加一些动画和另一个 Sass for
循环,以便我们可以根据我们所在的页面初始化指示器的位置。当您将鼠标悬停在项目上时,指示器将移动。但是,一旦您移出鼠标,它将返回到 is-active
状态。这是一种简洁且无需 JavaScript 的创建菜单指示器的方法。
// We had to use !important to make the hovers overide for when the :last-child is-active or hovered
@for $i from 1 through $menu-items-loop-offset {
// When the menu is :hover make the indicator line up with it.
.Nav-item:nth-child(#{$i}):hover ~ .Nav-item:last-child:after {
left:($width*$i)-$width !important;
}
.Nav-item:nth-child(#{$i}):hover ~ .Nav-item:last-child:before{
left:($width*$i)+($width/2)-$width !important;
}
} // end @for loop
// make sure the last-child talks to itself
.Nav-item {
&:last-child {
&:hover, &.is-active {
&:before {
left: (100%-$width)+($width/2) !important;
}
&:after{
left: 100%-$width !important;
}
}
}
}
最终结果
就是这样!一个无需 JavaScript 依赖的动画菜单指示器。
查看 CodePen 上 CSS-Tricks (@css-tricks) 编写的 步骤 3。
非常酷的效果;以及 Sass 如何使用的良好示例。
我之前做过 类似的事情,但它不如你的好,并且使用了一个糟糕的非语义元素来创建指示器,所以我更喜欢你的方法 :)
干得好,写得也很好。
展示了如何有效地使用 Sass,无论如何它似乎是 CSS-Tricks 上的一篇客座文章
way2sms
我试了一下,并且
...
&:after {
...
bottom :-6px;
...
}
...
没有按计划工作,只有当我将其设置为 -66px 时才有效。
不知道为什么?(在 Chrome 和 Firefox 中尝试过)
我认为缺少 clearfix
谢谢,Dave,这里没有显示全局重置,这就是它无法正常工作的原因。
这在我的 iOS 9 iPad 上最新的 Chrome 中不起作用。我每次都必须返回“联系”选项卡,然后点击其他选项卡才能看到过渡。
有什么想法可以使其适用于不同的菜单项宽度吗?
如演示中所做的那样,使用固定百分比来执行此操作要容易得多,并且它更具响应性。
如果您**知道**所有链接项的确切宽度,则理论上可以像这样硬编码宽度
但是,如果您像那样硬编码很多项目,那么您将对任何更新感到恐惧。
如果导航项的宽度有很多变化且未知,我个人会考虑使用 JavaScript。
很漂亮,但是如果你使用 .is-active 指示器类,你必须使用一点 JavaScript 来添加该类(或 :active CSS 选择器),不是吗?有什么魔法吗?谢谢
is-active
类可以在页面加载时由服务器设置。由于此演示没有重新加载页面,因此不太明显。在 WordPress 中有几种方法可以完成此操作,但一种简单的方法是使用 is_page,如下所示。
或者,如果您使用其他 CMS(如 Craft),您可以执行以下操作
甚至可以 创建一个宏
希望这有帮助!
非常棒的技巧,但是当
.is-active
类位于最后一个元素上时,它似乎无法正确动画。我想不出任何解决方法,这可能只是我的电脑问题…?我使用此帖子中的原始示例进行了测试(链接),但 HTML 代码如下尽管如此,我还是喜欢这篇文章 :-)
好的,最后一个元素无法正确动画的问题是由于
:last- child
的特异性不如兄弟选择器。一种解决方法是在选择器上使用
!important
。这是一个 包含此工作原理的更新示例我希望找到一种方法来解决此问题,而不必诉诸
!important
这种最后手段。我已经要求 Chris 对文章进行调整,加入这些更改 :)
您是我的新神!
垂直示例:http://cssmojo.com/menus-with-a-sliding-marker/
使用 :hover 是唯一一种仅限于 CSS 的方式,在我看来,对于悬停效果来说它有点太笨重了。它在触摸设备上不会物有所值,并且可能会突出由于各种原因而可能存在的卡顿现象。
无论如何,如果您计划创建的不仅仅是一个简单的静态网站,或者如果选项卡/导航是动态的而不是垂直的,则应谨慎使用它。
此效果最适合用作动画活动选项卡/导航指示器,结合任何开箱即用的选项卡控件的 .active 类使用相同的技术。如果您知道导航栏不会更改,那么对于 SPA 或与滚动侦听器结合使用的登录页面来说,这是一个非常好的效果。
此技术(如果为水平方向)的一个警告是所有列表项都必须大小相同。如果您计划创建一个选项卡可能会换行的动态网站,这会使其不那么简单,并破坏转换计算……这只会增加您不想维护的复杂性。
Polymer 具有允许可变长度和选项卡滚动的元素,但这表明用稳健的方式自己实现它在技术上有多么复杂
https://elements.polymer-project.org/elements/paper-tabs?view=demo:demo/index.html&active=paper-tabs
100% 同意!它更像是一个 CSS 技巧……看看我做了什么:|
很酷的东西!不过有一点:如果我将鼠标悬停在指示器上(只是红色的线,上面或下面的线),它会跳到最后一个子元素(Opera、Chrome、Firefox、IE11)。
谢谢 Andrei,我没有注意到这一点,但是如果你使指示器变大,它就会变得非常明显。
这可能是因为指示器上的
:hover
实际上位于last-child
的一部分上。Chris 做了一个视频,讲解了 有意使用此行为。由于我们不希望出现这种行为——如果在这种情况下我们使用 pointer-events
pointer-events: none;
在构成指示器的:before
和:after
上,它将停止在 受支持的浏览器 中发生。很酷的想法,可以付诸实践。我只是想反馈一些我最近发现的东西,以便稍微清理一下你的 SASS。
可以是
文档参考:https://sass-lang.com.cn/documentation/Sass/Script/Functions.html#percentage-instance_method
实际上,你不需要做任何戏剧性的操作,可以用以下方法轻松实现