不久前,Geoff写了一篇关于一个很酷的悬停效果的文章。这个效果依靠的是CSS伪元素、变换和过渡的组合。很多评论表明,同样的效果也可以用背景属性来完成。Geoff提到这是他最初的想法,这也是我的想法。我并不是说他登陆的伪元素不好,但知道用不同的方法来实现同样的效果只能是一件好事。
在这篇文章中,我们将重新制作那个悬停效果,同时也将它扩展到其他类型的悬停效果,只使用CSSbackground 属性。
CodePen 嵌入回退
你可以在该演示中看到background 属性的作用,以及我们如何使用自定义属性和calc() 函数来做更多事情。我们将学习如何将所有这些结合起来,这样我们就能得到很好的优化代码了
悬停效果#1
CodePen 嵌入回退
让我们从第一个效果开始,这是Geoff在他的文章中详细介绍的一个效果的再现。用来实现这种效果的代码如下。
.hover-1 {
background: linear-gradient(#1095c1 0 0) var(--p, 0) / var(--p, 0) no-repeat;
transition: .4s, background-position 0s;
}
.hover-1:hover {
--p: 100%;
color: #fff;
}
如果我们省略颜色过渡(这是可选的),我们只需要三个CSS声明来实现这个效果。你可能会惊讶于代码之小,但你会看到我们是如何达到的。
首先,让我们从一个简单的background-size 过渡开始。
CodePen嵌入回退
我们正在使一个线性渐变的大小从0 100% 到100% 100% 。这意味着宽度从0 到100% ,而背景本身保持在全高。到目前为止并不复杂。
让我们开始我们的优化工作。我们首先将我们的梯度转换为只使用一次颜色。
background-image: linear-gradient(#1095c1 0 0);
这个语法可能看起来有点奇怪,但我们是在告诉浏览器,一种颜色应用于两个色块,这就足以在CSS中定义一个渐变。两个色块都是0 ,所以浏览器会自动将最后一个色块变成100% ,并以相同的颜色填充我们的梯度。捷径,太棒了
使用background-size ,我们可以省略高度,因为梯度默认为全高。我们可以做一个从background-size: 0 到background-size: 100% 的过渡。
.hover-1 {
background-image: linear-gradient(#1095c1 0 0);
background-size: 0;
background-repeat: no-repeat;
transition: .4s;
}
.hover-1:hover {
background-size: 100%;
}
让我们引入一个自定义属性,以避免background-size 的重复性。
.hover-1 {
background-image: linear-gradient(#1095c1 0 0);
background-size: var(--p, 0%);
background-repeat: no-repeat;
transition: .4s;
}
.hover-1:hover {
--p: 100%;
}
我们最初没有定义--p ,所以将使用回退值(在我们的例子中是0% )。在悬停时,我们定义一个值来取代后退值(100% )。
现在,让我们用速记版本来组合所有的背景属性,得到。
.hover-1 {
background: linear-gradient(#1095c1 0 0) left / var(--p, 0%) no-repeat;
transition: .4s;
}
.hover-1:hover {
--p: 100%;
}
我们越来越接近了!请注意,我引入了一个left 的值(为background-position ),这是在background 速记中定义大小时必须的。另外,无论如何我们需要它来实现我们的悬停效果。
我们还需要更新悬停时的位置。我们可以分两步来做。
- 在鼠标悬停时从右侧增加尺寸。
- 鼠标移出时从左边减少尺寸。
要做到这一点,我们也需要在悬停时更新background-position 。
CodePen嵌入回退
我们在我们的代码中添加了两件事。
- 悬停时的
background-position值为right - 一个
transition-duration,在0s。background-position
这意味着,在悬停时,我们立即将background-position ,从left (看,我们需要这个值!)改为right ,所以背景的大小将从右侧增加。然后,当鼠标光标离开链接时,过渡就会反向播放,从right 到left ,使人觉得我们在从左边减少背景的大小。我们的悬停效果就完成了!
但是你说我们只需要三个声明,而现在有四个声明。
这倒是真的,接得好。left 和right 的值可以分别改为0 0 和100% 0 ;由于我们的梯度默认已经是全高,我们可以通过使用0 和100% 。
.hover-1 {
background: linear-gradient(#1095c1 0 0) 0 / var(--p, 0%) no-repeat;
transition: .4s, background-position 0s;
}
.hover-1:hover {
--p: 100%;
background-position: 100%;
}
看到background-position 和--p 是如何使用相同的值的吗?现在我们可以把代码减少到三个声明。
.hover-1 {
background: linear-gradient(#1095c1 0 0) var(--p, 0%) / var(--p,0%) no-repeat;
transition: .4s, background-position 0s;
}
.hover-1:hover {
--p: 100%;
}
自定义属性--p 同时定义了背景位置和大小。在悬停时,它也会更新这两个值。这是一个完美的用例,显示了自定义属性如何帮助我们减少多余的代码,避免多次编写属性。我们使用自定义属性来定义我们的设置,我们只在悬停时更新后者。
但是Geoff描述的效果是做相反的事情,从左边开始,在右边结束。我们似乎不能依赖同一个变量,那我们该怎么做呢?
我们仍然可以使用一个变量并稍微更新我们的代码,以达到相反的效果。我们要做的是从100% 到0% ,而不是从0% 到100% 。我们有一个100% 的差异,我们可以用calc() 来表达,像这样。
.hover-1 {
background: linear-gradient(#1095c1 0 0) calc(100% - var(--p,0%)) / var(--p,0%) no-repeat;
transition: .4s, background-position 0s;
}
.hover-1:hover {
--p: 100%;
}
--p 将从 改为 ,但背景的位置将从 改为 ,由于 。0% 100% 100% 0% calc()
我们仍然有三个声明和一个自定义属性,但效果不同。
CodePen嵌入回退
在我们进入下一个悬停效果之前,我想强调一些重要的事情,你可能已经注意到了。当处理自定义属性时,我使用0% (有单位)而不是无单位的0 。当自定义属性单独存在时,无单位的零可能会起作用,但在我们需要明确定义单位的calc() 里面会失败。我可能需要另一篇文章来解释这个怪癖,但在处理自定义属性时,一定要记得添加单元。我在StackOverflow上有两个答案(这里和这里),其中有更多细节。
悬停效果#2
CodePen嵌入回落
对于这个效果,我们需要一个更复杂的过渡。让我们看一下一步步的图示来了解发生了什么。
最初,一个固定高度、全宽的梯度在视野之外。然后,我们将渐变向右移动以覆盖底面。最后,我们将梯度的大小从固定高度增加到100% ,以覆盖整个元素。
我们首先有一个background-position ,然后是一个background-size 的过渡。让我们把它翻译成代码。
.hover-2 {
background-image: linear-gradient(#1095c1 0 0);
background-size: 100% .08em; /* .08em is our fixed height; modify as needed. */
background-position: /* ??? */;
background-repeat: no-repeat;
transition: background-size .3s, background-position .3s .3s;
}
.hover-2:hover {
transition: background-size .3s .3s, background-position .3s;
background-size: 100% 100%;
background-position: /* ??? */;
}
注意两个过渡值的使用。在悬停时,我们需要先改变位置,然后再改变大小,这就是为什么我们要在大小上增加一个延迟。在鼠标移出时,我们做相反的事情。
现在的问题是:我们对background-position 使用什么值?我们在上面留了空白。background-size 的值是微不足道的,但background-position 的值就不是了。而且,如果我们保持实际的配置,我们就无法移动我们的梯度。
我们的梯度的宽度等于100% ,所以我们不能使用background-position 的百分比值来移动它。
与background-position 一起使用的百分比值总是很麻烦,特别是当你第一次使用它们时。它们的行为是不直观的,但如果我们得到它背后的逻辑,就可以很好地定义和理解。我想这需要另一篇文章来全面解释为什么它是这样工作的,但这里是我在Stack Overflow上发布的另一个 "长 "解释。我建议花几分钟时间来阅读这个答案,你会感谢我的。
诀窍是将宽度改为与100% 不同的东西。 让我们使用200% 。我们不担心背景会超过元素,因为无论如何溢出都是隐藏的。
.hover-2 {
background-image: linear-gradient(#1095c1 0 0);
background-size: 200% .08em;
background-position: 200% 100%;
background-repeat: no-repeat;
transition: background-size .3s, background-position .3s .3s;
}
.hover-2:hover {
transition: background-size .3s .3s, background-position .3s;
background-size: 200% 100%;
background-position: 100% 100%;
}
这就是我们得到的结果。
CodePen嵌入回退
现在是时候优化我们的代码了。如果我们采用我们从第一个悬停效果中学到的想法,我们可以使用速记属性,写更少的声明来实现这个效果。
.hover-2 {
background:
linear-gradient(#1095c1 0 0) no-repeat
var(--p, 200%) 100% / 200% var(--p, .08em);
transition: .3s var(--t, 0s), background-position .3s calc(.3s - var(--t, 0s));
}
.hover-2:hover {
--p: 100%;
--t: .3s;
}
我们使用速记版本将所有的背景属性加在一起,然后使用--p 来表达我们的值。尺寸从.08em 改为100% ,位置从200% 改为 。100%
我还使用另一个变量--t ,来优化过渡属性。在鼠标悬停时,我们将其设置为.3s ,这样就得到了这个。
transition: .3s .3s, background-position .3s 0s;
在鼠标移出时,--t 是未定义的,所以将使用回退值。
transition: .3s 0s, background-position .3s .3s;
我们不是应该在
transition中设置background-size吗?
这的确是我们可以做的另一个优化。如果我们不指定任何属性,就意味着 "所有 "的属性,所以过渡被定义为 "所有 "的属性(包括background-size 和background-position )。然后再为background-position 定义,这与为background-size ,然后为background-position 定义类似。
"类似 "与说某物 "相同 "是不同的。如果你在悬停时改变更多的属性,你会看到区别,所以最后的优化在某些情况下可能不适合。
我们还能优化代码并只使用一个自定义属性吗?
是的,我们可以!Ana Tudor分享了一篇很棒的文章,解释了如何创建DRY切换,其中一个自定义属性可以更新多个属性。在这里我就不多说了,但我们的代码可以这样修改。
.hover-2 {
background:
linear-gradient(#1095c1 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-2:hover {
--i: 1;
}
--i 自定义属性最初是未定义的,所以使用回退值,0 。不过在悬停时,我们用1 替换0 。你可以对这两种情况进行计算,得到每一种情况的数值。你可以把这个变量看作是一个 "开关",在悬停时一次更新我们所有的值。
同样,我们又回到了只有三个声明的相当酷的悬停效果了
CodePen 嵌入回退
悬停效果#3
我们将使用两个梯度而不是一个梯度来实现这个效果。我们将看到,结合多个梯度是创建花式悬停效果的另一种方式。
下面是我们正在做的事情的示意图。
我们最初有两个溢出元素的渐变,这样它们就不在视野之内了。每一个都有一个固定的高度,占据了元素宽度的一半。然后我们将它们滑入视野,使其可见。第一个渐变被放在左下方,第二个被放在右上方。最后,我们增加高度以覆盖整个元素。
下面是CSS中的效果。
.hover-3 {
background-image:
linear-gradient(#1095c1 0 0),
linear-gradient(#1095c1 0 0);
background-repeat: no-repeat;
background-size: 50% .08em;
background-position:
-100% 100%,
200% 0;
transition: background-size .3s, background-position .3s .3s;
}
.hover-3:hover {
background-size: 50% 100%;
background-position:
0 100%,
100% 0;
transition: background-size .3s .3s, background-position .3s;
}
该代码与我们所涉及的其他悬停效果几乎相同。唯一不同的是,我们有两个不同位置的渐变。位置值可能看起来很奇怪,但同样,这与CSS中background-position 属性的百分比工作方式有关,所以如果你想了解具体细节,我强烈建议你阅读我在Stack Overflow的回答。
现在让我们来优化吧你现在已经明白了--我们正在使用速记属性、自定义属性和calc() 来整理东西。
.hover-3 {
--c: no-repeat linear-gradient(#1095c1 0 0);
background:
var(--c) calc(-100% + var(--p, 0%)) 100% / 50% var(--p, .08em),
var(--c) calc( 200% - var(--p, 0%)) 0 / 50% var(--p, .08em);
transition: .3s var(--t, 0s), background-position .3s calc(.3s - var(--t, 0s));
}
.hover-3:hover {
--p: 100%;
--t: 0.3s;
}
我已经添加了一个额外的自定义属性,--c ,它定义了梯度,因为在两个地方使用的是同一个梯度。
CodePen嵌入回退
我在该演示中使用50.1% ,而不是50% ,因为它可以防止梯度之间出现间隙。出于类似的原因,我还在位置上添加了1% 。
让我们通过使用开关变量进行第二次优化。
.hover-3 {
--c: no-repeat linear-gradient(#1095c1 0 0);
background:
var(--c) calc(-100% + var(--i, 0) * 100%) 100% / 50% calc(100% * var(--i, 0) + .08em),
var(--c) calc( 200% - var(--i, 0) * 100%) 0 / 50% calc(100% * var(--i, 0) + .08em);
transition: .3s calc(var(--i, 0) * .3s), background-position .3s calc(.3s - var(--i, 0) * .3s);
}
.hover-3:hover {
--i: 1;
}
CodePen Embed Fallback
你开始看到这里的模式了吗?这并不是说我们所做的效果有多难。它更像是代码优化的 "最后一步"。我们从写有大量属性的冗长代码开始,然后按照简单的规则(如使用速记、删除默认值、避免多余的值等)进行缩减,尽可能地简化事情。
悬停效果#4
CodePen嵌入回退
我将提高这最后一个效果的难度,但你从其他例子中知道的足够多,我怀疑你对这个效果会有任何问题。
这个悬停效果依赖于两个圆锥梯度和更多的计算。
最初,我们在步骤1中有两个零尺寸的梯度。我们在步骤2中增加每一个的尺寸。我们不断增加它们的宽度,直到它们完全覆盖该元素,如步骤3所示。之后,我们将它们滑动到底部以更新它们的位置。这就是悬停效果的 "神奇 "部分。由于两个渐变都将使用相同的颜色,在步骤4中改变它们的位置将不会产生视觉上的差异--但一旦我们在步骤5中缩小鼠标移出的尺寸,我们将看到差异。
如果你比较一下步骤2和步骤5,你可以看到我们有一个不同的倾向。让我们把它翻译成代码。
.hover-4 {
background-image:
conic-gradient(/* ??? */),
conic-gradient(/* ??? */);
background-position:
0 0,
100% 0;
background-size: 0% 200%;
background-repeat: no-repeat;
transition: background-size .4s, background-position 0s;
}
.hover-4:hover {
background-size: /* ??? */ 200%;
background-position:
0 100%,
100% 100%;
}
位置是很清楚的。一个梯度从左上角开始(0 0),在左下角结束(0 100%),而另一个从右上角开始(100% 0),在右下角结束(100% 100%)。
我们在背景位置和尺寸上使用transition ,以显示它们。我们只需要为background-size 的过渡值。和之前一样,background-position 需要即时变化,所以我们为过渡的持续时间分配一个0s 的值。
对于尺寸,两个渐变都需要有0的宽度和两倍的元素高度(0% 200%)。我们将在后面看到它们的尺寸在悬停时如何变化。让我们首先定义梯度的配置。
下图说明了每个梯度的配置。
请注意,对于第二个渐变(用绿色表示),我们需要知道高度,以便在我们创建的conic-gradient 内使用它。出于这个原因,我准备添加一个line-height ,设置元素的高度,然后对我们遗漏的圆锥梯度值尝试相同的值。
.hover-4 {
--c: #1095c1;
line-height: 1.2em;
background-image:
conic-gradient(from -135deg at 100% 50%, var(--c) 90deg, #0000 0),
conic-gradient(from -135deg at 1.2em 50%, #0000 90deg, var(--c) 0);
background-position:
0 0,
100% 0;
background-size: 0% 200%;
background-repeat: no-repeat;
transition: background-size .4s, background-position 0s;
}
.hover-4:hover {
background-size: /* ??? */ 200%;
background-position:
0 100%,
100% 100%;
}
我们剩下的最后一件事是弄清背景的大小。直观地讲,我们可能认为每个渐变需要占到元素宽度的一半,但这实际上是不够的。
如果我们用50% 作为两个梯度的background-size 值,我们就会留下很大的差距。
我们得到的差距等于高度,所以我们实际上需要做的是在悬停时将每个梯度的大小增加一半的高度,使它们覆盖整个元素。
.hover-4:hover {
background-size: calc(50% + .6em) 200%;
background-position:
0 100%,
100% 100%;
}
下面是我们像前面的例子一样优化它们后得到的结果。
.hover-4 {
--c: #1095c1;
line-height: 1.2em;
background:
conic-gradient(from -135deg at 100% 50%, var(--c) 90deg, #0000 0)
0 var(--p, 0%) / var(--s, 0%) 200% no-repeat,
conic-gradient(from -135deg at 1.2em 50%, #0000 90deg, var(--c) 0)
100% var(--p, 0%) / var(--s, 0%) 200% no-repeat;
transition: .4s, background-position 0s;
}
.hover-4:hover {
--p: 100%;
--s: calc(50% + .6em);
}
CodePen 嵌入回退
那只有一个自定义属性的版本呢?
我将把它留给你!在看了四个类似的悬停效果后,你应该能把最后的优化降到一个自定义属性。在评论区分享你的工作!没有奖品,但我们最终可能会有不同的实现方式和想法,让大家受益匪浅
在我们结束之前,让我分享Ana Tudor制作的最后一个悬停效果的一个版本。它是一个改进!但是请注意,由于一个已知的错误,它缺乏对Firefox的支持。不过,这是一个伟大的想法,它显示了如何将梯度与混合模式结合起来,创造更酷的悬停效果。
CodePen嵌入回退
总结
我们做了四个超酷的悬停效果尽管它们是不同的效果,但它们都采用了相同的方法,即使用CSSbackground 属性、自定义属性和calc() 。不同的组合使我们能够做出不同的版本,都使用了相同的技术,使我们的代码干净、可维护。
如果你想获得一些想法,我做了一个500个(是的,500个!)悬停效果的集合,其中400个是不用伪元素完成的。我们在这篇文章中涉及的四个只是冰山一角!
使用背景属性的酷炫悬停效果最初发表于CSS-Tricks。你应该收到通讯。