假设我们想定位一个元素,并且只视觉上模糊它的边框。我们无法直接使用任何简单的内置 Web 平台功能。但是,我们可以使用一些 CSS 小技巧来实现它。
这是我们想要的效果

让我们看看如何编写代码来实现这种效果,如何使用圆角来增强它,如何扩展支持以使其跨浏览器工作,未来在这个领域会带来什么以及我们可以从相同的想法开始获得哪些其他有趣的结果!
编写基本的模糊边框代码
我们从一个元素开始,在这个元素上,我们设置了一些虚拟尺寸、一个部分透明
(仅略微可见)的边框
和一个背景
,其大小相对于边框框
,但其可见性限制在填充框
内
$b: 1.5em; // border-width
div {
border: solid $b rgba(#000, .2);
height: 50vmin;
max-width: 13em;
max-height: 7em;
background: url(oranges.jpg) 50%/ cover
border-box /* background-origin */
padding-box /* background-clip */;
}
由background-origin
指定的框是其左上角为background-position
的0 0
点的框,也是background-size
(在我们这里设置为cover
)相对于的框。由background-clip
指定的框是背景
可见的框的边界。
background-origin
的初始值为padding-box
,background-clip
的初始值为border-box
,因此在这种情况下我们需要同时指定它们。
如果您需要更深入地了解background-origin
和background-clip
,您可以查看这篇关于该主题的详细文章。
上面的代码会给我们以下结果
查看 thebabydino (@thebabydino) 在 CodePen 上创建的 Pen。
接下来,我们添加一个绝对定位的伪元素,它覆盖其父元素的整个边框框
,并位于其后面(z-index: -1
)。我们还使这个伪元素继承其父元素的边框
和背景
,然后我们将边框颜色
更改为透明
,并将背景剪裁
更改为边框框
$b: 1.5em; // border-width
div {
position: relative;
/* same styles as before */
&:before {
position: absolute;
z-index: -1;
/* go outside padding-box by
* a border-width ($b) in every direction */
top: -$b; right: -$b; bottom: -$b; left: -$b;
border: inherit;
border-color: transparent;
background: inherit;
background-clip: border-box;
content: ''
}
}
现在我们也可以看到几乎不可见的边框
后面的背景
查看 thebabydino (@thebabydino) 在 CodePen 上创建的 Pen。
好吧,您可能已经看到了事情的发展方向!下一步是对伪元素进行blur()
处理。由于这个伪元素只在部分透明的边框
下方可见(其余部分被其父元素的填充框
限制的背景
覆盖),因此会导致边框
区域是图像中我们看到的唯一模糊区域。
查看 thebabydino (@thebabydino) 在 CodePen 上创建的 Pen。
我们还将元素的边框颜色
的 alpha 值降低到.03
,因为我们希望模糊度能够完成大部分突出显示边框
位置的工作。
这可能看起来已经完成了,但我仍然不喜欢一件事:伪元素的边缘现在也被模糊了。所以让我们修复它!
在浏览器应用属性的顺序方面,一个方便的事情是过滤器在剪裁之前应用。虽然这不是我们想要的,并且迫使我们在很多其他情况下采用不方便的解决方法……就在这里,它被证明非常有用!
这意味着,在模糊伪元素之后,我们可以将其剪裁到其边框框
!
我首选的方法是将clip-path
设置为inset(0)
,因为……这确实是实现它的最简单方法!polygon(0 0, 100% 0, 100% 100%, 0 100%)
会太过复杂。
查看 thebabydino (@thebabydino) 在 CodePen 上创建的 Pen。
如果您想知道为什么不在实际元素上设置clip-path
,而是在:before
伪元素上设置它,这是因为在元素上设置clip-path
会使它成为一个堆叠上下文。这会迫使所有子元素(以及因此也包括其模糊的:before
伪元素)包含在其中,因此位于其背景
的前面。然后,任何核武器z-index
或!important
都无法改变这一点。
我们可以通过添加一些带有更漂亮字体
、盒子阴影
和一些布局属性的文本来美化它。
如果我们有圆角呢?
使用inset()
而不是polygon()
作为clip-path
的最佳之处在于,inset()
还可以适应我们可能需要的任何border-radius
!
当我说是任何border-radius
时,我是认真的!看看这个!
div {
--r: 15% 75px 35vh 13vw/ 3em 5rem 29vmin 12.5vmax;
border-radius: var(--r);
/* same styles as before */
&:before {
/* same styles as before */
border-radius: inherit;
clip-path: inset(0 round var(--r));
}
}
它运行得很好!
查看 thebabydino (@thebabydino) 在 CodePen 上创建的 Pen。
扩展支持
一些移动浏览器仍然需要-webkit-
前缀才能使用filter
和clip-path
,因此请确保也包含这些版本。请注意,它们包含在本文中嵌入的 CodePen 演示中,即使我在本文正文中提供的代码中选择跳过它们。
好吧,但如果我们需要支持 Edge 呢?clip-path
在 Edge 中不起作用,但filter
起作用,这意味着我们确实会得到模糊的边框,但没有清晰的剪裁限制。
嗯,如果我们不需要圆角,我们可以使用已弃用的clip
属性作为后备。这意味着在clip-path
之前添加以下行
clip: rect(0 100% 100% 0)
然后我们的演示现在在 Edge 中运行……有点!右侧、底部和左侧边缘被清晰地剪裁,但顶部的边缘仍然保持模糊(仅在 Pen 的调试模式下,在编辑器视图中的 iframe 中看起来一切正常)。打开开发者工具或在 Edge 窗口中右键单击或单击窗口外部的任何位置都会使该属性的效果消失。这是本月的错误!
好吧,由于它非常不可靠,即使我们想要圆角它也无济于事,所以让我们尝试另一种方法!
这有点像用右脚挠左耳(或者反过来,取决于哪一侧更灵活),但这是我能想到的让它在 Edge 中工作的唯一方法。
你们中的一些人可能已经在屏幕上大喊大叫:“但是 Ana……overflow: hidden
!”是的,这就是我们现在要做的。我最初避免使用它是因为它的工作方式:它会剪裁掉填充框
外部的所有后代内容。而不是边框框
外部,就像我们通过剪裁所做的那样!
这意味着我们需要丢弃真正的边框
,并用填充
来模拟它,我对这并不完全满意,因为它会导致更多复杂情况,但让我们一步一步来!
就代码更改而言,我们首先要做的是删除所有与边框
相关的属性,并将边框宽度
值设置为填充
。然后,我们设置overflow: hidden
,并将实际元素的背景
限制在内容框
内。最后,我们将伪元素的背景剪裁
重置为填充框
值,并将它的偏移量归零。
$fake-b: 1.5em; // fake border-width
div {
/* same styles as before */
overflow: hidden;
padding: $fake-b;
background: url(oranges.jpg) 50%/ cover
padding-box /* background-origin */
content-box /* background-clip */;
&:before {
/* same styles as before */
top: 0; right: 0; bottom: 0; left: 0;
background: inherit;
background-clip: padding-box;
}
}
查看 thebabydino (@thebabydino) 在 CodePen 上创建的 Pen。
如果我们想要那个几乎不可见的“边框”覆盖,我们需要在实际元素上再添加一个背景
层
$fake-b: 1.5em; // fake border-width
$c: rgba(#000, .03);
div {
/* same styles as before */
overflow: hidden;
padding: $fake-b;
--img: url(oranges.jpg) 50%/ cover;
background: var(--img)
padding-box /* background-origin */
content-box /* background-clip */,
linear-gradient($c, $c);
&:before {
/* same styles as before */
top: 0; right: 0; bottom: 0; left: 0;
background: var(--img);
}
}
查看 thebabydino (@thebabydino) 在 CodePen 上创建的 Pen。
我们也可以轻松地添加圆角
查看 thebabydino (@thebabydino) 在 CodePen 上创建的 Pen。
那为什么我们一开始不这样做呢?!
还记得我之前说过,不使用实际的边框
可能会在以后使事情变得复杂吗?
好吧,假设我们想要添加一些文本。使用第一种方法,使用实际的边框
和clip-path
,要防止文本内容接触模糊的边框
,只需要在我们的元素上添加填充
(例如1em
)。
查看 thebabydino (@thebabydino) 在 CodePen 上创建的 Pen。
但是,对于overflow: hidden
方法,我们已经使用了padding
属性来创建模糊的“边框”。增加它的值没有帮助,因为它只会增加伪边框的宽度。
我们可以将文本添加到子元素中。或者我们也可以使用:after
伪元素!
这种方法的工作方式与第一种方法非常相似,:after
替换了实际的元素。不同的是,我们使用 overflow: hidden
而不是 clip-path: inset(0)
来裁剪模糊的边缘,而实际元素的 padding
是伪元素的 border-width
($b
) 加上我们想要的任何 padding
值。
$b: 1.5em; // border-width
div {
overflow: hidden;
position: relative;
padding: calc(1em + #{$b});
/* prettifying styles */
&:before, &:after {
position: absolute;
z-index: -1; /* put them *behind* parent */
/* zero all offsets */
top: 0; right: 0; bottom: 0; left: 0;
border: solid $b rgba(#000, .03);
background: url(oranges.jpg) 50%/ cover
border-box /* background-origin */
padding-box /* background-clip */;
content: ''
}
&:before {
border-color: transparent;
background-clip: border-box;
filter: blur(9px);
}
}
查看 thebabydino (@thebabydino) 在 CodePen 上的 Pen。
如何同时拥有文本和非常圆润的角?我们将在另一篇文章中讨论这个问题,敬请期待!
backdrop-filter
呢?
你们中的一些人可能想知道(就像我最初尝试实现这种效果时一样)backdrop-filter
是否不是一种选择。
既是也不是!
从技术上讲,可以实现相同的效果,但由于 Firefox 尚未实现它,如果我们选择这种方式,将放弃 Firefox 支持。更不用说这种方法还迫使我们使用两个伪元素才能获得对元素有文本内容情况的最佳支持(这意味着我们需要伪元素及其 padding-box
区域的 background
在此文本下方显示)。
更新:由于 回归,backdrop-filter
技术不再适用于 Chrome,因此支持现在最好限制在 Safari 和 Edge。
对于那些还不知道 backdrop-filter
是做什么的:它会过滤掉我们对其应用的元素的(部分)transparent
部分可以看到的内容。
我们需要这样处理:两个伪元素都有一个透明的 border
和一个相对于 padding-box
定位和大小的 background
。我们将顶部伪元素(:after
)的 background
限制在 padding-box
范围内。
现在 :after
在 border
区域不再有 background
,我们可以透过它看到它后面的 :before
伪元素。我们在 :after
上设置 backdrop-filter
,甚至可以将 border-color
从 transparent
更改为略微可见的颜色。底部(:before
)伪元素的 background
仍然可以通过(部分)transparent
、几乎不可区分的 :after
上部的 border
看见,由于应用了 backdrop-filter
而变得模糊。
$b: 1.5em; // border-width
div {
overflow: hidden;
position: relative;
padding: calc(1em + #{$b});
/* prettifying styles */
&:before, &:after {
position: absolute;
z-index: -1; /* put them *behind* parent */
/* zero all offsets */
top: 0; right: 0; bottom: 0; left: 0;
border: solid $b transparent;
background: $url 50%/ cover
/* background-origin & -clip */
border-box;
content: ''
}
&:after {
border-color: rgba(#000, .03);
background-clip: padding-box;
backdrop-filter: blur(9px); /* no Firefox support */
}
}
请记住,此方法的 实时演示 目前不适用于 Firefox,需要在 Chrome 的 chrome://flags
中启用 Experimental Web Platform 特性 才能在 Chrome 中使用。
消除一个伪元素
我不建议在实际环境中这样做,因为它也会切断 Edge 支持,但我们确实有一种方法可以使用一个伪元素来实现我们想要的结果。
我们首先在元素上设置图像背景(只要我们在 padding
中包含其宽度,我们实际上不需要显式设置 border
),然后在覆盖其整个父元素的绝对定位伪元素上设置一个部分 transparent
、几乎不可见的 background
。我们还在这个伪元素上设置了 backdrop-filter
。
$b: 1.5em; // border-width
div {
position: relative;
padding: calc(1em + #{$b});
background: url(oranges.jpg) 50%/ cover;
/* prettifying styles */
&:before {
position: absolute;
/* zero all offsets */
top: 0; right: 0; bottom: 0; left: 0;
background: rgba(#000, .03);
backdrop-filter: blur(9px); /* no Firefox support */
content: ''
}
}
好的,但这会模糊几乎 transparent
伪元素后面的整个元素,包括其文本。这不是错误,这是 backdrop-filter
应该做的事情。

为了解决这个问题,我们需要消除(不是使 transparent
,在这种情况下完全没有用)伪元素的内部矩形(其边缘距离 border-box
边缘 $b
距离)。
我们有两种方法可以做到这一点。
第一种方法(实时演示)是使用 clip-path
和零宽度隧道 技术。
$b: 1.5em; // border-width
$o: calc(100% - #{$b});
div {
/* same styles as before */
&:before {
/* same styles as before */
/* doesn't work in Edge */
clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%,
0 0,
#{$b $b}, #{$b $o}, #{$o $o}, #{$o $b},
#{$b $b});
}
}
第二种方法(实时演示)是使用两个 组合的 mask
层(请注意,在这种情况下,我们需要在我们的伪元素上显式设置 border
)。
$b: 1.5em; // border-width
div {
/* same styles as before */
&:before {
/* same styles as before */
border: solid $b transparent;
/* doesn't work in Edge */
--fill: linear-gradient(red, red);
-webkit-mask: var(--fill) padding-box,
var(--fill);
-webkit-mask-composite: xor;
mask: var(--fill) padding-box exclude,
var(--fill);
}
}
由于这两个属性都不适用于 Edge,这意味着支持现在仅限于 WebKit 浏览器(并且我们仍然需要为 Chrome 中的 backdrop-filter
启用 Experimental Web Platform 特性标志)。
未来的(更好的!)解决方案
filter()
函数允许我们在各个 background
层上应用过滤器。这消除了对伪元素的需求,并将实现此效果所需的代码减少到两个 CSS 声明!
border: solid 1.5em rgba(#000, .03);
background: $url
border-box /* background-origin */
padding-box /* background-clip */,
filter($url, blur(9px))
/* background-origin & background-clip */
border-box
你可能已经猜到了,这里的问题是支持。Safari 是目前唯一实现它的浏览器,但如果你认为 filter()
可以帮助你,你可以添加你的用例并跟踪 Chrome 和 Firefox 的实现进度。
更多边界过滤器选项
到目前为止,我只讨论了对 border
进行模糊处理,但这种技术适用于几乎所有 CSS filter
(除了 drop-shadow()
,这在当前情况下没有多大意义)。你可以在下面的交互式演示中尝试在它们之间切换并调整值。
查看 thebabydino (@thebabydino) 在 CodePen 上的 Pen。
到目前为止,我们只使用了一个 filter
函数,但我们也可以将它们链接起来,这样可能性就无穷无尽了——你能用这种方式想出什么酷炫的效果?
查看 thebabydino (@thebabydino) 在 CodePen 上的 Pen。
感谢您撰写了这篇精彩的文章。:)
说到
backdrop-filter
,你可以使用element()
模仿它,使其在 Firefox 中工作。这并不容易,而且有缺点,但可以做到:http://iamvdo.me/en/blog/css-element-function#faking-backdrop-filter说到
filter()
函数,它在 Safari 9 中就已经实现了:http://iamvdo.me/en/blog/advanced-css-filters#filter并且可以使用 Houdini 很容易地进行多态化(目前有一些解决方法,因为
<image>
类型的自定义属性尚未得到支持):https://css-houdini.rocks/background-properties哦,使用
element()
来解决 Firefox 不支持backdrop-filter
的问题真是一个好主意!感谢您告诉我filter()
已经被 Safari 支持了,我已经更新了这篇文章。我喜欢
filter()
函数,希望更多浏览器能实现它!Safari 已经支持了一段时间,最初它还很不完善(如果我记得没错的话,与过滤区域的大小有关),但据我所知,它已经有所改善。
你也可以使用 SVG 过滤器来实现:https://codepen.io/ccprog/pen/jJQrdE
我的做法略有不同,因此在模糊效果结束的地方不会出现突然的内边框,而是逐渐消失。这是通过在全尺寸未模糊的原始图像上覆盖一个模糊的“边框图像”来实现的。
如果你仔细观察,会发现另一个区别:你的效果在图像的外边缘仍然显示一些透明度,即使在使用
overflow: hidden
裁剪之后。在我的情况下,模糊程度会稍微降低。哦,是的。我从来没有怀疑过 SVG 在这里可以提供更多灵活性。只是 SVG 过滤器有点超出我的能力范围。这也是为什么我需要问:你知道为什么这种 SVG 技术只在 Firefox 中有效吗?Chrome 和 Edge 对我来说什么都没有显示出来。
哦,对了,我应该测试一下……
原因出乎意料地有趣。
feMorphology
过滤器基元的工作原理如下:在我的示例中,黑色泛滥区域填充了过滤器区域到其边界,这些边界根据规范“作为硬剪切裁剪矩形”起作用。Firefox 将此解释为靠近边界的像素,就像边界外的像素是透明黑色一样,而 Chrome 似乎完全不考虑这些像素。我倾向于选择 Firefox。
再思考一下,我找到了另一种描述边界的解决方案:将泛滥区域向左上角平移,将第二个副本向右下角平移,相交并反转。这适用于 Firefox 和 Chrome:https://codepen.io/ccprog/pen/ywQEmZ