CSS `border-image`:复杂却强大,解锁更多创意你肯定知道 CSS 的 border-image 属性,但
你肯定知道 CSS 的 border-image
属性,但可能从未真正使用过它,或者你曾经尝试过,却被它的切片逻辑弄得晕头转向。确实,border-image
的语法有些复杂,但这并不妨碍它创造出超酷的装饰效果和形状。 本文将由 Temani Afif 带领大家探索 border-image
的奥妙,并使用它打造各种炫酷的 UI 元素!
border-image
属性并不新鲜,甚至已经过时的 IE 浏览器都支持它。但它并非我们常用的属性,而且它关于“切片”和“偏移”的复杂概念,让它看起来很难使用。
但是,我要告诉你,border-image
不仅能创造出令人惊叹的 UI 设计,还能实现我们通常使用其他属性和方法才能完成的常见效果。
这篇文章将用简单易懂的语言,带领你玩转 border-image
,用它来创建形状,并尝试一些新奇的设计,例如范围滑块、工具提示和标题装饰等等。
我相信,看完这篇文章后,border-image
会成为你最爱的 CSS 属性之一,就像它已经成为我的最爱一样!
理解边框图像的概念
理解 border-image
的几个关键点:
- 容易覆盖边框图像: CSS 规范中提到
border-image
应该替换你定义的任何常规边框,但实际情况并非总是如此。 试着运行下面的代码,你只会看到一个红色边框。
/* 只能看到红色边框 */
.element {
border-image: linear-gradient(blue, red) 1;
border: 5px solid red;
}
这是因为我们是在 border-image
之后定义了 border
。在使用 border-image
时,顺序真的很重要!
/* 👍 */
.element {
border: 5px solid red;
border-image: linear-gradient(blue, red) 1;
}
你已经可以看出,这对于初学者来说有多么令人困惑。你还会注意到,我们的渐变边框的厚度等于我们定义的 border-width
,也就是 5px
。
我个人习惯不将 border
和 border-image
一起使用,这样可以避免覆盖我想要创建的边框图像,并且可以只使用一个属性来控制边框装饰(即使两者可以一起使用)。所以,如果你遇到奇怪的结果,别忘了检查是否在某个地方定义了 border
。
-
边框图像绘制在背景和阴影之上: 第二个技巧是,
border-image
绘制在元素的background
和box-shadow
之上,但在元素的内容之下。这个细节对于我们后面示例中的一些技巧很重要。下面的示例演示了边框图像的应用顺序:
- 复杂的语法:
border-image
的语法是它难以理解的主要原因。
border-image: <source> <slice>/<width>/<outset> <repeat>
在这篇文章中,<source>
将由渐变组成,尽管它也支持图像源。我们不会经常使用 <repeat>
。让我们重新编写语法,删除 <repeat>
并设置一个占位符 *-gradient()
作为当前的源:
border-image: *-gradient() <slice>/<width>/<outset>
我们抽象了一些参数,只剩下 <slice>
、<width>
和 <outset>
。
<width>
和 <outset>
的工作原理与设置 border-width
、padding
和 margin
相同。它们分别接受一个到四个值,具体取决于你是否想分别为元素的边缘设置特定的 <outset>
和 <width>
值。 有时,我只需要指定偏移量,而不需要宽度。所以,你可能会看到我这样写:
border-image: *-gradient() <slice>//<outset>
这可能是唯一一个属性可以使用双斜杠的!
<slice>
也接受一个到四个值(无单位或百分比),以及可选的 fill
关键字。这就是 border-image
难以理解的地方,因为它不容易将切片的概念可视化。
图 1: border-image
属性的 <slice>
、<width>
和 <outset>
值的示意图。
如果我们要使用提供的变量作为值将上面的图转换为代码,它将如下所示:
border-image:
linear-gradient(...)
s-top s-right s-bottom s-left /
w-top w-right w-bottom w-left /
o-top o-right o-bottom o-left;
默认情况下,border-image
将元素的边界(图 1 中用蓝色虚线边框表示)视为其要绘制渐变的 区域,但我们可以使用 <outset>
来改变这个区域,并创建一个溢出效果。这对于创建“外部”装饰非常有用。
然后,<width>
用于将该区域分成 九个区域,<slice>
用于将源(即渐变)也分成 九个切片。从那里,我们将每个切片分配到其对应的区域。每个切片都将拉伸以填充其分配的区域,如果它们没有共享相等的尺寸,结果通常是扭曲的图像切片。稍后,我们将学习如何控制它并防止扭曲。
中间区域默认情况下保持为空。也就是说,完全可以使用 fill
关键字来执行它的功能,并使用切片九(始终是中心切片)填充中间区域。
border-image: linear-gradient(...) fill
s-top s-right s-bottom s-left /
w-top w-right w-bottom w-left /
o-top o-right o-bottom o-left;
我知道这只是 border-image
的快速入门,但我认为这足以让我们做一些非常酷的事情。让我们开始动手尝试各种效果吧。
渐变覆盖
我们的第一个尝试是在现有的背景之上添加 一个渐变覆盖。这是一个相当常见的模式,可以通过增加文本颜色和背景颜色之间的对比度来提高文本的可读性。
在文本和内容之间设置覆盖层有很多众所周知的做法。2013 年 Chris Coyier 曾经分享过一种方法,但这并非最常用的方法,最常用的方法可能是 使用伪元素。
但是 border-image
可以让我们用一行代码实现:
.overlay {
border-image: fill 0 linear-gradient(#0003,#000);
}
就是这样!不需要额外的元素,不需要伪元素,也不需要修改 background
属性。
如果我们分析一下该属性,我们会发现它包含 <source>
(即渐变)和 <slice>
(即 fill 0
)。我们没有定义 <width>
或 <outset>
,所以它们默认为 0
。
让我们使用之前演示切片概念时使用的相同思路,将我们用作占位符的变量替换为我们刚刚看到的最后一个示例中的值。
我们有一个等于 0
的切片,所以切片 1 到 8 将为空,而中间的切片将填充整个渐变。就像我们只有一个切片一样。<width>
等于 0
,所以区域 1 到 8 的大小为 0
,将中间区域留给剩余的空间(整个 border-image
的区域)。由于我们没有指定 <outset>
,所以 border-image
的区域等于元素的区域。
最终,我们有一个放置在中间区域的切片,以及另外八个我们根本看不到的切片。这就是我们在元素的背景之上和文本之后的渐变层。
让我们尝试使用不同的语法,但产生与我们刚刚完成的操作相同的结果。
.overlay {
border-image: linear-gradient(#0003, #000) 50%/50%;
}
这次,我们定义了一个等于 50%
的 <slice>
,一个也等于 50%
的 <width>
,以及没有 <outset>
。切片 1 到 4 各占渐变的四分之一。与此同时,切片 5 到 9 为空。使用相同的区域逻辑,我们有四个区域——1 到 4——每个区域占元素面积的四分之一,而其他区域的大小为 0
。
在第一种方法中,我们将一个切片放置在元素的中间。在这种方法中,我们有四个切片,每个切片都放置在元素的一个角上,并允许它填充整个元素。
结果相同,但使用相同的属性,方法不同。
如果切片概念让你感到困惑,我们还有很多其他示例可以帮助你理解它。诀窍是首先理解渐变是如何分成不同的切片的,然后理解这些切片是如何放置在不同的区域中的,这些区域的大小由 <width>
和 <outset>
决定。理解这个机制可能需要时间,但一旦你理解了,就会很有趣! 让我们继续下一个示例。
全宽背景
我们的下一个示例是一个常见的设计模式,我们希望元素的背景扩展到屏幕的整个宽度。我们通常称之为 “突破”背景,因为元素通常需要超出父容器的约束宽度才能扩展到整个屏幕。
图 2: 背景能够“突破”父元素的约束宽度。
猜猜看,border-image
属性可以用一行代码实现这个效果:
.full-background {
border-image: conic-gradient(pink 0 0) fill 0//0 100vw;
}
你可能想知道 conic-gradient()
是怎么回事,对吧?我想用纯色,但与 background
和 background-color
属性不同,border-image
不支持颜色值,所以我使用了生成纯色的渐变。为此,我在渐变中定义了一个颜色,并使用两个颜色停止。
渐变的类型或应用于它的颜色停止并不重要。我更喜欢 conic-gradient()
和两个零,因为它只是为了获得纯色而提供最小的语法。
现在,让我们分析一下这一行代码。我们有一个等于 0
的 <slice>
,没有 <width>
(它隐式地等于 0
),这次 <outset>
等于 0 100vw
,它将 border-image
的区域从左侧和右侧增加了视窗宽度(100vw
)的 100%。
从技术上讲,我们可以使用小于 100vw
的值,因为我们需要覆盖的空间很可能更小,但是使用更大的值始终是好的,以确保我们考虑了所有可能性。border-image
不会触发滚动,所以不用担心使用大值!
图 3: 使用较大的值可以保证背景始终足够宽。
如果你将我们刚刚完成的操作与渐变覆盖示例进行比较,你会发现 <outset>
是两种实现之间的唯一区别。除此之外,我们有一个放置在中间区域的单个切片,它覆盖了我们扩展到屏幕边缘的整个区域。
当然,我们不局限于纯色,因为我们正在使用渐变。
我们甚至可以通过倾斜背景来制作一个有趣的设计。
“棘手”的部分是我将 border-image
与 clip-path
结合使用:
.slant {
--a: 3deg; /* 控制角度(应该很小) */
border-image: conic-gradient(pink 0 0) fill 0//9999px;
clip-path:
polygon(
-9999px calc(tan(var(--a)) * 9999px),
9999px calc(tan(var(--a)) * -9999px),
calc(100% + 9999px) calc(100% - tan(var(--a)) * 9999px),
calc(100% - 9999px) calc(100% + tan(var(--a)) * 9999px)
);
}
这次,我们有一个相当大的 <outset>
向所有方向扩展。这迫使颜色溢出元素的边界,这使我们能够使用 CSS 的 clip-path
属性来裁剪形状。
图 4: 裁剪背景创建倾斜外观。
花哨的标题
我们可以用 border-image
来装饰标题,使其拥有花哨的边框。让我们从与全宽背景相同的实现开始。只是这次,我们将 conic-gradient()
替换为 linear-gradient()
:
.full-background {
border-image: linear-gradient(0deg, #1095c1 5px, lightblue 0) fill 0//0 100vw;
}
现在,我们将它应用于 <h1>
元素:
没什么可惊讶的。我们基本上是在建立一个渐变,它具有一个锐利的颜色停止,在标题的底部边缘创建了一个类似边框的外观,并扩展到屏幕的整个宽度。但是,如果我们将其中一种颜色设置为 transparent
,并在一个方向上定义一个大的 <outset>
呢?这会产生一个底部边框,它在单个方向上而不是两个方向上扩展到屏幕边缘。
这是一个简单的模式,但它可能是一个不错的设计元素。仍然使用一行代码:
.full-line {
border-image: linear-gradient(0deg, #1095c1 5px, #0000 0) fill 0//0 100vw 0 0;
}
我们实际上可以用另一种方法实现它:
.full-line {
border-image: conic-gradient(#1095c1 0 0) fill 0/calc(100% - 8px) 0 0 0/0 100vw 0 0;
}
这将顶部区域的大小设置为等于 100% - 8px
,而其他区域的大小等于 0
。如果你回头看看第一张图,这意味着我们将第五个区域的高度设置为 100% - 8px
,因此中间区域只有 8px
的高度,由于切片仍然为 0
,因此我们只有放置在该 8px
空间内的中间切片。
图 5: 区域的高度设置为元素的整个高度,底部边缘减去 8px。
所以,这是使用相同的 border-image
语法获得相同效果的两种不同方法。我们实际上还可以用第三种方法实现:
.full-line {
border-image: conic-gradient(#1095c1 0 0) 0 0 1 0/0 0 8px 0/0 100vw 0 0;
}
这次,我定义了一个底部切片,它等于 1
(无单位的值将计算为像素),这会产生两个切片,第七个(底部中心)和第九个(中心)。从那里,我将第七个区域的高度设置为 8px
。注意,这次我不用 fill
关键字,所以中间区域没有像上次那样填充。相反,我们只填充第七个区域,它占据了 border-image
区域的 100%
和高度的 8px
。
你可能想知道为什么我定义了一个等于 1
的切片,对吧?目标是只有两个切片:第七个(底部中心)和第九个(中间),由于我们应用的是纯色,所以大小并不重要。这就是我使用 1
的原因;一个小的正值。任何值都可以(例如,0.5
、2
、100
、50%
、76%
等等);只是 1
输入起来更短。记住,切片将在其区域内拉伸,所以 1px
足以填充整个区域。
重点是: 当使用纯色时,切片值并不重要。在大多数情况下,该值最终将为 0
(空)或 1
(填充)。你可以把它想象成二进制逻辑。
我们可以用第四种方法实现!
.full-line {
border-image: conic-gradient(#1095c1 0 0) 0 1 0 0/calc(100% - 8px) 100% 0 0/0 100vw 0 0;
}
我会让你自己去想一下上面的 CSS 代码是如何工作的。这是一个很好的机会,让你了解切片元素。拿笔和纸试试,看看我们使用了哪些切片,哪些区域将被填充。
border-image
之所以复杂,就是因为它有许多不同的方法可以实现相同的结果。你可能会遇到很多不同的组合,当它们都产生相同的结果时,很难形成一个心理模型来理解所有值是如何协同工作的。
即使没有唯一的“正确”方法来制作这些标题边框,我还是更喜欢第二种语法,因为它允许我简单地更改一个颜色值,以创建“真实”的渐变而不是纯色。
.full-line {
border-image: repeating-linear-gradient(...) fill 0 /
calc(100% - var(--b)) 0 0/0 100vw 0 0 repeat;
}
注意最后一个示例中的 repeat
关键字。我之前说过,切片可以设置为不同于其对应区域的大小,这会导致边框图像变形。在处理纯色时,这不是问题。但是,使用真正的渐变时,可能会出现变形。幸运的是,在大多数情况下,解决方法是在 border-image
声明的末尾设置 repeat
(stretch
是默认值)。
如果 repeat
似乎不起作用,那么可能是代码中的其他地方存在问题……或者可能你已经达到了 border-image
所能做到的极限。这是一个强大的属性,但就像任何其他属性一样,它也有自己的局限性。
标题分割线
既然我们在上一个示例中为标题制作了底部边框,我想我们可以做一个变体,通过 在标题 中间 绘制边框 来实现。
这可能是一种强调标题的好方法,尤其是在文章正文中的标题。以下是相关的代码:
h2 {
--s: 3px; /* 厚度 */
--c: red; /* 颜色 */
--w: 100px; /* 宽度 */
--g: 10px; /* 间隙 */
border-image:
linear-gradient(
#0000 calc(50% - var(--s)/2),
var(--c) 0 calc(50% + var(--s)/2),
#0000 0)
0 1 / 0 var(--w) / 0 calc(var(--w) + var(--g));
}
这个示例使用了 CSS 变量,因此我们可以灵活地重新配置边框图像并创建该模式的变体。但在阅读关于它如何工作的解释之前,请花点时间使用你已经学到的知识来可视化结果。尝试识别切片、区域及其尺寸。
准备好了吗?以下是它的工作原理。
我们有一个等于 0 1
的切片——左右各 1px
。这将建立宽度为 1px
的切片八(中心右侧)和六(中心左侧),而剩余的空间保留给第九个(中心)切片。我们没有使用 fill
,所以我们并不关心第九个切片,因为它不会被绘制。
宽度 (--w
) 用于设置区域六和八的大小。我们需要在 <outset>
中设置相同的值,以确保这两个区域在元素边界 之外。另一个变量 --g
添加到偏移量公式中,用于控制文本和装饰之间的间隙。
渐变类似于我们在上一节中为底部标题边框使用的渐变。只是这次颜色位于中心而不是底部,它的大小由 --s
变量控制。
**图 7. ** (放大预览)
让我们尝试使用相同的效果,但使用不同的语法:
h2 {
--s: 3px; /* 厚度 */
--w: 100px; /* 宽度 */
--g: 10px; /* 间隙 */
border-image:
conic-gradient(red 0 0)
0 50%/calc(50% - var(--s)/2) var(--w)/0 calc(var(--w) + var(--g));
}
<slice>
的顶部和底部值为 0
,左右值为 50%
。这意味着切片六和八共享渐变。所有其他切片(包括中心)都是空的。
至于区域,顶部和底部区域(包含顶部的区域 1、5 和 2,以及底部的区域 4、7 和 3)的高度等于 50% - var(--s)/2
,将 --s
变量作为剩余区域(6、8 和 9)的高度。右侧和左侧区域的宽度等于 --w
变量。由于切片 6 和 8 是唯一填充的切片,因此我们只需要关心区域 6 和 8。它们的高度都等于边框的厚度 --s
,宽度等于 --w
。
我相信你已经知道后面的故事了。
**图 8. ** (放大预览)
请注意,我使用的是 50%
作为切片。它展示了任何值都可以完成工作,就像我们在上一节中解释为什么我选择使用 1
作为值一样,同时也是为了为下一个示例做准备,在该示例中,我将使用真正的渐变:
当涉及到真正的渐变时,切片的值很重要,有时你需要非常精确的值。老实说,这很棘手,我甚至在尝试找出正确的值时也会迷路。
让我们用更多标题装饰的示例来结束本节。当与其他属性结合使用时,border-image
可以产生非常漂亮的效果。
更多示例
现在我们已经看到了几个关于 border-image
的详细示例,我将展示其他几个示例。与其详细解释,不如尝试通过查看 CSS 来用自己的语言解释它们,并将其作为你自己的工作的灵感。
无限图像装饰
在涉及图像时,border-image
可以成为救星,因为我们无法访问伪元素。以下是一些可以实现 3D 效果的酷炫无限装饰。
如果你查看这些示例中的代码,你会发现它们几乎具有相同的结构。如果你难以识别模式,请随时在本文末尾留下评论,我很乐意指出它。
自定义范围滑块
我写了一篇关于如何创建以下示例的 详细文章,你可以参考它来了解使用相同技术的范围滑块变体。
我使用 border-image
并只对“滑块”元素进行了样式设置。众所周知,跨浏览器范围输入的实现不同,但“滑块”是它们之间共有的元素。
丝带形状
如果你错过了,我已经创建了 超过 100 种单元素丝带形状的集合,其中一些依赖于 border-image
。我称它们为“无限丝带”。
心形
我已经写过 关于使用不同方法创建 CSS 心形的文章,其中一种方法使用的是 border-image
技术。
.heart {
width: 200px;
aspect-ratio: 1;
border-image: radial-gradient(red 69%,#0000 70%) 84.5%/50%;
clip-path: polygon(-42% 0,50% 91%, 142% 0);
}
这里有趣的部分是切片等于 84.5%
。这个值大于 50%
,所以看起来可能不正确,因为总和超过了 100%
。但这完全没问题,因为 切片可以相互重叠!
当使用大于 50%
的值时,角切片(1、2、3 和 4)将共享公共部分,但其他切片将被认为为空。从逻辑上讲,当使用等于 100%
的切片时,我们将最终得到四个包含完整源的切片。
以下是一个说明技巧的示例:
滑块会将切片从 0%
更新到 100%
。在左侧,你可以看到角切片(1-4)是如何增长的。在 0%
和 50%
之间,结果是合乎逻辑且直观的。大于 50%
时,你会开始出现重叠。当达到 100%
时,你可以看到完整的圆圈重复了四次,因为每个切片都包含了完整的渐变,这得益于重叠。
这可能令人困惑,而且不容易可视化,但重叠对于创建自定义形状和花哨装饰非常有用。
工具提示
如何用 两个属性创建一个简单的工具提示形状? 没错,这完全可以做到!
.tooltip {
/* 三角形尺寸 */
--b: 2em; /* 底边 */
--h: 1em; /* 高度*/
border-image: conic-gradient(#CC333F 0 0) fill 0//var(--h);
clip-path:
polygon(0 100%,0 0,100% 0,100% 100%,
calc(50% + var(--b)/2) 100%,
50% calc(100% + var(--h)),
calc(50% - var(--b)/2) 100%);
}
这只是一个简单的例子,我在 另一篇文章 中探索了更多工具提示形状,也使用了 border-image
属性来实现视觉效果。
填充圆角
与大多数装饰性边框属性(例如,box-shadow
、outline
、border
等等)不同,border-image
不尊重 border-radius
。即使我们已经圆角了,该元素仍然是一个盒子。其他属性会识别由 border-radius
建立的视觉边界,但 border-image
会直接穿过它。
我想,在某些情况下这可能是一个缺点,但它也是 CSS 的奇特之处之一,可以利用它来创建其他用途,例如创建 带有内圆角的图像:
很酷,对吧?仅用一行代码就可以实现!
img {
--c: #A7DBD8;
--s: 10px; /* 边框厚度*/
border-image: conic-gradient(var(--c) 0 0) fill 0 // var(--s);
}
我们甚至可以留空中心,以获得一个仅对整个元素进行边框处理的变体:
结论
你是否知道 border-image
属性是一个如此强大而灵活的 CSS 属性?尽管理解它的语法具有挑战性,但有一些方法可以使代码保持干净和简单。此外,通常有多种“正确”方法可以实现相同的结果。这是一个复杂而强大的 CSS 功能。
如果你仍然对使用 border-image
来切片和定义区域的概念感到困惑,别担心。这很常见。我花了很长时间才完全理解 border-image
的工作原理以及如何使用不同的语法方法。你也给自己足够的时间。多读几遍这篇文章,让这些概念深入你的脑海。
撇开复杂性不谈,我希望你能将 border-image
添加到你的工具箱中,用它创造很多魔法。我们可以用 border-image
做的事情比这里展示的还要多。我经常尝试这类东西,并在我的 CSS Tip 网站 上分享我的作品。考虑订阅 (RSS),以了解我尝试的有趣和奇怪的东西。
关于本文
原文:www.smashingmagazine.com/2024/01/css…
译者:前端宝哥
转载自:https://juejin.cn/post/7405153695506251827