不久前,我正在为 An Event Apart 的网站添加焦点样式。 其中一部分是在设计的不同区域应用不同的焦点效果,例如标题和页脚中的白色圆环以及主文本中的橙色圆环。 但在一个地方,我想要更明显的圆环——类似于将两个边框叠加在一起,以创建吸引眼球的独特形状。

我尝试过将带有边框的元素嵌套并使用一些负边距将一个边框拉到另一个边框之上,或者将边框嵌套在轮廓中,然后使用负边距来防止布局偏移。 但是,这些方法都无法让我满意。
事实证明,有很多技巧可以实现将一个边框叠加到另一个边框之上的效果,方法是将边框与其他一些 CSS 效果结合使用,甚至根本不需要使用任何边框。 我们来探索一下,好吗?
轮廓和盒子阴影
如果要进行多边框的元素是矩形——你知道,就像几乎所有块级元素一样——那么混合轮廓和扩散的硬盒子阴影可能正是你所需要的。
让我们从盒子阴影开始。 你可能习惯了像这样的盒子阴影
.drop-me {
background: #AEA;
box-shadow: 10px 12px 0.5rem rgba(0,0,0,0.5);
}

这会让你在元素下方和右侧获得模糊的阴影。 过时的下拉阴影! 但是,在 box-shadow
中,存在第四个长度值的空间和支持,该值定义了扩散距离。 这会将阴影形状的大小在所有方向上增加给定的长度,然后进行模糊处理。 假设存在模糊处理。 也就是说。
因此,如果我们给盒子阴影没有偏移、没有模糊以及一些扩散,它将在元素周围绘制自身,看起来像一个实心边框,但实际上不是边框。
.boxborder-me {
box-shadow: 0 0 0 5px firebrick;
}

这个盒子阴影“边框”正在元素的外边框边缘之外绘制。 这与块级盒子周围绘制轮廓的位置相同,因此现在我们要做的就是将轮廓绘制在阴影之上。 像这样
.boxborder-me {
box-shadow: 0 0 0 5px firebrick;
outline: dashed 5px darkturquoise;
}

Bingo。 一个多色“边框”,在本例中,甚至不会影响布局大小,因为阴影和轮廓是在计算元素大小之后绘制的。 位于顶部的轮廓可以使用几乎任何轮廓样式,它与边框样式列表相同。 因此,点线和双线轮廓都是可能的选项。 (其他所有样式也是如此,但它们没有透明部分,因此只能通过半透明颜色看到实心阴影。)
如果你想要在边框中使用三色效果,可以使用逗号分隔的列表创建多个盒子阴影,然后在顶部添加轮廓。 例如
.boxborder-me {
box-shadow: 0 0 0 1px darkturquoise,
0 0 0 3px firebrick,
0 0 0 5px orange,
0 0 0 6px darkturquoise;
outline: dashed 6px darkturquoise;
}

回到更简单的效果,将dashed
轮廓与扩散盒子阴影和与盒子阴影颜色相同的实心边框结合使用,可以创建另一种效果
.boxborder-me {
box-shadow: 0 0 0 5px firebrick;
outline: dashed 5px darkturquoise;
border: solid 5px darkturquoise;
}

这里额外的优势是,即使使用了盒子阴影,它也不会填充元素的背景,因此你可以通过它看到背景。 这就是盒子阴影始终的行为方式:它们只绘制在外部边框边缘之外。 你可能认为始终位于元素后面的“阴影的剩余部分”并不存在。 它永远不会被绘制。 因此,你会得到这样的结果

这是 CSS 背景和边框模块,级别 3,第 7.1.1 节 中明确语言的结果
外部盒子阴影会投射出阴影,就好像元素的边框盒子是不透明的一样。 假设扩散距离为零,其周长与边框盒子的尺寸和形状完全相同。 阴影只绘制在边框边缘之外:它在元素的边框盒内部被裁剪。
(强调部分添加。)
边框和盒子阴影
说到边框,也许有一种方法可以将边框和盒子阴影结合起来。 毕竟,盒子阴影不仅仅是下拉阴影。 它们也可以是内嵌的。 那么,如果我们将之前的阴影向内翻转,并在其顶部放置一个边框呢?
.boxborder-me {
box-shadow: 0 0 0 5px firebrick inset;
border: dashed 5px darkturquoise;
}

这……不是我们想要的结果。 但是,内嵌阴影的运作方式是这样的:它们绘制在外部填充边缘(也称为内部边框边缘)内,并在其之外被裁剪
内嵌盒子阴影会投射出阴影,就好像填充边缘之外的所有内容都是不透明的一样。 假设扩散距离为零,其周长与填充盒子的尺寸和形状完全相同。 阴影只绘制在填充边缘内:它在元素的填充盒之外被裁剪。
(同上;强调部分添加。)
因此,我们无法将边框叠加在内嵌盒子阴影之上。 也许我们可以将边框叠加在其他东西之上……?
边框和多个背景
内嵌阴影可能被限制在外部填充边缘,但背景则不然。 元素的背景默认情况下会填充到外部边框边缘的区域。 用实心颜色填充元素背景,并赋予它一个粗虚线边框,你会看到边框可见部分之间的背景颜色。
那么,如果我们将一些背景叠加在一起,从而绘制出我们想要放在边框后面的实心颜色呢? 这是第一步
.multibg-me {
border: 5px dashed firebrick;
background:
linear-gradient(to right, darkturquoise, 5px, transparent 5px);
background-origin: border-box;
}

我们可以看到,在左侧,蓝色背景通过红色虚线边框的透明部分可见。 再添加三个类似的背景,每个背景对应元素盒子的一个边缘,然后
.multibg-me {
border: 5px dashed firebrick;
background:
linear-gradient(to top, darkturquoise, 5px, transparent 5px),
linear-gradient(to right, darkturquoise, 5px, transparent 5px),
linear-gradient(to bottom, darkturquoise, 5px, transparent 5px),
linear-gradient(to left, darkturquoise, 5px, transparent 5px);
background-origin: border-box;
}

在每种情况下,背景渐变都以实心深青色背景运行 5 个像素,然后有一个颜色停顿时会立即过渡到透明。 这样可以让“背景”显示在元素中,同时仍然提供“叠加边框”。
这里一个主要的优势是,我们并不局限于实心线性渐变——我们可以使用任何复杂程度的任何渐变,只是为了稍微调味一下。 以下示例中,虚线边框已变得大部分透明,因此我们可以完整地看到四种不同的渐变
.multibg-me {
border: 15px dashed rgba(128,0,0,0.1);
background:
linear-gradient(to top, darkturquoise, red 15px, transparent 15px),
linear-gradient(to right, darkturquoise, red 15px, transparent 15px),
linear-gradient(to bottom, darkturquoise, red 15px, transparent 15px),
linear-gradient(to left, darkturquoise, red 15px, transparent 15px);
background-origin: border-box;
}

如果你看一下角落,你会发现背景渐变是矩形的,并且彼此重叠。 它们并没有像边框角落一样整齐地相遇。 如果你的边框在角落有透明部分,这可能会成为问题,例如 border-style: double
的情况。
此外,如果你只是想要在边框后面使用实心颜色,这是一种相当笨拙的方式来拼接这种效果。 肯定有更好的方法吧?
边框和背景裁剪
是的,有! 它涉及更改元素背景的两个不同层的裁剪盒子。 最初想到的可能是这样的
.multibg-me {
border: 5px dashed firebrick;
background: #EEE, darkturquoise;
background-clip: padding-box, border-box;
}
但是,这不起作用,因为 CSS 要求只有最后一个(因此也是最下面的)背景被设置为<color>
值。 任何其他背景层必须是图像。
因此,我们将那个非常浅灰色的背景颜色替换为从该颜色到该颜色的渐变:这之所以有效是因为渐变是图像。 换句话说
.multibg-me {
border: 5px dashed firebrickred;
background: linear-gradient(to top, #EEE, #EEE), darkturquoise;
background-clip: padding-box, border-box;
}

浅灰色“渐变”填充整个背景区域,但使用 background-clip
被裁剪到填充盒子。 深青色填充整个区域,并且被裁剪到边框盒子,正如背景一直以来默认的行为一样。 我们可以将渐变颜色和方向更改为我们喜欢的任何东西,创建一个实际可见的渐变,或者将其更改为全白色或我们想要的任何其他线性效果。
这里的缺点是,无法使填充区域背景透明,以便可以通过元素看到元素的背景。 如果线性渐变变为透明,那么整个元素背景将被填充为深青色。 或者,更确切地说,我们将能够看到始终存在的深青色。
在许多情况下,元素背景是否透明并不重要,但这仍然是一个令人沮丧的限制。 难道就没有办法获得叠加边框的效果,而无需使用奇怪的技巧和失去功能吗?
边框图像
事实上,如果我们可以获取我们想要在世界上看到的叠加边框的图像,将其切片,然后将它用作边框呢? 就像,说,这张图片变成了这个边框?

以下是执行此操作的代码
.borderimage-me {
border: solid 5px;
border-image: url(triple-stack-border.gif) 15 / 15px round;
}
首先,我们设置一个带有某些宽度的实心边框。 我们也可以设置一个颜色作为备用,但这并非必需。 然后,我们指向一个图像 URL,在 15
处定义切片内边距,将边框的宽度设置为 15px
,最后是 round
的重复模式。
边框图像还有更多选项,这些选项过于复杂,这里就不详细介绍了。但总的来说,你可以使用一张图像,通过偏移值定义它的九个切片,然后使用这些切片来合成图像周围的完整边框。这是通过定义图像边缘的偏移量来完成的,在本例中是 15
。由于图像是一个 GIF,因此是基于像素的,所以偏移量以像素为单位,因此“切片线”设置在图像边缘内侧 15 个像素处。(对于 SVG,偏移量以 SVG 的坐标系为单位。)它看起来像这样

每个切片都分配给与自身相对应的元素框的角或边;例如,右下角切片放置在元素的右下角,顶部(中心)切片沿元素的顶部边缘使用,依此类推。
如果某个边缘切片小于元素边缘的长度——这种情况几乎总是会发生,而且在这里肯定也是这样——那么该切片会以多种方式之一重复。我选择了 round
,它会尽可能地填充重复,然后将所有重复的切片按比例放大,以填满边缘。所以对于一个 70 像素长的切片,如果边缘是 1,337 像素长,那么该切片将重复 19 次,每次的宽度都按比例放大到 70.3 像素。或者,更可能的是,浏览器会生成一个包含 19 个重复的图像,宽度为 1,330 像素,然后将该图像拉伸额外的 7 个像素。
你可能会认为这里的缺点是浏览器支持,但事实并非如此。
此浏览器支持数据来自 Caniuse,其中包含更多详细信息。数字表示浏览器从该版本开始支持该功能。
台式机
Chrome | Firefox | IE | Edge | Safari |
---|---|---|---|---|
56 | 50 | 11 | 12 | 9.1 |
移动设备/平板电脑
Android Chrome | Android Firefox | Android | iOS Safari |
---|---|---|---|
127 | 127 | 127 | 9.3 |
只需注意一些围绕少数实现存在的错误(实际上是实现限制),你就会没事的。
结论
虽然你可能很少遇到需要组合多个“边框”效果或将它们叠加在一起的情况,但了解 CSS 提供了许多方法来完成这项工作,而且其中大多数方法已经得到广泛支持,这一点很重要。谁知道呢?也许有一天,我们会找到一种简单的方法,通过单个属性来实现这些效果,而不是将多个属性混合在一起。在那之前,祝你堆叠边框愉快!
2 个笔记
令我惊讶的是,这里没有提到 outline-offset!我经常在类似的情况下使用这个属性。
鉴于各种辅助功能技术经常被组合使用,那么当这些技术与高对比度模式组合使用时,是否会存在任何问题?我记得一些背景和盒子阴影会消失,这意味着不可见的焦点样式。但我还不确定。
Mark,我制作了一个 CodePen,其中包含上面所有代码示例。然后,我在调试模式下在 Edge 中打开它,并切换到 Windows 高对比度模式。我截取了一个屏幕截图,并将它嵌入到 CodePen 中(因为我无法在此处发布图像):https://s.codepen.io/aardrian/debug/EMdvdO
简而言之,阴影会消失,轮廓会被保留,但会重置为文本颜色(因此,如果控件是交互式的,则应谨慎使用),渐变颜色会被保留。
供将来参考:CSS 工作组 在几年前就达成一致,应该在 CSS 中原生支持堆叠边框或轮廓(通过允许边框颜色/样式列表)。
在这个(很长)线程中,仍然有一些关于它如何工作的细节正在讨论,但它的主要论点与 Eric 在此开始探索的内容相同——创建对许多不同的组件和背景颜色具有高对比度的稳健焦点样式。
谢谢,Amelia!我的印象是,多个边框应该是嵌套的,而不是堆叠的,根据 编辑草案的第 3 节,至少在我写这篇文章的时候是这样。堆叠也正在讨论吗?