被低估的border-image属性

这篇文章发布于 2022年02月25日,星期五,18:09,归类于 CSS相关。 阅读 17235 次, 今日 6 次 14 条评论

 

本文不讲具体的 border-image 语法,因为语法庞杂,不是我针对谁,是在座的所有个位都消化不了,所以,直指要害,讲讲那些只有 border-image 能够实现的事情,读完后发现 border-image 属性还是挺有一套的,则可以买本 CSS新世界,里面有对 border-image 语法非常细致的介绍。

一、从鸡肋华丽转身的转折点

在很长一段时间内,border-image 属性就是个鸡肋属性,有着非常怪异的渲染机制,需要把图切成 9 块,分别填充到边框的 9 个区域。

几乎所有的 CSS 样式渲染属性,无论是渐变、圆角、盒阴影、滤镜还是混合模式,都可以从设计软件中找到对应的设置选项,唯一一个例外就是 border-image

为什么会出现这种现象呢?

现在的小年轻肯定不知道背后的原因,容我来给大家讲讲。

在 CSS3 还没有出现的年代,那个时候,所有的彩色效果、所有的圆角效果一定是PNG小图片实现的,例如,一些渐变按钮,还有一些渐变选项卡。

当时,为了让这些图像可以适应足够多的文字,会通过特殊的切图,通过两层 HTML 标签嵌套,配合 background-position 属性实现。

比方说下图就是当年实现1-7个字符宽度自适应选项卡使用的 PNG 小图片,中间有一段镂空:

自适应选项卡 PNG 图

HTML 结构是这样的:

<a href class="outer">
    <span class="inner">选项卡</span>
</a>

这个就是最终实现的效果:

实现的选项卡效果

是不是很麻烦?无论是切图还是写代码?

于是就催生了 border-image 属性,专门用来实现 PNG 小图片的内容自适应效果,只需要提供一个规整的(还是需要处理)的体积更小的图片,就可以实现任意字数的样式效果了。

下面就是一些典型的案例:

border-image典型应用

您可以狠狠地点击这里:border-image 设计初衷作用demo

看起来 border-image 属性还有点用,但是大家仔细观察上面的效果图,很容易就会发现,这些所谓的自适应效果,圆角效果,特么直接 Gradient 渐变、box-shadow 盒阴影以及 border-radius 圆角就可以实现,代码简单易上手,关键是兼容性绝赞(IE10+支持),比 border-image 属性的兼容性(IE11+)还要好,资源开销也小,这就导致没有任何需要使用 border-image 的理由。

border-image 效果被其他属性代替

border-image 属性从此沦为了鸡肋,只在某些花里花哨的边框装饰场景还有一点使用价值(因为不得不使用图片)。

然而,故事在这里并没有结束,随着时间的流逝,从 2012 年开始,在接下来1-2年的时间内,随着某一些新特性的支持,border-image 属性迎来了新生,开始在特定的场景下绽放出别样的竞争力。

这个特性就是:

  • 2012年7月 Firefox 和 Safari 紧随 Chrome 浏览器的步伐对 border-image 新语法支持,包括:border-image:fillborder-image-outset 属性;
  • 2014年4月,Firefox 浏览器终于支持了 border-image 的渐变图像功能。

这两个新变化让 border-image 属性迎来了新生,成为了一个被远远低估的 CSS 属性。

二、从简单的渐变边框说起

由于 border-image 支持了渐变图像,因此可以轻松实现其他 CSS 属性不太好实现的渐变边框效果。

<p class="border-linear-gradient">上下渐变边框</p>
<p class="border-radial-gradient">径向渐变边框</p>
.border-linear-gradient {
    border-style: solid;
    border-image: linear-gradient(deepskyblue, deeppink) 20 / 10px;
}
.border-radial-gradient {
    border-style: solid;
    border-image: radial-gradient(deepskyblue, deeppink) 20 / 10px;
}

效果如下图所示:

渐变边框

要想警示某一段内容存在风险,可以使用红色的条纹边框,代码如下:

<div class="border-stripe">我们可以使用红色条纹边框表示警示</div>
.border-stripe {
    border: 12px solid;
    border-image: repeating-linear-gradient(-45deg, red, red 5px, transparent 5px, transparent 10px) 12;
}

MDN 一些废弃文档的警示边框就是使用 border-image 属性实现了,效果截图参见下方。

红色斜纹警示边框

我们甚至可以用 border-image 属性重新定义元素的虚线边框,虚线的尺寸和虚实比例都可以随意控制,例如:

<div class="border-dashed">1:1的虚线</div>
.border-dashed {
    border: 1px dashed deepskyblue;
    border-image: repeating-linear-gradient(135deg, deepskyblue, deepskyblue 5px, transparent 5px, transparent 10px) 1;
}

兼容性一致的虚线边框

三、反馈态中的应用

移动端开发者,可交互元素需要有点击反馈态,如下所示:

反馈态效果示意

通常做法是:

a[href]:active, button:active {
    background-image: linear-gradient(rgba(0,0,0,.05), rgba(0,0,0,.05));
}

如果元素本身有 background-image 图形,我们还可以使用 box-shadow 属性模拟:

button:active {
    box-shadow: inset 0 0 0 999px rgba(0,0,0,.05);
}

如果盒阴影也被占用,则还有一招,那就是 border-image

button:active {
    border-image: linear-gradient(rgba(0,0,0,.05), rgba(0,0,0,.05)) 1 fill;
}

特别适合无边框的场景,效果示意:

border-image按钮点击反馈

如果到这里就结束,那就太小儿科了,border-image 还有个厉害的子属性 border-image-outset,可以扩展边框图像的范围,这个可是个大杀器。

先从我最近遇到的一个实际交互场景说起。

子项active穿透

描述为点击输入框,整个列表项出现反馈态,如果是点击其他位置,则没有任何反馈。

如果是你,你会怎么实现?

有一种做法是这样的:

.list:focus-within:active {
    background-image: linear-gradient(rgba(0,0,0,.05), rgba(0,0,0,.05));
}

但是,如果列表中同时有按钮,就会有问题,因为点击按钮的时候,也会触发此选择器的执行,这并不是我们希望的效果,这个时候,就轮到 border-image 属性出马的时候了。

list {
    overflow: clip;    
}
input:active {
    border-image: linear-gradient(rgba(0,0,0,.05), rgba(0,0,0,.05)) 1 fill / / 100vw;
}

对内 fill 填充,对外 outset 扩展,填满整个区域。

对比其他实现

  • background-image 无法扩展;
  • outline 则是层级不对,会覆盖输入框的文字内容。
    input:active {
        outline: 199vw solid rgba(0,0,0,.05);
        outline-offset: -999px;
    }

    outline覆盖输入框文字示意

  • box-shadow 层级在文字之下,不过需要要求元素不能有边框宽度,否则就会有部分区域的颜色镂空:
    input:active {
        box-shadow: 0 0 0 199vw rgba(0,0,0,.05), inset 0 0 0 999px rgba(0,0,0,.05);
    }

    box-shadow边框问题

于是,综合来看,在这个场景中,border-image 属性属于最佳实现。

四、绝杀:在元素之外构建图像

在元素之外构建纯色有 outlinebox-shadow 这两个对手,那如果有需求,希望在元素之外构建图像(甚至是元素显示),那么 border-image 属性就是个王炸。

例如,希望在 <progress> 进度条上方同时显示进度数值,在不影响布局,不额外创建元素,兼容所有现代浏览器的方法一定只有 border-image 属性。

<progress value=".5"></progress>
progress {
    width: 300px; height: 6px;
    --img: url("data:image/svg+xml,%3Csvg width='300' height='30' xmlns='http://www.w3.org/2000/svg'%3E%3Ctext x='50%25' y='50%25' font-size='14' fill='%232a80eb' font-family='system-ui, sans-serif' text-anchor='middle' dominant-baseline='middle'%3E50%%3C/text%3E%3C/svg%3E");
    border-image: var(--img) 0 fill / / 26px 0 0;
}

效果如下截图所示:

元素外显示数字

立足于实际开发,这种构建需要和 JS 配合,走组件模式,图像跟随元素实时动态生成,因为 border-image 在元素的任意位置(包括元素外)显示图像是有一些局限的。

局限

有如下局限:

  • 只能一个图像,极限是两个图像,使用 cross-fade() 图像函数;
  • 填充模式只能是拉伸,也就是 border-image 图像一定是拉伸填充元素区域,这使得图像尺寸和元素尺寸需要有匹配的对应关系,否则图像无法保持原始比例。

    通常做法是通过 border-image-width 属性约束最中心填充区域的大小,使填充图像保持原始比例(见最后那个邮箱输入框验证的案例)。

  • 由于层级限制,无法在元素上方显示,会被内容覆盖。
  • 会影响原本边框的显示,如果同时需要有边框效果,需要使用 box-shadow 模拟边框。

总结

由于元素显示区域外的图像显示可以通过伪元素绝对定位实现,因此,border-image 处理区域外图像就适合用在不支持伪元素的 HTML 元素上,例如:<input><textarea><img><canvas><progress><video> 等元素。

以及可以使用伪元素,但是不方便使用绝对定位、或者容器设置了 overflow:hidden 的场景,要知道,border-image 图像效果不会因为元素设置了 overflow:hidden 而不可见,同时不会占据额外的尺寸,也就是无论如何显示,都不会影响布局。

再演示个效果

这里再演示一个验证效果实现,先看下效果。

边框图像模拟的验证

此效果纯 CSS 实现,单 <input> 标签实现:

邮箱:<input type="email" class="use-border" placeholder="输入邮箱观察变化" required>
[type="email"] {
    width: 240px; padding: 10px;
    font-size: 100%;
    border: 1px solid transparent;
    background: #fff;
    box-shadow: inset 0 0 0 1px #ccc;
    outline: none;
    --valid: url("data:image/svg+xml,%3Csvg...'/%3E%3C/svg%3E");
    --invalid: url("data:image/svg+xml,%3Csvg...%3E%3C/svg%3E");
}
[type="email"]:focus {
    box-shadow: inset 0 0 0 1px #2a80eb;
}
.use-border {
    /* 20px 图标尺寸,272 是输入框长度 262 + 10px 偏移 */
    border-image: 0 fill / 9 0 9 272 / 0 30px 0 0;
}
.use-border:valid {
    border-image-source: var(--valid);
}
.use-border:invalid:not(:placeholder-shown) {
    border-image-source: var(--invalid);
}

最关键的实现就是这么一句:

border-image: 0 fill / 9 0 9 272 / 0 30px 0 0;

其中:

  • 0 fill 表示不剪裁,直接填充到边框九宫格的中心位置;
  • 9 0 9 272 表示 border-image-width,用来确定一个 20 * 20 大小的中心区域,因为输入框高度 38px,宽度262px,图标偏移在右侧 30px 的地方,因此,才有右边框大小是 0,左边框大小是 272(262 – 20 + 30);
  • 0 30px 0 0 表示 border-image-outset 扩展大小,这里只往右扩展了 30px 大小。

这里例子有页面演示,眼见为实,您可以狠狠地点击这里:CSS border-image 实现输入框验证提示demo

多嘴一句,如果图像是要外挂在元素的四个边角,则理解上要相对简单些,此时,通过利用九宫格 4 个边角实现即可,难点在于一开始的 border-image-slice,假设图像的原始尺寸是 200px * 200px,需要定位在右上角,以 20px 大小显示,则代码是这样的:

border-image: url(...) 200 200 0 0 / 20 20 0 0 / 0;

是不是有些不理解?那就对了,《CSS新世界》看起来,border-image 还是有些厉害的!

五、扯点其他的再结语

虽然不推荐,但是难免有人会遇到需要对 <img> 图像等替换元素进行点击反馈效果的实现,此时,本文提到的 3 种方法全部都是中国男足——凉凉!

img:active {
    /* 看不见 */
    background-image: linear-gradient(rgba(0,0,0,.05), rgba(0,0,0,.05));
    /* 看不见 + 1*/
    box-shadow: inset 0 0 0 999px rgba(0,0,0,.05);
    /* 看不见 + 2 */
    border-image: linear-gradient(rgba(0,0,0,.05), rgba(0,0,0,.05)) 1 fill;
}

因为图像属于内容,层叠顺序比较高,此时可以使用滤镜实现:

img:active {
    filter: brightness(.95);
}

本文最后那个邮箱输入后面自动显示对应验证图标的案例其实也可以使用 background-image 模拟。

.use-background {
    border-right-width: 30px;
    background: no-repeat right / 20px 20px;
    background-origin: border-box;
}
.use-background:valid {
    background-image: var(--valid);
}
.use-background:invalid:not(:placeholder-shown) {
    background-image: var(--invalid);
}

不过这个实现虽然视觉上好像也 OK,但是,右侧的提示图标占据了布局空间,并且是输入框的一部分,点击是能够触发输入框的 focus 状态的,没有 border-image 实现的交互状态好。

对比示意

好,以上就是本文的全部内容了。

对了,最近在自己独立小说网站“日紫烟”更新百万字长篇连载小说,轻小说带奇幻,欢迎品鉴,点击这里开始阅读

感谢阅读,欢迎一键三连,哦,走错场了,不是视频,那欢迎分享,比心思密达!

(本篇完)

分享到:


发表评论(目前14 条评论)

  1. 阿斯顿说道:

    大佬怎样能让这个渐变转起来

  2. 苏水儿说道:

    //lol.qq.com/act/a20231114heartsteelpass/index.html?e_code=500130

    英雄联盟活动页,随便点击一个按钮触发弹框。这个弹框的边框实现就是用的border-image,但我看郁闷了。没看明白这个属性的规则;
    求大佬解析

  3. senna说道:

    大佬求助,iPhone11上border-image四周有一圈2px左右的透明有遇到过吗,别的iPhone上好像没问题,元素上只有这些css
    element.style {
    overflow-x: auto;
    white-space: nowrap;
    border-image: url(src) 0 fill / 1 / 0 stretch;
    padding: 11.4667vw 5.33333vw 5.33333vw;
    position: relative;
    box-sizing: border-box;
    }

  4. 吕业浩说道:

    我想问问关于文章中 出现 border-image: 0 fill / 9 0 9 272 / 0 30px 0 0;
    这个声明块中 // 是什么语法
    原先以为是替代符号,用在其他简写形式的属性时是故障的。
    而且这个vscode插件的检查还飘红
    不太懂,求赐教

  5. 东东说道:

    不错

  6. 单手干翻尼米兹说道:

    大佬,针对个位,是不是太简单了,建议换成 各位

  7. Young说道:

    > border-image 属性从此沦为了积累,

    “积累”应为“鸡肋”

  8. whc说道:

    想咨询个问题。我们的系统有个场景:固定区域为了显示尽可能多的文字并且样式比较合适,会随着文字的多少动态的改变文字的字号,文字小到一定程度时再去省略。

    目前我们的方式是根据字数进行分段,1-10,11-20,21-30每段一个字号

  9. 黑羽说道:

    感觉最初的那个感觉有点像安卓上那个 9patch,可以做聊天气泡