经常有人问我这样一个问题:能否用渐变色而不是纯色来创建阴影? CSS中没有专门的属性可以实现这一点(相信我,我已经找过了),任何你找到的关于这方面的博文基本上都是一些近似渐变的CSS技巧。我们接下来会介绍其中一些技巧。
但首先……又是关于渐变阴影的文章?真的吗?
是的,这又是关于这个主题的另一篇文章,但它有所不同。我们将一起突破极限,寻找一个解决方案,涵盖我从未在其他地方见过的内容:透明度。大多数技巧在元素具有非透明背景时有效,但如果我们有透明背景怎么办?我们将在此探讨这种情况!
在我们开始之前,让我介绍一下我的渐变阴影生成器。你只需调整配置,即可获得代码。但请继续阅读,因为我将帮助你理解生成代码背后的所有逻辑。
让我们从适用于80% 常见情况的解决方案开始。最典型的情况是:你正在使用一个带有背景的元素,并且需要向其添加渐变阴影。这里不需要考虑透明度问题。
解决方案是依靠一个定义了渐变的伪元素。你把它放在实际元素的后面,并对其应用模糊滤镜。
<code>.box { position: relative; } .box::before { content: ""; position: absolute; inset: -5px; /* 控制扩散 */ transform: translate(10px, 8px); /* 控制偏移量 */ z-index: -1; /* 将元素置于后面 */ background: /* 你的渐变色在这里 */; filter: blur(10px); /* 控制模糊 */ }</code>
代码看起来很多,这是因为它确实很多。以下是如何使用box-shadow代替它,如果我们使用纯色而不是渐变色的话。
<code>box-shadow: 10px 8px 10px 5px orange;</code>
这应该让你很好地了解第一段代码中值的作用。我们有X和Y偏移量、模糊半径和扩散距离。请注意,我们需要一个来自inset属性的负扩散距离值。
这是一个演示,显示了渐变阴影与经典box-shadow并排:
如果你仔细观察,你会注意到两个阴影略有不同,尤其是模糊部分。这并不奇怪,因为我很确定filter属性的算法与box-shadow的算法不同。这没什么大不了的,因为最终结果非常相似。
这个解决方案很好,但仍然有一些与z-index:-1声明相关的缺点。是的,那里发生了“堆叠上下文”!
我向主元素应用了一个转换,然后,阴影不再位于元素下方。这不是错误,而是堆叠上下文的逻辑结果。别担心,我不会开始对堆叠上下文进行枯燥的解释(我已经在Stack Overflow线程中做过这个了),但我仍然会向你展示如何解决它。
我推荐的第一个解决方案是使用3D转换:
<code>.box { position: relative; } .box::before { content: ""; position: absolute; inset: -5px; /* 控制扩散 */ transform: translate(10px, 8px); /* 控制偏移量 */ z-index: -1; /* 将元素置于后面 */ background: /* 你的渐变色在这里 */; filter: blur(10px); /* 控制模糊 */ }</code>
我们不使用z-index: -1,而是使用沿Z轴的负平移。我们将所有内容都放在translate3d()中。不要忘记在主元素上使用transform-style: preserve-3d;否则,3D转换将不会生效。
据我所知,这个解决方案没有任何副作用……但也许你会看到一个。如果是这样,请在评论区分享,让我们尝试找到一个解决方法!
如果由于某种原因你无法使用3D转换,另一个解决方案是依靠两个伪元素——::before和::after。一个创建渐变阴影,另一个复制主背景(以及你可能需要的其他样式)。这样,我们可以轻松控制两个伪元素的堆叠顺序。
<code>box-shadow: 10px 8px 10px 5px orange;</code>
重要的是要注意,我们正在通过在其上声明z-index: 0或任何其他执行相同操作的属性来强制主元素创建堆叠上下文。此外,不要忘记伪元素将主元素的填充框视为参考。因此,如果主元素有边框,则在定义伪元素样式时需要考虑这一点。你会注意到我在::after上使用inset: -2px来考虑在主元素上定义的边框。
正如我所说,只要你不需要支持透明度,这个解决方案在大多数情况下可能就足够好了,你想要一个渐变阴影。但我们是为了挑战和突破极限而来的,所以即使你不需要接下来要讲的内容,也请继续关注。你可能会学习到可以在其他地方使用的新的CSS技巧。
让我们从我们在3D转换中结束的地方开始,并从主元素中移除背景。我将从一个偏移量和扩散距离都等于0的阴影开始。
其想法是找到一种方法来裁剪或隐藏元素区域(绿色边框内)内的所有内容,同时保留外部内容。我们将为此使用clip-path。但你可能会想知道clip-path如何在一个元素内部进行裁剪。
确实,没有办法做到这一点,但是我们可以使用特定的多边形图案来模拟它:
<code>.box { position: relative; transform-style: preserve-3d; } .box::before { content: ""; position: absolute; inset: -5px; transform: translate3d(10px, 8px, -1px); /* (X, Y, Z) */ background: /* .. */; filter: blur(10px); }</code>
瞧!我们得到了一个支持透明度的渐变阴影。我们所做的只是向之前的代码添加了一个clip-path。这里有一张图来说明多边形部分。
蓝色区域是在应用clip-path后可见的部分。我只使用蓝色来说明这个概念,但实际上,我们只会看到该区域内的阴影。正如你所看到的,我们定义了四个点,它们的值很大(B)。我的大值是100vmax,但它可以是你想要的任何大值。其想法是确保我们有足够的阴影空间。我们还有四个点是伪元素的角点。
箭头表示定义多边形的路径。我们从(-B, -B)开始,直到到达(0,0)。总共我们需要10个点。不是8个点,因为路径中重复了两次两个点(( -B,-B)和(0,0))。
我们还需要做最后一件事,那就是考虑扩散距离和偏移量。上面演示之所以有效,仅仅是因为这是一个特殊情况,偏移量和扩散距离都等于0。
让我们定义扩散并看看会发生什么。记住,我们使用负值的inset来做到这一点:
伪元素现在比主元素大,所以clip-path裁剪的内容比我们需要多。记住,我们总是需要裁剪主元素内部的部分(示例中绿色边框内的区域)。我们需要调整clip-path中四个点的位。
<code>.box { position: relative; } .box::before { content: ""; position: absolute; inset: -5px; /* 控制扩散 */ transform: translate(10px, 8px); /* 控制偏移量 */ z-index: -1; /* 将元素置于后面 */ background: /* 你的渐变色在这里 */; filter: blur(10px); /* 控制模糊 */ }</code>
我们为扩散距离定义了一个CSS变量--s,并更新了多边形点。我没有触及我使用大值的地方。我只更新定义伪元素角的点。我将所有零值增加--s,并将100%的值减少--s。
偏移量也是同样的逻辑。当我们转换伪元素时,阴影会错位,我们需要再次校正多边形并将点移动到相反的方向。
<code>box-shadow: 10px 8px 10px 5px orange;</code>
还有两个变量用于偏移量:--x和--y。我们在transform内部使用它们,我们还更新了clip-path值。我们仍然不触及具有大值的多边形点,但我们偏移所有其他点——我们从X坐标中减少--x,从Y坐标中减少--y。
现在我们只需要更新一些变量来控制渐变阴影。当我们这样做的时候,让我们也把模糊半径也变成一个变量:
我们还需要3D转换技巧吗?
这完全取决于边框。不要忘记伪元素的参考是填充框,因此如果你对主元素应用边框,你将会有重叠。你可以保留3D转换技巧或更新inset值来考虑边框。
这是前面演示中用更新的inset值代替3D转换的版本:
我认为这是一种更合适的方法,因为扩散距离将更准确,因为它从border-box而不是padding-box开始。但是你需要根据主元素的边框调整inset值。有时,元素的边框是未知的,你必须使用之前的解决方案。
使用之前的非透明解决方案,你可能会遇到堆叠上下文问题。使用透明解决方案,你可能会遇到边框问题。现在你有了解决这些问题的选择和方法。3D转换技巧是我最喜欢的解决方案,因为它修复了所有问题(在线生成器也会考虑它)
如果你尝试在使用我们开始的非透明解决方案时向元素添加border-radius,这是一个相当简单的任务。你只需要从主元素继承相同的值,就完成了。
即使你没有圆角,定义border-radius: inherit也是一个好主意。这考虑了你以后可能想要添加的任何潜在圆角或来自其他地方的圆角。
处理透明解决方案的情况就不同了。不幸的是,这意味着要找到另一个解决方案,因为clip-path无法处理曲线。这意味着我们将无法裁剪主元素内部的区域。
我们将mask属性添加到混合中。
这部分非常繁琐,我很难找到一个不依赖于幻数的通用解决方案。我最终得到了一个非常复杂的解决方案,它只使用一个伪元素,但是代码是一团乱麻,只涵盖了一些特定情况。我认为探索这条路不值得。
为了简化代码,我决定插入一个额外的元素。以下是标记:
<code>.box { position: relative; } .box::before { content: ""; position: absolute; inset: -5px; /* 控制扩散 */ transform: translate(10px, 8px); /* 控制偏移量 */ z-index: -1; /* 将元素置于后面 */ background: /* 你的渐变色在这里 */; filter: blur(10px); /* 控制模糊 */ }</code>
我使用自定义元素 代码可能看起来有点奇怪,但我们会逐步讲解其背后的逻辑。接下来,我们使用 正如你所看到的,伪元素使用了与所有之前的示例相同的代码。唯一的区别是在 请注意, 我知道这有点棘手,但是与clip-path不同,mask属性不会考虑元素外部的区域来显示和隐藏内容。这就是为什么我不得不引入额外的元素——来模拟“外部”区域。 此外,请注意,我正在使用边框和inset的组合来定义该区域。这允许我将额外元素的padding-box保持与主元素相同,以便伪元素不需要额外的计算。 从使用额外元素中获得的另一个有用的东西是元素是固定的,只有伪元素在移动(使用translate)。这将允许我轻松地定义遮罩,这是这个技巧的最后一步。 完成了!我们有了渐变阴影,而且它支持border-radius!你可能期望一个复杂的面具值,里面有很多渐变,但不是!我们只需要两个简单的渐变和一个mask-composite来完成魔法。 让我们隔离 以下是我们得到的结果: 请注意内部半径如何与主元素的border-radius匹配。我定义了一个大的边框(150px)和一个等于大边框加主元素半径的border-radius。在外部,我有一个等于150px R的半径。在内部,我有150px R - 150px = R。 我们必须隐藏内部(蓝色)部分,并确保边框(红色)部分仍然可见。为此,我定义了两个遮罩层——一个只覆盖content-box区域,另一个覆盖border-box区域(默认值)。然后我将一个从另一个中排除以显示边框。 我使用相同的技术来创建支持渐变和border-radius的边框。Ana Tudor也有一篇关于遮罩复合的好文章,我邀请你阅读。 这种方法有什么缺点吗? 是的,这绝对不完美。你可能遇到的第一个问题与在主元素上使用边框有关。如果你没有考虑它,这可能会导致半径出现轻微错位。我们的示例中存在此问题,但你可能很难注意到它。 修复起来相对容易:为 另一个缺点是我们用于边框的大值(示例中为150px)。此值应该足够大以包含阴影,但不能太大以避免溢出和滚动条问题。幸运的是,在线生成器将考虑所有参数来计算最佳值。 我意识到的最后一个缺点是当你使用复杂的border-radius时。例如,如果你想对每个角应用不同的半径,你必须为每一侧定义一个变量。这并不是真正的缺点,我认为,但这可能会使你的代码维护起来有点困难。 为了简单起见,在线生成器只考虑统一的半径,但你现在知道如果要考虑复杂的半径配置如何修改代码了。 我们已经到达了终点!渐变阴影背后的魔法不再是谜团。我试图涵盖所有可能性以及你可能遇到的任何问题。如果我遗漏了什么或者你发现了任何问题,请随时在评论区报告,我会检查一下。 同样,考虑到事实上的解决方案将涵盖你的大多数用例,这其中很多可能都是多余的。然而,了解技巧背后的“为什么”和“如何”,以及如何克服其局限性是很好的。此外,我们在玩CSS剪辑和遮罩方面得到了很好的练习。 当然,你随时可以使用在线生成器来避免麻烦。<code>box-shadow: 10px 8px 10px 5px orange;</code>
<code>.box {
position: relative;
transform-style: preserve-3d;
}
.box::before {
content: "";
position: absolute;
inset: -5px;
transform: translate3d(10px, 8px, -1px); /* (X, Y, Z) */
background: /* .. */;
filter: blur(10px);
}</code>
<code>.box {
position: relative;
z-index: 0; /* 我们强制创建一个堆叠上下文 */
}
/* 创建阴影 */
.box::before {
content: "";
position: absolute;
z-index: -2;
inset: -5px;
transform: translate(10px, 8px);
background: /* .. */;
filter: blur(10px);
}
/* 复制主元素样式 */
.box::after {
content: "";
position: absolute;
z-index: -1;
inset: 0;
/* 继承在主元素上定义的所有装饰 */
background: inherit;
border: inherit;
box-shadow: inherit;
}</code>
<code>clip-path: polygon(-100vmax -100vmax,100vmax -100vmax,100vmax 100vmax,-100vmax 100vmax,-100vmax -100vmax,0 0,0 100%,100% 100%,100% 0,0 0)</code>
<code>.box {
position: relative;
}
.box::before {
content: "";
position: absolute;
inset: -5px; /* 控制扩散 */
transform: translate(10px, 8px); /* 控制偏移量 */
z-index: -1; /* 将元素置于后面 */
background: /* 你的渐变色在这里 */;
filter: blur(10px); /* 控制模糊 */
}</code>
<code>box-shadow: 10px 8px 10px 5px orange;</code>
<code>.box {
position: relative;
transform-style: preserve-3d;
}
.box::before {
content: "";
position: absolute;
inset: -5px;
transform: translate3d(10px, 8px, -1px); /* (X, Y, Z) */
background: /* .. */;
filter: blur(10px);
}</code>
总结
以上是获得CSS梯度阴影的不同方法的详细内容。更多信息请关注PHP中文网其他相关文章!