普通视图

发现新文章,点击刷新页面。
昨天 — 2026年1月12日首页

我被 border-image 坑了一天,总算解决了

作者 吴敬悦
2026年1月12日 16:53

你不会还不知道 border-image 吧,那我跟你普及一下:

在元素的边框位置画图像,而不是常见的 solid ,dashed 这些线条,线段等。具体使用请参考# border-image 详解

现在才明白, border-image 如果理解错了,可能就要多花费很久的时间,就跟我这次一样。

先说避坑指南:

  1. border-image-slice 用设计稿尺寸,应该使用图片中的像素;
  2. 没有认真分析图片直接开切,弄明白哪些需要拉伸,哪些不需要,然后再去得到尺寸;
  3. 如果你切的尺寸不同,需要弄明白 border-image-width 绘制宽度。

故事的开始是这样的。

设计图是这样的:

刚开始的思路有:

  • 内容部分和外面的 QA 圆圈分开,也就是里面内容写上边框,但是我发现右下角边框只是占了一点点,并不是全部,而 border 设置边框要不就是一边,所以这种方法行不通;
  • 全部使用绝对定位弄上去,因为外面我可以使用 svg 整体,但是这样存在一个问题,就是里面的内容并不是高度一致的,当高度变高或者变窄了就会出现拉伸,当然 svg 默认不拉伸而是居中显示,当然也是不符合我的要求,所以这种方法也行不通;
  • 最笨的方法就是分成几块绝对定位,也就是 Q边框A 和对应的的那个下边线 ,可以实现,但是不够优雅,所以这种方法暂不考虑;
  • 可以发现这个都在边框的位置,那么可以使用 border-image 来实现,顺便把中间的背景白色也弄成图中的一部分,这样里面的内容就不需要再设置 padding 了,理论可行于是我就开始实践。

避坑指南1: border-image-slice 用设计稿尺寸

border: 36px solid transparent;
border-image: url(./images/qa.png) 36;
/*
是以下的简写:
border-image-source: url(./images/qa.png);
border-image-slice: 36;
*/

于是就变成了下面这个样子:

这是啥,咋成这样了;难道是我 border-image-slice 不对嘛,可是设计图就是 36 呀;于是我再次检查了设计图,发现的确是这么多,那可能是我对这个属性的理解不对,先看看 border-image-slice 文档。

表示到图像边缘的偏移量,在位图中的单位为像素点,在矢量图中则是坐标。对于矢量图,<number> 值与元素大小相关,而非矢量图的原始大小。因此,使用矢量图时,使用百分比值(<percentage>)更可取。

原来是图片的偏移量,像素点,不是设计图的,于是我根据图片比例算了算得到了 36 / (352/1259) = 128.76136363636363

border: 36px solid transparent;
border-image: url(./images/qa.png) 128.76136363636363;
/*
相当于下面的写法:
border-image-source: url(./images/qa.png);
border-image-slice: 128.76136363636363;
*/

首先 Q 正常了,下边的 A 明显有变形,同时中间的白色并没有覆盖。

避坑指南2: 不分析图片直接开切

拿到图片要分析哪些部分需要拉伸,哪些部分不需要拉伸。

首先思考 A 为啥会变形,我们知道 slice 是将图片分割成 9 部分,拉伸除了四个角的其他部分,而我 slice 设置的是一个值,一个值代表四边都是这么多,很明显图片 A ,也就是右边包含 A 部分要大,所以右边部分还需要单独设置。

--slice: calc(36 / (352/1259));
border: 36px solid transparent;
border-image: url(./images/qa.png) var(--slice) calc(57.5 / (352/1259)) var(--slice) var(--slice);
/*
相当于下面的写法:
border-image-source: url(./images/qa.png);
border-image-slice: var(--slice) calc(57.5 / (352/1259)) var(--slice) var(--slice);
*/

可以看到右边仍然变形,只不过跟之前相比被挤压了,为啥??于是我把对应的 9 宫格画出来研究一下,结果不画不知道,一画就发现了问题:

避坑指南3: 当切的宽度不同时,需要考虑绘制宽度,不然就会问不是说好的 1/2/3/4 不拉伸嘛

根据上图看到由于边框大小都是 36 ,即便我把右边的 slice 改大了,但是仍然是在 36 这个大小内绘制,既然可绘制的宽度大小没变,那么要想画完整要么拉伸,要么缩小,而这里采用的就是拉伸,我猜为啥不采用缩小,是因为要保证图画的连续性,比较跟图片明显拼接相比,拉伸至少还知道是同一张图片。既然是右边的边框宽度不够导致的,那么我设置后边长度的宽度。

--slice: calc(36 / (352/1259));
border: 36px solid transparent;
border-right-width: 57.5px;
border-image: url(./images/qa.png) var(--slice) calc(57.5 / (352/1259)) var(--slice) var(--slice);
/*
相当于下面的写法:
border-image-source: url(./images/qa.png);
border-image-slice: var(--slice) calc(57.5 / (352/1259)) var(--slice) var(--slice);
*/

现在看起来就没问题了;但是看到右边的边框由于宽度太宽,导致当内容过多的时候会提前换行,并没有做到跟左边差不多,所以这样是不行的,于是我又去学习了一波 border-image-width ,这个属性是调整图片绘制宽度的,于是我改成了这样:

--slice: calc(36 / (352/1259));
border: 36px solid transparent;
border-image: url(./images/qa.png) var(--slice) calc(57.5 / (352/1259)) var(--slice) var(--slice) / 1 57.5px 1 1;
/*
相当于下面的写法:
border-image-source: url(./images/qa.png);
border-image-slice: var(--slice) calc(57.5 / (352/1259)) var(--slice) var(--slice);
border-image-width: 1 57.5px 1 1; 
*/

感觉跟上面完全一样,但实际上这个时候能变长就会正常了,我把内容增加就能看到了。

现在就剩下中间部分了,默认情况下 border-image 是不会绘制到除了 border 以外的地方的,如果需要铺满则需要 slice 中添加 fill 属性。

fill

保留图像的中心区域并将其作为背景图像显示出来,但其会堆叠在 background 之上。它的宽度和高度分别对应顶部和左侧图像切片的宽度和高度。

也就是这样设置:

--slice: calc(36 / (352/1259));
border: 36px solid transparent;
border-image: url(./images/qa.png) var(--slice) calc(57.5 / (352/1259)) var(--slice) var(--slice) fill / 1 57.5px 1 1;
/*
相当于下面的写法:
border-image-source: url(./images/qa.png);
border-image-slice: var(--slice) calc(57.5 / (352/1259)) var(--slice) var(--slice) fill;
border-image-width: 1 57.5px 1 1; 
*/

下面就是成果:

可算是解决了。其实右上角的圆角还存在一定的瑕疵,因为圆角的那个位置发生了拉伸,我只需要将 slice 上边调整大一些就解决了。

教训

通过我的惨痛教训,我们必须要记住,这样大家就不会再遇到,即便遇到了也可以通过我的避坑指南快速解决。

  • 必须要先分析图片,哪些应该拉伸,哪些不拉伸;
  • border-image-slice 的数值,永远基于图片原始尺寸,而不是设计稿;
  • 还有一点我没说到,也就是当使用百分比设置 border-image-slice 的时候,上下使用原图片的高度,左右使用图片的宽度。
❌
❌