胖蔡说技术
随便扯扯

几种设置CSS渐变阴影的方法

这是我经常听到的一个问题:有可能从渐变色而不是纯色中创建阴影吗?没有特定的CSS属性可以做到这一点(相信我,我已经看过了),你发现的任何关于它的博客文章基本上都是很多CSS技巧来近似梯度。实际上,我们会边走边报道其中的一些。

但首先…另一篇关于渐变阴影的文章?真正地

是的,这是关于这个话题的另一篇帖子,但不同。我们将一起突破极限,找到一个涵盖我在其他地方从未见过的东西的解决方案:透明度。如果元素有不透明的背景,大多数技巧都有效,但如果我们有透明的背景呢?我们将在这里探讨这个案例!

在我们开始之前,让我介绍一下我的渐变阴影生成器。您所要做的就是调整配置,并获取代码。但请继续阅读,因为我将帮助您理解生成代码背后的所有逻辑。

不透明解决方案

让我们从适用于80%大多数情况的解决方案开始。最典型的情况是:你使用的是一个有背景的元素,你需要给它添加一个渐变阴影。没有透明度问题需要考虑。

解决方案是依赖于定义梯度的伪元素。将其放置在实际元素后面,并对其应用模糊过滤器。

.box {
  position: relative;
}
.box::before {
  content: "";
  position: absolute;
  inset: -5px; /* control the spread */
  transform: translate(10px, 8px); /* control the offsets */
  z-index: -1; /* place the element behind */
  background: /* your gradient here */;
  filter: blur(10px); /* control the blur */
}

它看起来像很多代码,这是因为它确实如此。以下是如果我们使用纯色而不是渐变色,我们可以用方框阴影来完成它的方法。

box-shadow: 10px 8px 10px 5px orange;

这应该能让您很好地了解第一个代码段中的值在做什么。我们有X和Y偏移、模糊半径和扩散距离。请注意,我们需要一个来自inset属性的传播距离的负值。

下面是一个演示,显示了经典长方体阴影旁边的渐变阴影:

如果你仔细观察,你会发现两个阴影都有点不同,尤其是模糊部分。这并不奇怪,因为我很确定filter属性的算法与长方体阴影的算法工作方式不同。这没什么大不了的,因为最终的结果是非常相似的。

这个解决方案很好,但仍有一些与z-index:-1声明相关的缺点。是的,那里正在发生“堆叠上下文”!

我对主要元素进行了转换,砰!阴影不再位于元素下方。这不是一个bug,而是堆栈上下文的逻辑结果。别担心,我不会开始对堆叠上下文进行无聊的解释(我已经在Stack Overflow线程中这样做了),但我仍然会向您展示如何处理它。

我建议的第一个解决方案是使用三维变换:

.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);
}

我们将使用沿z轴的负平移,而不是使用z索引:-1。我们将把所有内容都放在translate3d()中。不要忘记使用转换样式:preserve-3d对主要元素;否则,3D变换将不会生效。

据我所知,这个解决方案没有副作用……但也许你看到了。如果是这样的话,请在评论区分享,让我们试着找到解决方案!

如果由于某种原因无法使用三维变换,另一种解决方案是依赖于两个伪元素——:before和:after。一个创建渐变阴影,另一个再现主背景(以及您可能需要的其他样式)。这样,我们可以很容易地控制两个伪元素的堆叠顺序。

.box {
  position: relative;
  z-index: 0; /* We force a stacking context */
}
/* Creates the shadow */
.box::before {
  content: "";
  position: absolute;
  z-index: -2;
  inset: -5px;
  transform: translate(10px, 8px);
  background: /* .. */;
  filter: blur(10px);
}
/* Reproduces the main element styles */
.box::after {
  content: """;
  position: absolute;
  z-index: -1;
  inset: 0;
  /* Inherit all the decorations defined on the main element */
  background: inherit;
  border: inherit;
  box-shadow: inherit;
}

需要注意的是,我们通过在主元素上声明z-index:0或任何其他相同的属性来强制主元素创建堆栈上下文。此外,不要忘记伪元素将主元素的填充框视为引用。因此,如果主元素有边界,那么在定义伪元素样式时需要考虑到这一点。您会注意到,我使用inset:-2px on::after来说明在主元素上定义的边界。

正如我所说,在大多数需要渐变阴影的情况下,只要不需要支持透明度,这种解决方案可能就足够了。但我们在这里是为了挑战和突破极限,所以即使你不需要接下来的内容,也请和我呆在一起。你可能会学到新的CSS技巧,可以在其他地方使用。

透明解决方案

让我们从3D变换的中断处开始,并从主元素中删除背景。我将从一个偏移量和扩散距离都等于0的阴影开始。

其想法是找到一种方法来剪切或隐藏元素区域内(绿色边界内)的所有内容,同时保留外部内容。我们将使用剪辑路径。但您可能想知道剪辑路径是如何在元素内部进行剪切的。

事实上,没有办法做到这一点,但我们可以使用特定的多边形模式来模拟它:

clip-path: polygon(-100vmax -100vmax,100vmax -100vmax,100vmax 100vmax,-100vmax 100vmax,-100vmax -100vmax,0 0,0 100%,100% 100%,100% 0,0 0)

我们有一个支持透明度的渐变阴影。我们所做的只是在前面的代码中添加一个剪辑路径。这是一个图来说明多边形部分。

蓝色区域是应用片段路径后的可见部分。我只是用蓝色来说明这个概念,但实际上,我们只会看到那个区域内的阴影。正如你所看到的,我们定义了四个值很大的点(B)。我的最大值是100vmax,但它可以是你想要的任何大值。这个想法是为了确保我们有足够的空间来放置阴影。我们还有四个点,它们是伪元素的角。

箭头显示了定义多边形的路径。我们从(-B,-B)开始,直到到达(0,0)。我们总共需要10分。不是八个点,因为两个点在路径中重复两次((-B,-B)和(0,0))。

我们还有一件事要做,那就是考虑传播距离和偏移量。上面的演示之所以有效,是因为这是一个偏移和扩展距离等于0的特殊情况。

让我们定义传播,看看会发生什么。请记住,我们使用带有负值的insert来执行此操作:

伪元素现在比主元素大,所以剪辑路径的剪切量超过了我们的需要。记住,我们总是需要剪切主元素内部的部分(示例中绿色边界内的区域)。我们需要调整剪辑路径内四个点的位置。

.box {
  --s: 10px; /* the spread  */
  position: relative;
}
.box::before {
  inset: calc(-1 * var(--s));
  clip-path: polygon(
    -100vmax -100vmax,
     100vmax -100vmax,
     100vmax 100vmax,
    -100vmax 100vmax,
    -100vmax -100vmax,
    calc(0px  + var(--s)) calc(0px  + var(--s)),
    calc(0px  + var(--s)) calc(100% - var(--s)),
    calc(100% - var(--s)) calc(100% - var(--s)),
    calc(100% - var(--s)) calc(0px  + var(--s)),
    calc(0px  + var(--s)) calc(0px  + var(--s))
  );
}

我们已经为展开距离定义了一个CSS变量–s,并更新了多边形点。我没有触及我使用大值的地方。我只更新定义伪元素角的点。我将所有零值增加-s,将100%的值减少-s。

偏移也是同样的逻辑。当我们平移伪元素时,阴影不对齐,我们需要再次校正多边形并将点向相反的方向移动。

.box {
  --s: 10px; /* the spread */
  --x: 10px; /* X offset */
  --y: 8px;  /* Y offset */
  position: relative;
}
.box::before {
  inset: calc(-1 * var(--s));
  transform: translate3d(var(--x), var(--y), -1px);
  clip-path: polygon(
    -100vmax -100vmax,
     100vmax -100vmax,
     100vmax 100vmax,
    -100vmax 100vmax,
    -100vmax -100vmax,
    calc(0px  + var(--s) - var(--x)) calc(0px  + var(--s) - var(--y)),
    calc(0px  + var(--s) - var(--x)) calc(100% - var(--s) - var(--y)),
    calc(100% - var(--s) - var(--x)) calc(100% - var(--s) - var(--y)),
    calc(100% - var(--s) - var(--x)) calc(0px  + var(--s) - var(--y)),
    calc(0px  + var(--s) - var(--x)) calc(0px  + var(--s) - var(--y))
  );
}

偏移还有两个变量:-x和-y。我们在变换中使用它们,还更新剪辑路径值。我们仍然不接触具有大值的多边形点,但我们偏移了所有其他点——我们从x坐标减少了-x,从y坐标减少了-y。

现在我们所要做的就是更新一些变量来控制渐变阴影。当我们处理它的时候,让我们也让模糊半径成为一个变量:

这完全取决于边界。不要忘记伪元素的引用是填充框,所以如果将边界应用于主元素,则会出现重叠。可以保留三维变换技巧,也可以更新插入值以考虑边界。

这是之前的演示,使用更新的插入值代替3D变换:

我想说这是一种更合适的方式,因为传播距离将更准确,因为它从边界框而不是填充框开始。但您需要根据主元素的边界调整插入值。有时,元素的边界是未知的,您必须使用以前的解决方案。

使用早期的不透明解决方案,您可能会面临堆叠上下文问题。有了透明的解决方案,你可能会面临边境问题。现在你有了解决这些问题的选择和方法。3D转换技巧是我最喜欢的解决方案,因为它解决了所有问题(在线生成器也会考虑它)

添加边界半径

如果在使用我们开始使用的不透明解决方案时尝试向元素添加边界半径,那么这是一项相当琐碎的任务。您所需要做的就是从main元素继承相同的值,就完成了。

即使你没有边界半径,定义边界半径也是个好主意:inherit。这说明了你以后可能想添加的任何潜在边界半径或来自其他地方的边界半径。

在处理透明解决方案时,情况就不同了。不幸的是,这意味着要找到另一个解决方案,因为剪辑路径无法处理曲率。这意味着我们将无法剪切主元素内部的区域。

我们将在混合物中引入遮罩特性。

这部分非常乏味,我很难找到一个不依赖幻数的通用解决方案。我最终得到了一个非常复杂的解决方案,它只使用了一个伪元素,但代码只是一块意大利面条,只涵盖了少数特定情况。我认为这条路不值得探索。

为了简化代码,我决定插入一个额外的元素。以下是标记:

<div class="box">
  <sh></sh>
</div>

我正在使用一个自定义元素,以避免与外部CSS发生任何潜在冲突。我本可以使用<div>,但由于它是一个公共元素,它很容易被来自其他地方的另一个CSS规则所攻击,这可能会破坏我们的代码。

第一步是定位元素并有意创建溢出:

.box {
  --r: 50px;
  position: relative;
  border-radius: var(--r);
}
.box sh {
  position: absolute;
  inset: -150px;
  border: 150px solid #0000;
  border-radius: calc(150px + var(--r));
}

代码看起来可能有点奇怪,但我们将在进行过程中了解其背后的逻辑。接下来,我们使用的伪元素创建渐变阴影。

.box {
  --r: 50px;
  position: relative;
  border-radius: var(--r);
  transform-style: preserve-3d;
}
.box sh {
  position: absolute;
  inset: -150px;
  border: 150px solid #0000;
  border-radius: calc(150px + var(--r));
  transform: translateZ(-1px)
}
.box sh::before {
  content: "";
  position: absolute;
  inset: -5px;
  border-radius: var(--r);
  background: /* Your gradient */;
  filter: blur(10px);
  transform: translate(10px,8px);
}

正如您所看到的,pseudo元素使用与前面所有示例相同的代码。唯一的区别是在元素而不是伪元素上定义的3D变换。目前,我们有一个没有透明度功能的渐变阴影:

请注意,元素的区域是用黑色轮廓定义的。我为什么这么做?因为这样,我就可以在上面戴上口罩,将溢出的部分隐藏在绿色区域内,并将其保留在我们需要看到阴影的地方。

我知道这有点棘手,但与剪辑路径不同,mask属性不考虑元素外部的区域来显示和隐藏内容。这就是为什么我有义务引入额外的元素——模拟“外部”区域。

另外,请注意,我正在使用边框和插图的组合来定义该区域。这允许我保持该额外元素的填充框与主元素相同,这样伪元素就不需要额外的计算。

我们从使用额外元素中得到的另一个有用的东西是,元素是固定的,只有伪元素在移动(使用translate)。这将使我能够轻松定义掩码,这是这个技巧的最后一步。

mask:
  linear-gradient(#000 0 0) content-box,
  linear-gradient(#000 0 0);
mask-composite: exclude;

完成了!我们有我们的梯度阴影,它支持边界半径!您可能期望一个具有大量梯度的复杂遮罩值,但没有!我们只需要两个简单的梯度和一个掩模组合就可以完成魔术。

让我们隔离元素,以了解那里发生了什么:

.box sh {
  position: absolute;
  inset: -150px;
  border: 150px solid red;
  background: lightblue;
  border-radius: calc(150px + var(--r));
}

这是我们得到的:

请注意内部半径如何与主元素的边界半径相匹配。我定义了一个大边界(150px)和一个等于大边界加上主元素半径的边界半径。在外面,我有一个等于150px+R的半径。在里面,我有150px+R-150px=R

我们必须隐藏内部(蓝色)部分,并确保边框(红色)部分仍然可见。为此,我定义了两个遮罩层——一个仅覆盖内容框区域,另一个覆盖边框区域(默认值)。然后我把一个排除在另一个之外,以揭示边界。

mask:
  linear-gradient(#000 0 0) content-box,
  linear-gradient(#000 0 0);
mask-composite: exclude;

修复相对容易:为<sh>元素的插入添加边框的宽度。

.box {
  --r: 50px;
  border-radius: var(--r);
  border: 2px solid;
}
.box sh {
  position: absolute;
  inset: -152px; /* 150px + 2px */
  border: 150px solid #0000;
  border-radius: calc(150px + var(--r));
}

另一个缺点是我们对边界使用的值太大(本例中为150px)。这个值应该足够大以包含阴影,但不能太大以避免溢出和滚动条问题。幸运的是,在线生成器将考虑所有参数来计算最优值。

我知道的最后一个缺点是,当您使用复杂的边界半径时。例如,如果希望将不同的半径应用于每个角,则必须为每条边定义一个变量。我想,这并不是一个真正的缺点,但它会使代码更难维护。

.box {
  --r-top: 10px;
  --r-right: 40px;
  --r-bottom: 30px;
  --r-left: 20px;
  border-radius: var(--r-top) var(--r-right) var(--r-bottom) var(--r-left);
}
.box sh {
  border-radius: calc(150px + var(--r-top)) calc(150px + var(--r-right)) calc(150px + var(--r-bottom)) calc(150px + var(--r-left));
}
.box sh:before {
  border-radius: var(--r-top) var(--r-right) var(--r-bottom) var(--r-left);
}

为了简单起见,在线生成器只考虑统一的半径,但如果您想考虑复杂的半径配置,现在您已经知道如何修改代码了。

总结

我们已经到了终点!渐变阴影背后的魔力不再是个谜。我试图涵盖你可能面临的所有可能性和任何可能的问题。如果我错过了什么,或者你发现了任何问题,请随时在评论区报告,我会查看的。

同样,考虑到事实上的解决方案将覆盖您的大多数用例,很多这可能都是过头了。尽管如此,了解这个技巧背后的“为什么”和“如何”,以及如何克服它的局限性,还是很好的。此外,我们还进行了很好的CSS剪辑和屏蔽练习。

赞(0) 打赏
转载请附上原文出处链接:胖蔡说技术 » 几种设置CSS渐变阴影的方法
分享到: 更多 (0)

请小编喝杯咖啡~

支付宝扫一扫打赏

微信扫一扫打赏