带有方向感知的 CSS 幽灵按钮

Avatar of Jhey Tompkins
Jhey Tompkins

DigitalOcean 提供适用于您旅程各个阶段的云产品。 立即开始使用 $200 的免费积分!

如果您从未遇到过幽灵按钮 👻,我会感到惊讶。 您知道的是:它们有一个透明的背景,在悬停时会填充为实色。 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 来显示它们。

具有 50% 剪切的子元素

它们必须与父按钮对齐。 我们的 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-indexclip-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 上。

可访问性

就其当前状态而言,该按钮不可访问。

额外的标记会被 VoiceOver 读取。

这些额外的元素并没有太大帮助,因为屏幕阅读器会重复内容四次。 我们需要将这些元素从屏幕阅读器中隐藏起来。

<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 上。