使用 CSS 文本阴影创建酷炫的悬停效果

Avatar of Temani Afif
Temani Afif

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

在我的 上一篇文章 中,我们了解了如何使用 CSS background 属性创建酷炫的悬停效果。这一次,我们将重点关注 CSS text-shadow 属性,以探索更多有趣的效果。您可能想知道,在文本中添加阴影如何才能产生酷炫的效果,但关键在于:我们实际上不会为这些文本悬停效果创建任何阴影。

酷炫的悬停效果系列

  1. 使用背景属性创建酷炫的悬停效果
  2. 使用 CSS 文本阴影创建酷炫的悬停效果(您就在这里!
  3. 使用背景裁剪、蒙版和 3D 创建酷炫的悬停效果

text-shadow 但没有文本阴影?

让我通过展示以下演示中我们将构建的悬停效果来消除您的困惑。

在不查看代码的情况下,你们中的许多人会直观地认为,对于每个悬停效果,我们都在复制文本,然后独立地对其进行动画处理。现在,如果您查看代码,您会发现 HTML 中没有实际复制任何文本。您注意到 CSS 中没有使用 content: "text" 吗?

文本层完全由 text-shadow 创建!

悬停效果 #1

让我们仔细分析一下 CSS。

.hover-1 {
  line-height: 1.2em;
  color: #0000;
  text-shadow: 
    0 0 #000, 
    0 1.2em #1095c1;
  overflow: hidden;
  transition: .3s;
}
.hover-1:hover {
  text-shadow: 
    0 -1.2em #000, 
    0 0 #1095c1;
}

首先要注意的是,我将实际文本的颜色设置为透明(使用 #0000)以隐藏它。之后,我使用 text-shadow 创建了两个阴影,其中我为每个阴影仅定义了两个长度值。这意味着没有模糊半径,从而形成了一个清晰锐利的阴影,有效地以指定颜色复制了文本。

这就是我为什么在引言中声称这里没有阴影的原因。我们所做的是比“经典”阴影更简单的文本复制方法。

Diagram of the start and end of the hover effect.

我们有两个文本层,我们在悬停时移动它们。如果我们隐藏溢出,则复制的文本将不可见,而移动则使它看起来像是实际文本被其他文本替换了。这是本文所有示例都起作用的主要技巧。

让我们优化我们的代码。我经常使用值 1.2em 来定义阴影的高度和偏移量,使其成为 CSS 自定义属性(我们将其称为 --h)的理想候选者。

.hover-1 {
  --h: 1.2em;

  line-height: var(--h);
  color: #0000;
  text-shadow: 
    0 0 #000, 
    0 var(--h) #1095c1;
  overflow: hidden;
  transition: .3s;
}
.hover-1:hover {
  text-shadow: 
    0 calc(-1 * var(--h)) #000, 
    0 0 #1095c1;
}

我们还可以更进一步,应用更多 calc() 计算,将代码简化为只使用一次 text-shadow。(我们在 上一篇文章 中也这样做过。)

.hover-1 {
  --h: 1.2em;   

  line-height: var(--h);
  color: #0000;
  text-shadow: 
    0 calc(-1*var(--_t, 0em)) #000, 
    0 calc(var(--h) - var(--_t, 0em)) #1095c1;
  overflow: hidden;
  transition: .3s;
}
.hover-1:hover {
  --_t: var(--h);
}

如果您想知道为什么我在 --_t 变量中添加了下划线,这只是我使用的一种命名约定,用于区分我们用来控制用户可以更新的效果的变量(例如 --h)和仅用于优化目的的内部变量(例如 --_t),我们不需要更改(例如 --_t)。换句话说,下划线是变量名称的一部分,没有特殊含义。

我们还可以更新代码以获得相反的效果,即复制的文本从顶部滑入。

我们只对 text-shadow 属性进行了一些小的更新——我们没有触碰其他任何内容!

悬停效果 #2

对于这个效果,我们将对两个属性进行动画处理:text-shadowbackground。关于 text-shadow,我们仍然有两个层,就像之前的示例一样,但这一次,我们将只移动其中一个层,同时在交换过程中将另一个层的颜色更改为透明。

.hover-2 {
  /* the height */
  --h: 1.2em;

  line-height: var(--h);
  color: #0000;
  text-shadow: 
    0 var(--_t, var(--h)) #fff,
    0 0 var(--_c, #000);
  transition: 0.3s;
}
.hover-2:hover {
  --_t: 0;
  --_c: #0000;
}

悬停时,我们将白色文本层移到顶部,同时将另一个层的颜色更改为透明。为此,我们添加了对渐变应用的 background-size 动画。

最后,我们添加 overflow: hidden 以使动画仅在元素边界内可见。

.hover-2 {
  /* the height */
  --h: 1.2em;

  line-height: var(--h);
  color: #0000;
  text-shadow: 
    0 var(--_t,var(--h)) #fff,
    0 0 var(--_c, #000);
  background: 
    linear-gradient(#1095c1 0 0) 
    bottom/100% var(--_d, 0) no-repeat;
  overflow: hidden;
  transition: 0.3s;
}
.hover-2:hover {
  --_d: 100%;
  --_t: 0;
  --_c: #0000;
}

我们在这里所做的是将 CSS text-shadowbackground 属性结合起来,以创建一个酷炫的悬停效果。此外,我们能够使用 CSS 变量来优化代码。

如果您觉得 background 语法很奇怪,我强烈建议您阅读 我之前的文章。下一个悬停效果也依赖于我在那篇文章中详细介绍的动画。如果您不熟悉 CSS background 的技巧,建议您阅读那篇文章,然后再继续阅读本文,以便获得更多上下文。

在上一篇文章中,您向我们展示了如何只使用一个变量来创建悬停效果——我们可以在此处这样做吗?

当然可以!我们确实可以应用 相同的 DRY 切换技术,以便我们只使用一个 CSS 自定义属性,该属性仅在悬停时切换值。

.hover-2 {
  /* the height */
  --h: 1.2em;

  line-height: var(--h);
  color: #0000;
  text-shadow: 
    0 var(--_i, var(--h)) #fff,
    0 0 rgb(0 0 0 / calc(var(--_i, 1) * 100%) );
  background: 
    linear-gradient(#1095c1 0 0) 
    bottom/100% calc(100% - var(--_i, 1) * 100%) no-repeat;
  overflow: hidden;
  transition: 0.3s;
}
.hover-2:hover {
  --_i: 0;
}

悬停效果 #3

此悬停效果不过是我们将两个效果结合起来的结果:第二个 悬停效果来自上一篇文章,第一个悬停效果来自本文。

.hover-3 {
  /* the color  */
  --c: #1095c1;
  /* the height */
  --h: 1.2em;

  /* The first hover effect in this article */
  line-height: var(--h);  
  color: #0000;
  overflow: hidden;
  text-shadow: 
    0 calc(-1 * var(--_t, 0em)) var(--c), 
    0 calc(var(--h) - var(--_t, 0em)) #fff;
  /* The second hover effect from the previous article */
  background: 
    linear-gradient(var(--c) 0 0) no-repeat 
    calc(200% - var(--_p, 0%)) 100% / 200% var(--_p, .08em);
  transition: .3s var(--_s, 0s), background-position .3s calc(.3s - var(--_s, 0s));
}
.hover-3:hover {
  --_t: var(--h);
  --_p: 100%;
  --_s: .3s
}

我所做的就是从其他示例中复制和粘贴效果,并对变量名称进行了一些小的调整。当它们组合在一起时,它们形成了一个整洁的悬停效果!乍一看,这种效果可能看起来很复杂且难以实现,但最终,它仅仅是将两个相对简单的效果合二为一。

如果我们考虑已经完成的先前优化,使用 DRY 切换变量技术优化代码也应该是一件轻松的事情。

.hover-3 {
  /* the color  */
  --c: #1095c1;
  /* the height */
  --h: 1.2em;

  line-height: var(--h);  
  color: #0000;
  overflow: hidden;
  text-shadow: 
    0 calc(-1 * var(--h) * var(--_i, 0)) var(--c), 
    0 calc(var(--h) * (1 - var(--_i, 0))) #fff;
  background: 
    linear-gradient(var(--c) 0 0) no-repeat
    calc(200% - var(--_i, 0) * 100%) 100% / 200% calc(100% * var(--_i, 0) + .08em);
  transition: .3s calc(var(--_i, 0) * .3s), background-position .3s calc(.3s - calc(var(--_i, 0) * .3s));
}
.hover-3:hover {
  --_i: 1;
}

悬停效果 #4

此悬停效果是对第二个效果的改进。首先,让我们引入一个 clip-path 动画,以便在文本层移动之前显示它。

以下是另一个插图,以便更好地理解正在发生的事情。

Diagram of the start and end of the text hover.

最初,我们使用 inset(0 0 0 0),它类似于 overflow: hidden,因为我们只看到实际文本。悬停时,我们使用与高度相等的负值更新第三个值(表示底部偏移量),以显示放置在底部的文本层。

从那里,我们可以将其添加到本文中创建的第二个悬停效果中,这就是我们得到的结果。

我们越来越接近了!请注意,我们需要先运行 clip-path 动画,然后再运行其他所有操作。为此,我们可以为悬停时除 clip-path 之外的所有属性添加延迟。

transition: 0.4s 0.4s, clip-path 0.4s;

鼠标移出时,我们执行相反的操作。

transition: 0.4s, clip-path 0.4s 0.4s;

最后,我们添加一个 box-shadow 以创建蓝色矩形的滑动效果。不幸的是,background 无法产生这种效果,因为默认情况下,背景被裁剪到内容区域。而 box-shadow 可以超出内容区域。

.hover-4 {
  /* the color  */
  --c: #1095c1;
  /* the height */
  --h: 1.2em;
  
  line-height: var(--h);
  color: #0000;
  text-shadow: 
    0 var(--_t, var(--h)) #fff,
    0 0 var(--_c, #000);
  box-shadow: 0 var(--_t, var(--h)) var(--c);
  clip-path: inset(0 0 0 0);
  background: linear-gradient(var(--c) 0 0) 0 var(--_t, var(--h)) no-repeat;
  transition: 0.4s, clip-path 0.4s 0.4s;
}
.hover-4:hover {
  --_t: 0;
  --_c: #0000;
  clip-path: inset(0 0 calc(-1 * var(--h)) 0);
  transition: 0.4s 0.4s, clip-path 0.4s;
}

如果您仔细观察 box-shadow,您会发现它与 text-shadow 内的白色文本层的取值相同。这是合乎逻辑的,因为两者都需要以相同的方式移动。两者都会滑到顶部。然后 box-shadow 位于元素后面,而 text-shadow 最终位于顶部。

以下是一个演示,其中一些值已修改,以可视化层如何移动。

等等,背景语法与第二个悬停效果中使用的语法略有不同!

您观察得很仔细!是的,我们使用了一种不同的 background 技术来产生相同的效果。我们不是将大小从 0% 动画到 100%,而是对 position 进行动画处理。

如果我们没有为渐变指定大小,则它默认情况下将占用整个宽度和高度。由于我们知道元素的高度(--h),因此我们可以通过将位置从 0 var(--h) 更新到 0 0 来创建滑动效果。

.hover-4 {
  /* ... */
  background: linear-gradient(var(--c) 0 0) 0 var(--_t, var(--h)) no-repeat;
}
.hover-4:hover {
  --_t: 0;
}

我们本可以使用 background-size 动画来获得相同的效果,但我们只是在技巧列表中添加了另一个技巧!

在演示中,您还使用了 inset(0 0 1px 0)……为什么?

有时我会在此处添加或删除几个像素或百分比,以完善看起来不合适的地方。在本例中,在底部出现了一条不好的线,添加 1px 解决了这个问题。

DRY 开关变量优化怎么样?

这个任务留给你了!在完成这四个悬停效果和之前的文章后,你应该能够更新代码,使其只使用一个变量。我很想在评论区看到你的尝试!

轮到你了!

让我分享最后一个悬停效果,它是之前效果的另一种版本。你能在不看代码的情况下找出它是如何实现的吗?这是一个很好的练习,不要作弊!

总结

我们查看了一系列示例,这些示例展示了如何使用一个元素和几行 CSS 代码来在文本元素上创建一些看起来很复杂的悬停效果 - 不需要伪元素!我们甚至可以组合技术,用少量努力来实现更复杂的动画。

如果您有兴趣深入了解本文中的四个 text-shadow 悬停效果,请查看我收集的 500 个悬停效果,我正在探索各种不同的技术。