先记住这个答案
linear-gradient 在相邻两个色标之间做线性插值,从一种颜色平滑过渡到另一种。当两个色标写在同一个位置,比如 red 50%, blue 50%,插值区间的长度变成零,浏览器无法在零距离里做过渡,所以颜色直接从红跳到蓝,形成一条锐利的硬边。这不是 bug,而是插值算法的自然结果。
- 渐变本质是相邻色标之间的线性插值
- 色标位置相同时插值距离为零,颜色跳变
- 硬边条纹就是多对色标交替共用同一位置
- 后一个色标位置小于前一个时,浏览器会自动修正为相等
从普通渐变到硬边条纹
先看一个最简单的渐变:linear-gradient(red, blue)。浏览器默认把 red 放在 0%,blue 放在 100%,中间一百个百分点都在做颜色混合,所以看到的是柔和过渡。
如果把它改成 linear-gradient(red 50%, blue 50%),从 0% 到 50% 全是纯红(只有一个色标,没有混合对象),从 50% 到 100% 全是纯蓝,中间就是一条硬边。
/* 三条等宽硬边条纹 */
.stripe {
height: 200px;
background: linear-gradient(
red 0%, red 33.33%, /* 第一段纯红 */
white 33.33%, white 66.66%, /* 第二段纯白 */
blue 66.66%, blue 100% /* 第三段纯蓝 */
);
}每一对色标(比如 red 0%, red 33.33%)在自己的区间内保持纯色。相邻两对在同一个百分比交接,插值距离为零,形成硬边。这就是纯 CSS 条纹的原理。
浏览器的色标位置修正规则
规范规定:如果一个色标的位置比它前面那个色标小,浏览器会把它修正为和前一个相等。比如写 red 60%, blue 40%,blue 的 40% 会被修正成 60%,效果和 red 60%, blue 60% 一样,照样出现硬边。
这个规则经常被用来简化写法。比如画一个简单的进度条背景,前半段绿色后半段灰色,可以写成 linear-gradient(green var(--progress), #eee 0%)。#eee 的位置是 0%,比前面的 var(--progress) 小,会被自动修正到和它相等,刚好形成硬边分界。
容易答错的地方
- 以为硬边是浏览器渲染精度不够
- 硬边不是精度问题。浏览器按照规范在两个色标之间做线性插值,距离为零就是零,不存在「渲染精度不足导致过渡消失」的情况。这是算法层面的确定性行为。
- 忘了色标位置会被自动修正
- 如果后一个色标位置写得比前一个小,比如
red 80%, blue 20%,浏览器不会倒着画,而是把20%修正成80%。不知道这条规则的话,很容易以为代码写错了却看到了「正确」的硬边效果,排查不出原因。
面试官还会怎么问?
怎么用渐变画一个 45 度的斜条纹?
在 linear-gradient 第一个参数写角度,比如 45deg,然后用 repeating-linear-gradient 配合硬边色标。每一段条纹的宽度由相邻色标的位置差决定。repeating-linear-gradient(45deg, red 0 10px, white 10px 20px) 就能画出 10px 宽的红白斜条纹。
硬边渐变能做动画吗?
可以,但不能直接对 background 做 transition,因为渐变不是可插值类型。要实现进度条动画,通常把色标位置写成 CSS 自定义属性,用 @property 注册为 <percentage> 类型,这样浏览器就知道怎么对它做插值了。
repeating-linear-gradient 和手写多段色标有什么区别?
repeating-linear-gradient 会自动把你定义的色标段循环铺满整个元素,不用手动算每一段的百分比。手写多段色标更灵活,可以让每段宽度不一样,但条纹多的时候代码很长。效果上没有区别,都是硬边。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。