为什么我们会有 `repeating-linear-gradient`?

Avatar of Ana Tudor
Ana Tudor

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 $200 免费积分!

一切都始于 Keith Clark 最近在推特上提出的 这个问题

CSS `repeating-linear-gradient`,我们需要它们吗?难道不能用 `linear-gradient` 和 `background-size` 达到相同的效果吗?

这是一个好问题。

它们的表亲 `repeating-radial-gradient` 绝对派得上用场。当我想用 CSS 重现 黑胶唱片 的外观时,它们免去了我在普通 `radial-gradient` 中编写数十个停止点的麻烦。

使用 `repeating-radial-gradient` 的黑胶唱片

但是 `repeating-linear-gradient` 呢?

直到今年,我还坚信它们毫无用处。但在 2 月初,我在制作 一个画布演示 时,陷入了为演示设计控制面板的陷阱,最终复制了我在网上找到的 超过 100 种滑块设计,每个设计都带有 (自我强加的) 限制,即每个滑块只能使用一个范围输入。

滑块示例

警告:虽然我确实在创建它们的过程中学到了很多,但我从未回头将沿途学到的知识应用到我已经编码的滑块上,因此,除了最后几个之外,我不建议将它们作为良好的资源。此外:如果我再也不用为另一个范围输入设置样式,那就太好了。

这些带样式的范围输入中有一些带有条纹填充或标尺,因此它们需要使用渐变,尤其是 `repeating-linear-gradient`,相当频繁。我使用 `repeating-linear-gradient` 正是因为事实证明普通 `linear-gradient` 不够用。让我们看看为什么!

我们首先重现以下图案

简单条纹图案

它有黑色和蓝色条纹,蓝色条纹是黑色条纹的两倍宽。如果黑色条纹宽度为 `0.25em`,则使用 `repeating-linear-gradient` 获得此效果的代码为

background: repeating-linear-gradient(135deg, 
  #000, #000 .25em /* black stripe */, 
  #0092b7 0, #0092b7 .75em /* blue stripe */
);

渐变从左上角以 `135°` 的角度开始。如果您需要关于线性渐变及其角度如何工作的复习,请查看 此解释(忽略有关旧语法的部分,我们现在已经抛弃了它)。沿渐变线有一个从 `0` 到 `0.25em` 的黑色条纹,然后是一个从 `0.25em` 到 `0.75em` 的蓝色条纹(`0.75em - 0.25em = 0.5em = 蓝色条纹宽度 = 2*0.25em = 黑色条纹宽度的两倍`)。我们可以在这个 Pen 中看到它在行动

查看 Ana Tudor (@thebabydino) 在 CodePen 上的 Pen 简单的 `repeating-linear-gradient` 图案

现在让我们尝试用纯 `linear-gradient` 和 `background-size` 达到相同的效果。我们将背景设置为一个小的正方形,对角线加起来等于黑色条纹(`0.25em`)和蓝色条纹(`0.5em`)的宽度。正方形的对角线是正方形边长乘以 `√2`,如果我们知道对角线是 `0.25em + 0.5em = 0.75em`,那么 `background-size` 正方形的边长将是 `0.75em/sqrt(2)`。这将我们引入了以下代码

background: linear-gradient(135deg, #000 .25em, #0092b7 0);
background-size: .75em/sqrt(2) .75em/sqrt(2);

嗯,这不像我们想要实现的效果……

查看 Ana Tudor (@thebabydino) 在 CodePen 上的 Pen 简单的 `linear-gradient` 图案 - 第 1 步

如果我们将宽度为 `0.25em` 的黑色条纹放在中间会怎么样?让我们试试!

background: linear-gradient(135deg, 
  #0092b7 calc(50% - .125em) /* blue corner */, 
  #000 0, #000 calc(50% + .125em) /* black stripe */, 
  #0092b7 0 /* blue corner */
);
background-size: .75em/sqrt(2) .75em/sqrt(2);

现在它更接近了,但看起来仍然不好。

查看 Ana Tudor (@thebabydino) 在 CodePen 上的 Pen 简单的 `linear-gradient` 图案 - 第 2 步

如果我们放大,就会更清楚地看到问题所在以及我们需要做些什么。我们必须用黑色填充这些角落。

放大后的角落问题

这意味着在渐变线的开头和结尾添加一个小的黑色条纹。每个条纹将是初始黑色条纹宽度的一半(因此 `0.25em` 的一半,即 `0.125em`)。

background: linear-gradient(135deg, 
  #000 .125em /* black corner */, 
  #0092b7 0, #0092b7 calc(50% - .125em) /* blue stripe */, 
  #000 0, #000 calc(50% + .125em) /* black stripe */, 
  #0092b7 0, #0092b7 calc(100% - .125em) /* blue stripe */, 
  #000 0 /* black corner */
);
background-size: .75em/sqrt(2) .75em/sqrt(2);

看起来……稍微好点了吗?

查看 Ana Tudor (@thebabydino) 在 CodePen 上的 Pen 简单的 `linear-gradient` 图案 - 第 3 步

它仍然不是我们想要的,因为现在蓝色条纹太窄了。这是因为对角线的计算不再正确。从左上角到右下角,我们现在有半个黑色条纹(`0.125em`)、一个蓝色条纹(`0.5em`)、一个黑色条纹(`0.25em`)、另一个蓝色条纹(`0.5em`)和另一个半个黑色条纹(`0.125em`)。这些加起来总共是 `1.5em` 的对角线,这意味着我们需要将 `background-size` 更改为 `1.5em/sqrt(2) 1.5em/sqrt(2)`

查看 Ana Tudor (@thebabydino) 在 CodePen 上的 Pen 简单的 `linear-gradient` 图案 - 第 4 步

完美!它在视觉上与 `repeating-linear-gradient` 版本的结果相同。但在这种情况下,工作量太大,计算太多,代码太多,舍入误差的风险太大(如果我们将 `font-size` 设置为像 `1.734em` 这样不会计算为整数像素值的怪异值,上面的演示将中断)。

如果你还没有相信 `repeating-linear-gradient` 在这种情况下是更好的解决方案,那么让我们想象一下渐变角度不是 `135°`,而是其他什么……比如 `120°`。

简单条纹图案(`120°`)

使用 `repeating-linear-gradient`,代码几乎与之前相同,我们只需要将 `135deg` 替换为 `120deg`。

查看 Ana Tudor (@thebabydino) 在 CodePen 上的 Pen 简单的 `repeating-linear-gradient` 图案 v2

在 Firefox 和 Edge/IE 中一切似乎都很好,但从蓝色到黑色的过渡在 Chrome 中看起来不好。我们可以通过不使过渡变锐利,在两者之间保留 `1px` 的距离来修复它。

background: repeating-linear-gradient(120deg, 
  #0092b7 0, 
  #000 1px /* transition from previous blue stripe */, #000 .25em, 
  #0092b7 calc(.25em + 1px) /* from black to blue */, #0092b7 .75em
);

我们使用这个 Pen 来测试这是否修复了 Chrome 问题

查看 Ana Tudor (@thebabydino) 在 CodePen 上的 Pen 简单的 `repeating-linear-gradient` 图案 v2(Chrome 修复)

对于普通的 `linear-gradient`,我们也需要将角度从 `135deg` 更改为 `120deg`。但这还不够。

查看 Ana Tudor (@thebabydino) 在 CodePen 上的 Pen 简单的 `linear-gradient` 图案 v2 - 第 1 步

我们还需要更改 `background-size` 的尺寸,因为它不再是正方形。`x` 维度将是 `1.5em*abs(cos(120deg))`,而 `y` 维度将是 `1.5em*abs(sin(120deg))`。

查看 Ana Tudor (@thebabydino) 在 CodePen 上的 Pen 简单的 `linear-gradient` 图案 v2 - 第 2 步

再次,边缘在 Chrome 中看起来不好,因此我们需要使用 `1px` 间距技巧。

查看 Ana Tudor (@thebabydino) 在 CodePen 上的 Pen 简单的 `linear-gradient` 图案 v2(Chrome 修复)

`linear-gradient` 替代方案并不美观,但它存在。

`repeating-linear-gradient` 能做到普通 `linear-gradient` 和适当的 `background-size` 根本无法做到的事情吗?

嗯……确实可以!

如果您注意到了开头显示的滑块示例,上面复制的条纹渐变用于在滑块拇指之前(拇指左侧)填充滑块轨道。

IE 和 Firefox 为此拥有专门的伪元素(分别为 `-ms-fill-lower` 和 `-moz-progress`),我们只需要在这些伪元素上添加之前创建的背景即可。

但对于 WebKit 浏览器来说,获得条纹填充的唯一方法是在用于滑块轨道的普通背景之上添加另一个背景,然后随着滑块拇指的调整更新它覆盖的轨道部分。现在这给普通的 `linear-gradient` 带来了一个大问题。我们无法通过 `background-size` 控制它覆盖的轨道部分,因为我们已经在使用 `background-size` 来创建条纹外观。除此之外,渐变要么无限重复,要么根本不重复。

如果我们能够像为 `animation-iteration-count` 指定数字值一样,为 `background-repeat` 指定数字值,那可能会很酷。像 `background-repeat: 0.5 1.5` 这样的东西会使背景的一半水平显示并具有一个半瓷砖垂直显示。但我们做不到。也许我们有其他方法让 `linear-gradient` 仅在某些限制内重复?

嗯,我们用普通的 `linear-gradient` 和 `background-size` 创建接近我们想要的东西的唯一方法是叠加相同的渐变并一个接一个地放置它们,同时不允许它们至少在一个方向上重复。

例如,如果我们想让它们只水平覆盖元素的一部分,我们将不得不设置 `background-repeat: repeat-y`。第一个渐变背景将从 `0*$size-x` 水平开始,第二个从 `1*$size-x` 开始,依此类推……其中 `$size-x` 是 `background-size` 的 `x` 组件。下面的图片说明了它是如何工作的

背景按行一个接一个地放置

但这只适用于以 `$size-x` 的增量更改条纹部分覆盖的轨道部分,如果我们想要更精细的控制,它就无用。此外,将 20 个相同的渐变叠加在一起真的很丑。

background-image: 
  linear-gradient(135deg, 
      #000 0.125em, 
      #0092b7 0, #0092b7 calc(50% - .125em), 
      #000 0, #000 calc(50% + .125em), 
      #0092b7 0, #0092b7 calc(100% - .125em), #000 0
  ), 
  linear-gradient(135deg, 
      #000 0.125em, 
      #0092b7 0, #0092b7 calc(50% - .125em), 
      #000 0, #000 calc(50% + .125em), 
      #0092b7 0, #0092b7 calc(100% - .125em), #000 0
  ) /* repeat as many times as needed */;
;
background-position: 
  0*1.5em/sqrt(2) 0, 1*1.5em/sqrt(2) /* repeat for 2, 3, 4 and so on... */;
background-repeat: repeat-y;

Ugh。重复 20 次完全相同的渐变太糟糕了。我们可以让 Sass 变得更好,但生成的 CSS 仍然看起来很丑

$grad: linear-gradient(135deg, 
      #000 0.125em, 
      #0092b7 0, #0092b7 calc(50% - .125em), 
      #000 0, #000 calc(50% + .125em), 
      #0092b7 0, #0092b7 calc(100% - .125em), #000 0
  );

background-image: $grad, $grad /* repeat as many times as needed */;
background-position: 
  0*1.5em/sqrt(2) 0, 1*1.5em/sqrt(2) /* repeat for 2, 3, 4 and so on... */;
background-repeat: repeat-y;

下面的 Pen 展示了这种方法的实际应用

查看 CodePen 上 Ana Tudor(@thebabydino)的示例 在特定范围内约束 linear-gradient

简而言之,我的建议是:不要这样做!代码很糟糕且容易出错,我们很容易在某些背景大小或缩放时出现渐变之间的间隙。

我们有更好的方法可用。我们可以通过使用 background-size 并将 background-repeat 设置为 no-repeat 来限制 repeating-linear-gradient 覆盖元素的程度(无论重复部分的尺寸如何)。

查看 CodePen 上 Ana Tudor(@thebabydino)的示例 在特定范围内约束 repeating-linear-gradient

这就是我能够控制滑块轨道上填充条纹渐变的程度的方式,每次滑块滑块的值更新时,都会通过更改 background-sizex 分量来实现。

同样的想法有助于限制标尺在不希望它们延伸到滑块整个长度的情况下延伸的程度。假设我们还希望它们水平居中,这使得 background-positionx 分量为 50%。这就是一个非常简单的标尺的创建方式

width: 19em;
background: 
  repeating-linear-gradient(90deg, 
      #c8c8c8, #c8c8c8 0.125em /* lines */, 
      transparent 0.125em, transparent 1.25em /* space between */
  ) 50% no-repeat;
background-size: 12.625em /* = 10*1.25em + .125em */ .5em;

我们可以在这个示例中看到结果

查看 CodePen 上 Ana Tudor(@thebabydino)的示例 简单的标尺不覆盖整个元素

为了同时拥有主要线和次要线,我们需要添加第二个重复渐变,它以第一个渐变的两倍间隔重复,并且高度是第一个渐变的两倍。

background: 
  repeating-linear-gradient(90deg, /* major */
      #c8c8c8, #c8c8c8 .125em /* lines */, 
      transparent , transparent 2.5em /* space between */
  ) 50% no-repeat, 
  repeating-linear-gradient(90deg, /* minor */
      #c8c8c8, #c8c8c8 .125em /* lines */, 
      transparent , transparent 1.25em /* space between */
  ) 50% no-repeat;
background-size: 12.625em 1em, 12.625em .5em;

可以在这个示例中看到结果

查看 CodePen 上 Ana Tudor(@thebabydino)的示例 标尺不覆盖整个元素 #2

我们还可以调整 background-positiony 分量,使其与顶部或底部对齐。例如,我们可以做以下操作

background-position: right 50% bottom 2.25em;

这正是开头示例中使用的标尺类型。

查看 CodePen 上 Ana Tudor(@thebabydino)的示例 标尺不覆盖整个元素 #3

以下示例中的滑块滑块是以完全相同的方式创建的。

滑块滑块

有两个相同的重复线性渐变,它们都水平限制为重复部分的两倍大小加上不透明部分的宽度。水平方向上,第一个位于 25%,第二个位于 75%

background: 
  repeating-linear-gradient(90deg,
    #6b4c1e, #6b4c1e 1px, 
    #e1ba75 0, #e1ba75 2px, 
    transparent 0, transparent 4px
  ) 25% 50% /* left */, 
  repeating-linear-gradient(90deg,
    #6b4c1e, #6b4c1e 1px, 
    #e1ba75 0, #e1ba75 2px, 
    transparent 0, transparent 4px
  ) 75% 50% /* right */
  orange;
background-repeat: no-repeat;
background-size: 10px 10px;

但这是一种非常 WET 的代码编写方式。我们可以使用 Sass 使其更易于维护。

$grip: repeating-linear-gradient(90deg,
  #6b4c1e, #6b4c1e 1px, 
  #e1ba75 0, #e1ba75 2px, 
  transparent 0, transparent 4px); /* we use this twice */

background: 
  $grip 25% 50% /* left */, 
  $grip 75% 50% /* right */
  orange;
background-repeat: no-repeat;
background-size: 10px 10px;

这将给我们以下结果

查看 CodePen 上 Ana Tudor(@thebabydino)的示例 滑块滑块

重复渐变 曾经是一个真正的难题,我们仍然会遇到 细渐变线的问题,尤其是在覆盖较大区域时。它们在 WebKit 浏览器中不会始终具有相同的宽度,并且 OS X 上的 Firefox 在渲染它们时不够精确(虽然 Windows 上的 Firefox 很少在这里失败)。

细斜线的相关问题

但是情况已经有了很大的改善,我相信它们会继续改善。即使在今年年初创建这些滑块时,我也看到了 Chrome 的稳定版本(41)和 canary 版本(43)之间的很大差异

Chrome 41 中的重复渐变与 Chrome 43 中的重复渐变

现在使用重复渐变要安全得多,我认为它们应该得到更多关注,因为它们可以使我们的生活更轻松,这使得它们在我看来很酷。