如果您从未遇到过幽灵按钮 👻,我会感到惊讶。 您知道的是:它们有一个透明的背景,在悬停时会填充为实色。 Smashing Magazine 有一篇关于此想法的 整篇文章。 在本文中,我们将构建一个幽灵按钮,但这将是最简单的部分。 有趣且棘手的部分将是为该幽灵按钮的填充设置动画,使得背景从光标悬停的方向填充。
这是一个幽灵按钮的基本入门
查看 CodePen 上的
基本幽灵按钮 👻 by Jhey (@jh3y)
在 CodePen 上。
在大多数情况下,background-color
具有对实色的 transition
。 有些设计中,按钮可能从左到右、从上到下填充,等等,以获得一些视觉效果。 例如,这里是从左到右
查看 CodePen 上的
方向填充幽灵按钮 👻 by Jhey (@jh3y)
在 CodePen 上。
这里有一个 UX 的挑剔之处。 如果您在填充方向上悬停,它会感觉怪怪的。 考虑这个例子。 按钮从左侧填充,而您从右侧悬停。

如果按钮从我们最初悬停的点填充会更好。

那么,我们如何才能让按钮具有方向感知? 您最初的直觉可能是求助于 JavaScript 解决方案,但我们可以使用 CSS 和一些额外的标记来创建一些东西。
对于那些在 TL;DR 阵营中的人来说,这里有一些带有方向感知的纯 CSS 幽灵按钮!
查看 CodePen 上的
带有方向感知的纯 CSS 幽灵按钮 ✨👻😎 by Jhey (@jh3y)
在 CodePen 上。
让我们一步一步地构建它。 所有代码都在 此 CodePen 集合 中可用。
创建基础
让我们从创建幽灵按钮的基础开始。 标记很简单。
<button>Boo!</button>
我们的 CSS 实现将利用 CSS 自定义属性。 这些使维护更容易。 它们也简化了通过内联属性进行自定义。
button {
--borderWidth: 5;
--boxShadowDepth: 8;
--buttonColor: #f00;
--fontSize: 3;
--horizontalPadding: 16;
--verticalPadding: 8;
background: transparent;
border: calc(var(--borderWidth) * 1px) solid var(--buttonColor);
box-shadow: calc(var(--boxShadowDepth) * 1px) calc(var(--boxShadowDepth) * 1px) 0 #888;
color: var(--buttonColor);
cursor: pointer;
font-size: calc(var(--fontSize) * 1rem);
font-weight: bold;
outline: transparent;
padding: calc(var(--verticalPadding) * 1px) calc(var(--horizontalPadding) * 1px);
transition: box-shadow 0.15s ease;
}
button:hover {
box-shadow: calc(var(--boxShadowDepth) / 2 * 1px) calc(var(--boxShadowDepth) / 2 * 1px) 0 #888;
}
button:active {
box-shadow: 0 0 0 #888;
}
将所有这些放在一起,我们就得到了以下结果
查看 CodePen 上的
幽灵按钮基础 👻 by Jhey (@jh3y)
在 CodePen 上。
太棒了! 我们有一个按钮和一个悬停效果,但没有填充。 让我们在下一步中完成它。
添加填充
为此,我们创建元素来显示幽灵按钮的填充状态。 诀窍是使用 clip-path
剪切这些元素并将它们隐藏起来。 当我们悬停在按钮上时,我们可以通过转换 clip-path
来显示它们。

它们必须与父按钮对齐。 我们的 CSS 变量在这里将大有帮助。
乍一看,我们可以使用伪元素。 但是,对于每个方向来说,伪元素都不够。 它们还会影响可访问性……但这将在后面详细介绍。
让我们从在悬停时添加一个基本的从左到右的填充开始。 首先,让我们添加一个 span。 该 span 需要与按钮相同的文本内容。
<button>Boo!
<span>Boo!</span>
</button>
现在我们需要将 span 与按钮对齐。 我们的 CSS 变量将在这里发挥重要作用。
button span {
background: var(--buttonColor);
border: calc(var(--borderWidth) * 1px) solid var(--buttonColor);
bottom: calc(var(--borderWidth) * -1px);
color: var(--bg, #fafafa);
left: calc(var(--borderWidth) * -1px);
padding: calc(var(--verticalPadding) * 1px) calc(var(--horizontalPadding) * 1px);
position: absolute;
right: calc(var(--borderWidth) * -1px);
top: calc(var(--borderWidth) * -1px);
}
最后,我们将 span 剪切到视图之外,并添加一条规则,通过更新剪切来在悬停时显示它。 定义一个过渡将使其更加完美。
button span {
--clip: inset(0 100% 0 0);
-webkit-clip-path: var(--clip);
clip-path: var(--clip);
transition: clip-path 0.25s ease, -webkit-clip-path 0.25s ease;
// ...Remaining div styles
}
button:hover span {
--clip: inset(0 0 0 0);
}
查看 CodePen 上的
具有 LTR 填充的幽灵按钮 👻 by Jhey (@jh3y)
在 CodePen 上。
添加方向感知
那么,我们如何才能添加方向感知? 我们需要四个元素。 每个元素负责检测悬停入口点。 使用 clip-path
,我们可以将按钮区域分成四个部分。

:hover
部分让我们将四个 span 添加到按钮并定位它们以填充按钮。
<button>
Boo!
<span></span>
<span></span>
<span></span>
<span></span>
</button>
button span {
background: var(--bg);
bottom: calc(var(--borderWidth) * -1px);
-webkit-clip-path: var(--clip);
clip-path: var(--clip);
left: calc(var(--borderWidth) * -1px);
opacity: 0.5;
position: absolute;
right: calc(var(--borderWidth) * -1px);
top: calc(var(--borderWidth) * -1px);
z-index: 1;
}
我们可以定位每个元素,并使用 CSS 变量为其分配剪切和颜色。
button span:nth-of-type(1) {
--bg: #00f;
--clip: polygon(0 0, 100% 0, 50% 50%, 50% 50%);
}
button span:nth-of-type(2) {
--bg: #f00;
--clip: polygon(100% 0, 100% 100%, 50% 50%);
}
button span:nth-of-type(3) {
--bg: #008000;
--clip: polygon(0 100%, 100% 100%, 50% 50%);
}
button span:nth-of-type(4) {
--bg: #800080;
--clip: polygon(0 0, 0 100%, 50% 50%);
}
酷。 为了测试这一点,让我们在悬停时更改不透明度。
button span:nth-of-type(1):hover,
button span:nth-of-type(2):hover,
button span:nth-of-type(3):hover,
button span:nth-of-type(4):hover {
opacity: 1;
}

哦,不。 这里有一个问题。 如果我们进入并悬停在一个部分,然后悬停在另一个部分,填充方向会改变。 那样看起来会很奇怪。 为了解决这个问题,我们可以设置 z-index
和 clip-path
以便在悬停时,一个部分会填满空间。
button span:nth-of-type(1):hover,
button span:nth-of-type(2):hover,
button span:nth-of-type(3):hover,
button span:nth-of-type(4):hover {
--clip: polygon(0 0, 100% 0, 100% 100%, 0 100%);
opacity: 1;
z-index: 2;
}
查看 CodePen 上的
使用 clip-path 的纯 CSS 方向感知 👻 by Jhey (@jh3y)
在 CodePen 上。
将所有内容放在一起
我们知道如何创建填充动画,也知道如何检测方向。 我们如何才能将两者结合在一起? 使用 兄弟组合器!
这样做意味着当我们悬停在一个方向部分时,我们可以显示特定的填充元素。
首先,让我们更新标记。
<button>
Boo!
<span></span>
<span></span>
<span></span>
<span></span>
<b>Boo!</b>
<b>Boo!</b>
<b>Boo!</b>
<b>Boo!</b>
</button>
现在,我们可以更新 CSS。 参照我们从左到右的填充,我们可以重用样式。 我们只需要为每个元素设置特定的 clip-path
。 我对排序的方式与一些属性值的排序方式相同。 第一个子元素是顶部,第二个是右侧,依此类推。
button b:nth-of-type(1) {
--clip: inset(0 0 100% 0);
}
button b:nth-of-type(2) {
--clip: inset(0 0 0 100%);
}
button b:nth-of-type(3) {
--clip: inset(100% 0 0 0);
}
button b:nth-of-type(4) {
--clip: inset(0 100% 0 0);
}
最后一步是当悬停在配对部分时,更新相关元素的 clip-path
。
button span:nth-of-type(1):hover ~ b:nth-of-type(1),
button span:nth-of-type(2):hover ~ b:nth-of-type(2),
button span:nth-of-type(3):hover ~ b:nth-of-type(3),
button span:nth-of-type(4):hover ~ b:nth-of-type(4) {
--clip: inset(0 0 0 0);
}
瞧! 我们得到了一个带有方向感知的纯 CSS 幽灵按钮。
查看 CodePen 上的
带有方向感知的纯 CSS 幽灵按钮 👻 by Jhey (@jh3y)
在 CodePen 上。
可访问性
就其当前状态而言,该按钮不可访问。

这些额外的元素并没有太大帮助,因为屏幕阅读器会重复内容四次。 我们需要将这些元素从屏幕阅读器中隐藏起来。
<button>
Boo!
<span></span>
<span></span>
<span></span>
<span></span>
<b aria-hidden="true">Boo!</b>
<b aria-hidden="true">Boo!</b>
<b aria-hidden="true">Boo!</b>
<b aria-hidden="true">Boo!</b>
</button>
不再重复内容。
查看 CodePen 上的
带有方向感知的可访问纯 CSS 幽灵按钮 👻 by Jhey (@jh3y)
在 CodePen 上。
就是这样!
使用一些额外的标记和 CSS 技巧,我们可以创建带有方向感知的幽灵按钮。 使用预处理器或在您的应用程序中组合一个组件,您就不需要编写所有 HTML 代码。
这是一个使用内联 CSS 变量来控制按钮颜色的演示。
查看 CodePen 上的
带有方向感知的纯 CSS 幽灵按钮 ✨👻😎 by Jhey (@jh3y)
在 CodePen 上。
值得一提的是,
div
元素不是button
元素的有效子元素(尽管我认为它们不会在现代浏览器中引起任何错误)。是的,说得好! 尽管它没有引起任何错误,但我已更新了文章,以便在
button
中使用有效的内联元素。在 HTML 规范中,将 div 放入按钮中也是不允许的
是的,说得好! 我已更新了标记,以便仅在
button
中使用内联元素。我在大约 6 年前做过一个“方向感知悬停”,所以看到一个使用 clip-path 的现代版本真是太好了:)。做得好。
我的(https://codepen.io/paulobrien/details/kKaJq)有点乱,但效果很好。
我更喜欢这种方法,因为它支持的浏览器更多。不过,我想知道如果你不能保证按钮的纵横比或尺寸,效果会怎样。
很酷,但是悬停时它会向相同的方向移动,而悬停时会向相反的方向移动。
calc() 不适用于 div 的 position left top……至少对我来说,我试过了。
我必须为 div 设置一个特定的高度,然后悬停才会起作用,否则它会填满整个垂直空间。
我喜欢的是,经过这么多年,CSS 仍然能让我感到惊奇:D
为了实现一个微妙的效果,做了很多工作,但这非常聪明。我喜欢它(尽管我会犹豫是否要实现它)。
可能超出了范围,但它的合理演进要求 mouseleave 也具有方向性:)
完全同意 Thibaud :D 我相信这是可能的。我还没有尝试过解决方案。
看起来和我的笔非常相似 https://codepen.io/srekoble/pen/zQXGbZ?editors=1100,大约三个月前,为了好玩:)
哈哈,我不确定你用词是否合适。我不会说“可疑”。
不过,我还没有看到那支笔。谢谢分享!
但是按钮是填充还是清空呢?
值得一提的是之前关于方向感知悬停效果的 CSS-Tricks 文章(包括 CSS),它具有更好的支持:https://css-tricks.cn/direction-aware-hover-effects/
非常有创意的想法,感谢你的时间和努力!