多拇指滑块:通用案例

Avatar of Ana Tudor
Ana Tudor

DigitalOcean 提供适用于您旅程各个阶段的云产品。立即开始使用 $200 免费信用额度!

本系列文章的 第一部分 详细介绍了如何获得双拇指滑块。现在,我们将研究一般的多拇指情况,但使用不同的、更好的技术来创建拇指之间的填充。最后,我们将深入了解如何设计逼真的 3D 风格滑块和扁平滑块。

文章系列

  1. 多拇指滑块:特定的双拇指案例
  2. 多拇指滑块:通用案例 (本文)

更好的、更灵活的方法

假设在一个与范围输入相同区域的包装器伪元素上,我们按从左到右的顺序堆叠与每个拇指对应的 linear-gradient() 图层。每个渐变层从轨道最小值到拇指中线是完全不透明的(即 alpha 为 1),之后是完全 transparent(即 alpha 为 0)。

请注意,RGB 值无关紧要,因为我们只关心 alpha 值。我个人在代码中使用 red(对于完全不透明的部分)和 transparent 关键字,因为它们可以以最少的字符完成工作。

我们如何计算渐变停止位置,即从完全不透明到完全 transparent 的位置?嗯,这些位置始终位于从左边缘到右边缘的一个拇指半径之间,因此它们处于等于有效宽度(轨道宽度减去拇指直径)的范围内。

这意味着我们首先添加一个拇指半径。然后,我们将当前拇指位置与最小值之差除以最大值与最小值之差(--dif)来计算进度。此进度值是 [0, 1] 区间内的数字——当当前拇指位置位于滑块的最小值时为 0,当当前拇指位置位于滑块的最大值时为 1。为了获得在该有效宽度区间中的确切位置,我们将此进度值乘以有效宽度。

我们想要的位置是这两个长度值的总和:拇指半径和我们在有效宽度区间中的位置。

下面的演示允许我们看到所有内容在 2D 视图中的堆叠方式,以及范围输入和它们父元素上的渐变在 3D 视图中的分层方式。它也是交互式的,因此我们可以拖动滑块拇指,并查看相应的填充(由父元素伪元素上的渐变层创建)如何变化。

请查看 thebabydino 在 CodePen 上的 Pen。 (@thebabydino)

该演示最好在 Chrome 和 Firefox 中查看。

好的,但简单地堆叠这些渐变层并不能给我们想要的结果。

这里的解决方案是将这些渐变设置为 mask 图层,然后对它们进行 异或 操作(更准确地说,在 CSS 蒙版的情况下,这意味着对它们的 alpha 值进行异或操作)。

如果您需要复习异或操作的工作原理,这里有一个例子:给定两个输入,此操作的输出为 1,如果输入值不同(其中一个为 1,另一个为 0);如果输入值相同(两个都为 0 或两个都为 1),则输出为 0

异或操作的真值表如下所示

输入 输出
A B
0 0 0
0 1 1
1 0 1
1 1 0

您还可以通过以下交互式演示进行练习,在其中您可以切换输入值,并查看输出如何变化

请查看 thebabydino 在 CodePen 上的 Pen。 (@thebabydino)

在我们的案例中,输入值是渐变 mask 图层沿水平轴的 alpha 值。对多个图层进行异或操作意味着对最底部的两个图层进行异或操作,然后将第三个图层从底部与之前的异或操作结果进行异或操作,依此类推。对于我们特定的从左到右的渐变案例,其 alpha 值在某个点(由相应拇指值决定)之前等于 1,之后为 0,其外观如下所示(我们从底部开始,向上操作)

SVG illustration. Illustrates the process described in the following three paragraphs.
我们如何对渐变层 alpha 值进行异或操作(演示)。

当最底部的两个图层的 alpha 值都为 1 时,对它们进行异或操作后获得的图层的 alpha 值为 0。当它们具有不同的 alpha 值时,结果图层的 alpha 值为 1。当它们都具有 0 的 alpha 值时,结果图层的 alpha 值为 0

向上移动,我们将第三层与我们在上一步中获得的结果图层进行异或操作。当这两个图层具有相同的 alpha 值时,此第二次异或操作产生的图层的 alpha 值为 0。当它们具有不同的 alpha 值时,结果 alpha 值为 1

类似地,我们然后将第四层从底部与第二阶段异或操作产生的图层进行异或操作。

在 CSS 中,这意味着使用标准 mask-compositeexclude 值和非标准 -webkit-mask-compositexor 值。(为了更好地理解 mask 合成,请查看 速成课程。)

这种技术可以给我们想要的结果,同时还允许我们对所有填充使用单个伪元素。这种技术适用于任意数量的拇指。让我们看看如何将它写成代码!

为了保持完全灵活性,我们首先修改 Pug 代码,使其能够通过简单地添加或删除拇指对象数组中的项目来添加或删除拇指并相应地更新所有其他内容,其中每个对象包含一个值和一个标签(仅供屏幕阅读器使用)

- let min = -50, max = 50;
- let thumbs = [
-   { val: -15, lbl: 'Value A' }, 
-   { val: 20, lbl: 'Value B' }, 
-   { val: -35, lbl: 'Value C' }, 
-   { val: 45, lbl: 'Value D' }
- ];
- let nv = thumbs.length;

.wrap(role='group' aria-labelledby='multi-lbl' 
      style=`${thumbs.map((c, i) => `--v${i}: ${c.val}`).join('; ')}; 
             --min: ${min}; --max: ${max}`)
  #multi-lbl Multi thumb slider:
    - for(let i = 0; i < nv; i++)
      label.sr-only(for=`v${i}`) #{thumbs[i].lbl}
      input(type='range' id=`v${i}` min=min value=thumbs[i].val max=max)
      output(for=`v${i}` style=`--c: var(--v${i})`)

在这些特定四个值的案例中,生成的标记如下所示

<div class='wrap' role='group' aria-labelledby='multi-lbl' 
     style='--v0: -15; --v1: 20; --v2: -35; --v3: 45; --min: -50; --max: 50'>
  <div id='multi-lbl'>Multi thumb slider:</div>
  <label class='sr-only' for='v0'>Value A</label>
  <input type='range' id='v0' min='-50' value='-15' max='50'/>
  <output for='v0' style='--c: var(--v0)'></output>
  <label class='sr-only' for='v1'>Value B</label>
  <input type='range' id='v1' min='-50' value='20' max='50'/>
  <output for='v1' style='--c: var(--v1)'></output>
  <label class='sr-only' for='v2'>Value C</label>
  <input type='range' id='v2' min='-50' value='-35' max='50'/>
  <output for='v2' style='--c: var(--v2)'></output>
  <label class='sr-only' for='v3'>Value D</label>
  <input type='range' id='v3' min='-50' value='45' max='50'/>
  <output for='v3' style='--c: var(--v3)'></output>
</div>

我们不需要在 CSS 或 JavaScript 中添加任何内容来实现功能滑块,其中 <output> 值会在我们拖动滑块时更新。但是,当包装器的 grid 仍然有两列时,拥有四个 <output> 元素会导致布局中断。因此,现在我们删除了为 <output> 元素引入的行,将这些元素绝对定位,并且只在相应的 <input> 获得焦点时才使它们可见。我们还删除了之前使用包装器上的两个伪元素的解决方案的残余部分。

.wrap {
  /* same as before */
  grid-template-rows: max-content #{$h}; /* only 2 rows now */

  &::after {
    background: #95a;
    // content: ''; // don't display for now
    grid-column: 1/ span 2;
    grid-row: 3;
  }
}

input[type='range'] {
  /* same as before */
  grid-row: 2; /* last row is second row now */
}

output {
  color: transparent;
  position: absolute;
  right: 0;
	
  &::after {
    content: counter(c);
    counter-reset: c var(--c);
  }
}

我们将在稍后对结果进行进一步美化,但现在,我们有以下内容

请查看 thebabydino 在 CodePen 上的 Pen。 (@thebabydino)

接下来,我们需要获得这些拇指到拇指的填充。我们通过在 Pug 中生成 mask 图层并将它们放在包装器上的 --fill 自定义属性中来实现这一点。

//- same as before
- let layers = thumbs.map((c, i) => `linear-gradient(90deg, red calc(var(--r) + (var(--v${i}) - var(--min))/var(--dif)*var(--uw)), transparent 0)`);

.wrap(role='group' aria-labelledby='multi-lbl' 
  style=`${thumbs.map((c, i) => `--v${i}: ${c.val}`).join('; ')}; 
    --min: ${min}; --max: ${max};
    --fill: ${layers.join(', ')}`)
  // - same as before

下面显示了四个拇指具有这些值的特定案例生成的 HTML 代码。请注意,如果我们在初始数组中添加或删除项目,此代码会自动更改

<div class='wrap' role='group' aria-labelledby='multi-lbl' 
  style='--v0: -15; --v1: 20; --v2: -35; --v3: 45; 
    --min: -50; --max: 50;
    --fill: 
      linear-gradient(90deg, 
        red calc(var(--r) + (var(--v0) - var(--min))/var(--dif)*var(--uw)), 
        transparent 0), 
      linear-gradient(90deg, 
        red calc(var(--r) + (var(--v1) - var(--min))/var(--dif)*var(--uw)), 
        transparent 0), 
      linear-gradient(90deg, 
        red calc(var(--r) + (var(--v2) - var(--min))/var(--dif)*var(--uw)), 
        transparent 0), 
      linear-gradient(90deg, 
        red calc(var(--r) + (var(--v3) - var(--min))/var(--dif)*var(--uw)), 
        transparent 0)'>
  <div id='multi-lbl'>Multi thumb slider:</div>
  <label class='sr-only' for='v0'>Value A</label>
  <input type='range' id='v0' min='-50' value='-15' max='50'/>
  <output for='v0' style='--c: var(--v0)'></output>
  <label class='sr-only' for='v1'>Value B</label>
  <input type='range' id='v1' min='-50' value='20' max='50'/>
  <output for='v1' style='--c: var(--v1)'></output>
  <label class='sr-only' for='v2'>Value C</label>
  <input type='range' id='v2' min='-50' value='-35' max='50'/>
  <output for='v2' style='--c: var(--v2)'></output>
  <label class='sr-only' for='v3'>Value D</label>
  <input type='range' id='v3' min='-50' value='45' max='50'/>
  <output for='v3' style='--c: var(--v3)'></output>
</div>

请注意,这意味着我们需要将与尺寸相关的 Sass 变量转换为 CSS 变量,并用 CSS 变量替换使用这些 Sass 变量的属性

.wrap {
  /* same as before */
  --w: 20em;
  --h: 4em;
  --d: calc(.5*var(--h));
  --r: calc(.5*var(--d));
  --uw: calc(var(--w) - var(--d));
  background: linear-gradient(0deg, #ccc var(--h), transparent 0);
  grid-template: max-content var(--h)/ var(--w);
  width: var(--w);
}

我们在包装器的 ::after 伪元素上设置 mask

.wrap {
  /* same as before */
  
  &::after {
    content: '';
    background: #95a;
    grid-column: 1/ span 2;
    grid-row: 2;

    /* non-standard WebKit version */
    -webkit-mask: var(--fill);
    -webkit-mask-composite: xor;

    /* standard version, supported in Firefox */
    mask: var(--fill);
    mask-composite: exclude;
  }
}

现在我们得到了想要的结果,而这种技术的真正妙处在于,要更改拇指数量,我们只需在 Pug 代码中的 thumbs 数组中添加或删除拇指对象(每个对象包含一个值和一个标签)——其他任何内容都不需要改变!

请查看 thebabydino 在 CodePen 上的 Pen。 (@thebabydino)

美化调整

到目前为止,我们得到的结果非常难看。所以让我们开始修复它!

选项 #1:逼真的外观

假设我们要实现以下结果

Screenshot. The track and fill are the same height as the thumbs. The track looks carved into the page, while the fill and the thumb have a convex look inside it.
我们想要实现的逼真的外观。

第一步是使轨道与拇指具有相同的 height,并圆角轨道末端。到目前为止,我们使用 .wrap 元素上的 background 来模拟轨道。虽然从技术上讲可以使用分层的线性渐变和径向渐变来模拟圆角轨道,但这并不是最好的解决方案,尤其是在包装器仍然有一个空闲的伪元素(::before)的情况下。

.wrap {
  /* same as before */
  --h: 2em;
  --d: var(--h);
  
  &::before, &::after {
    border-radius: var(--r);
    background: #ccc;
    content: '';
    grid-column: 1/ span 2;
    grid-row: 2;
  }
  
  &::after {
    background: #95a;

    /* non-standard WebKit version */
    -webkit-mask: var(--fill);
    -webkit-mask-composite: xor;

    /* standard version, supported in Firefox */
    mask: var(--fill);
    mask-composite: exclude;
  }
}

请查看 thebabydino 在 CodePen 上的 Pen。 (@thebabydino)

使用 `::before` 来模拟轨道打开了获得轻微 3D 外观 的可能性。

<pre rel="SCSS"><code class="language-scss">.wrap {
  /* same as before */
  
  &::before, &::after {
    /* same as before */
    box-shadow: inset 0 2px 3px rgba(#000, .3);
  }
  
  &::after {
    /* same as before */
    background: 
      linear-gradient(rgba(#fff, .3), rgba(#000, .3))
      #95a;
  }
}

我绝不是设计师,因此这些值可能需要调整才能获得更好看的结果,但我们已经可以看到差异了。

请参阅 thebabydino 在 CodePen 上创建的 Pen (@thebabydino)。

这让我们得到了一个非常难看的滑块,所以让我们也修复这一部分!

我们使用多层背景叠加的技巧,这些背景具有不同的 `background-clip`(和 `background-origin`)值。

@mixin thumb() {
  border: solid calc(.5*var(--r)) transparent;
  border-radius: 50%; /* make circular */
  box-sizing: border-box; /* different between Chrome & Firefox */
  /* box-sizing needed now that we have a non-zero border */
  background: 
    linear-gradient(rgba(#000, .15), rgba(#fff, .2)) content-box, 
    linear-gradient(rgba(#fff, .3), rgba(#000, .3)) border-box, 
    currentcolor;
  pointer-events: auto;
  width: var(--d); height: var(--d);
}

我在一篇 较旧的文章 中详细介绍了此技术。如果您需要复习,请务必查看!

然而,如果 `currentcolor` 值为黑色(`#000`)(目前就是这样),则上面的代码几乎不会做任何事情。让我们修复它,并更改滑块上的 `cursor` 到更适合的东西。

input[type='range'] {
  /* same as before */
  color: #eee;
  cursor: grab;
  
  &:active { cursor: grabbing; }
}

结果肯定比以前更令人满意。

请参阅 thebabydino 在 CodePen 上创建的 Pen (@thebabydino)。

令我非常困扰的另一件事是标签文本离滑块太近。我们可以通过在包装器上引入 `grid-gap` 来修复此问题。

.wrap {
  /* same as before */
  grid-gap: .625em;
}

但我们仍然面临的最糟糕的问题是,那些位于右上角的绝对定位的输出。解决此问题的最佳方法是为它们引入第三个 `grid` 行,并使用滑块将其移动。

滑块的位置的计算方式类似于我们用于填充 `mask` 的渐变层中尖锐停止点的位置。

最初,我们将输出的左边缘放置在距滑块左边缘一个滑块半径 `--r` 远的垂直线上。为了将输出与该垂直线居中对齐,我们将它们向后平移(向左,即 x 轴的负方向,因此我们需要一个减号)一半的 `width`(`50%`,因为 `translate()` 函数中的百分比值相对于应用了 `transform` 的元素的尺寸)。

为了使用滑块将其移动,我们从相应滑块的当前值(`--c`)中减去最小值(`--min`),将此差除以最大值(`--max`)和最小值(`--min`)之间的差(`--dif`)。这将为我们提供一个在 `[0, 1]` 区间内的进度值。然后,我们将此值乘以有效宽度(`--uw`),它描述了实际的运动范围。

.wrap {
  /* same as before */
  grid-template-rows: max-content var(--h) max-content;
}

output {
  background: currentcolor;
  border-radius: 5px;
  color: transparent;
  grid-column: 1;
  grid-row: 3;
  margin-left: var(--r);
  padding: 0 .375em;
  transform: translate(calc((var(--c) - var(--min))/var(--dif)*var(--uw) - 50%));
  width: max-content;
  
  &::after {
    color: #fff;
    content: counter(c);
    counter-reset: c var(--c);
  }
}

请参阅 thebabydino 在 CodePen 上创建的 Pen (@thebabydino)。

乍一看,这看起来好多了。但是,仔细检查后发现我们仍然存在很多问题。

第一个问题是,当我们到达轨道末尾时,`overflow: hidden` 会切断一部分 `` 元素。

为了解决这个问题,我们必须了解 `overflow: hidden` 究竟做了什么。它会切断元素 `padding-box` 外部的所有内容,如下面的交互式演示所示,您可以单击代码来切换 CSS 声明。

请参阅 thebabydino 在 CodePen 上创建的 Pen (@thebabydino)。

这意味着解决此问题的快速方法是在包装器 `.wrap` 上添加足够大的横向 `padding`。

padding: 0 2em;

我们在这里孤立地设计我们的多滑块滑块,但在现实中,它可能不是页面上的唯一元素,因此,如果间距有限,我们可以使用负横向 `margin` 来反转该横向 `padding`。

如果附近的元素仍然具有默认的 `position: static`,那么我们已经相对定位了包装器这一事实应该使输出位于它们重叠的内容之上,否则,调整 `.wrap` 上的 `z-index` 应该可以解决。

更大的问题是,我们使用的这种技术会导致拖动滑块时出现一些非常奇怪的 `` 重叠。

当 `` 聚焦于相应的 `` 时,增加 `z-index` 也会解决 `` 重叠的特定问题。

input[type='range'] {
  &:focus {
    outline: solid 0 transparent;
		
    &, & + output {
      color: darkorange;
      z-index: 2;
    }
  }
}

但是,它对潜在问题无济于事,当我们更改 `body` 上的 `background` 时,这一点就会变得很明显,特别是当我们将其更改为图像背景时,因为这不再允许 `` 文本隐藏在其中。

请参阅 thebabydino 在 CodePen 上创建的 Pen (@thebabydino)。

这意味着我们需要重新思考如何在正常状态下隐藏 `` 元素,以及如何在高亮状态(如 `:focus`)下显示它们。我们还希望在不臃肿 CSS 的情况下做到这一点。

解决方案是使用我在大约一年前在 “使用 CSS 变量进行 DRY 切换” 文章中描述的技术:使用一个高亮 `--hl` 自定义属性,该属性的值在正常状态下为 `0`,在高亮状态(`:focus`)下为 `1`。我们还会计算它的否定(`--nothl`)。

* {
  --hl: 0;
  --nothl: calc(1 - var(--hl));
  margin: 0;
  font: inherit
}

就目前而言,这还没有做任何事情。诀窍是使我们想要在两种状态之间更改的所有属性都依赖于 `--hl`,并且如有必要,依赖于它的 否定 (code>–nothl)。

$hlc: #f90;

@mixin thumb() {
  /* same as before */
  background-color: $hlc;
}

input[type='range'] {
  /* same as before */
  filter: grayScale(var(--nothl));
  z-index: calc(1 + var(--hl));
  
  &:focus {
    outline: solid 0 transparent;
    
    &, & + output { --hl: 1; }
  }
}

output {
  /* same grid placement */
  margin-left: var(--r);
  max-width: max-content;
  transform: translate(calc((var(--c) - var(--min))/var(--dif)*var(--uw)));
	
  &::after {
    /* same as before */
    background: 
      linear-gradient(rgba(#fff, .3), rgba(#000, .3))
      $hlc;
    border-radius: 5px;
    display: block;
    padding: 0 .375em;
    transform: translate(-50%) scale(var(--hl));
  }
}

请参阅 thebabydino 在 CodePen 上创建的 Pen (@thebabydino)。

我们快到了!我们还可以添加状态更改时的过渡。

$t: .3s;

input[type='range'] {
  /* same as before */
  transition: filter $t ease-out;
}

output::after {
  /* same as before */
  transition: transform $t ease-out;
}

请参阅 thebabydino 在 CodePen 上创建的 Pen (@thebabydino)。

最后的一项改进是,如果没有任何滑块处于焦点状态,则将填充 `grayscale()`。我们可以通过使用我们的包装器上的 `:focus-within` 来做到这一点。

.wrap {
  &::after {
    /* same as before */
    filter: Grayscale(var(--nothl));
    transition: filter $t ease-out;
  }
	
  &:focus-within { --hl: 1; }
}

就是这样!

请参阅 thebabydino 在 CodePen 上创建的 Pen (@thebabydino)。

选项 #2:扁平化外观

让我们看看如何获得扁平化设计。例如

Screenshot. All slider components are flat, no shadows or gradients. The track and fill are narrower than the thumbs and middle aligned with these. The track has a striped background. The thumbs are scaled down and reveal circular holes in the track around them in their unfocused state.
我们追求的扁平化外观。

第一步是移除为我们的先前演示提供 3D 外观的方框阴影和渐变,并将轨道 `background` 设置为 重复渐变

请参阅 thebabydino 在 CodePen 上创建的 Pen (@thebabydino)。

`:focus` 时滑块的大小变化可以通过缩放 `transform` 来控制,该缩放的系数取决于高亮切换变量(`--hl`)。

@mixin thumb() {
  /* same as before */
  transform: scale(calc(1 - .5*var(--nothl)));
  transition: transform $t ease-out;
}

请参阅 thebabydino 在 CodePen 上创建的 Pen (@thebabydino)。

但轨道周围的滑块上的孔怎么办?

`mask` 合成技术在这里非常有用。这涉及到叠加径向渐变以在每个滑块位置创建圆盘,在我们完成这些操作后,反转(即与完全不透明的层合成)结果将这些圆盘变成孔。

SVG illustration. Shows that XOR-ing a bunch of radial-gradient() layers gives us a layer with opaque discs and everything else transparent and when, we xor this resulting layer with a fully opaque one, this fully opaque layer acts as an inverter, turning the discs into transparent holes in an otherwise fully opaque layer.
我们如何对渐变层 alpha 进行异或 (演示)。

这意味着要稍微更改 Pug 代码,以便我们生成创建对应于每个滑块的圆盘的径向渐变列表。反过来,我们将在 CSS 中反转它们。

//- same as before
- let tpos = thumbs.map((c, i) => `calc(var(--r) + (var(--v${i}) - var(--min))/var(--dif)*var(--uw))`);
- let fill = tpos.map(c => `linear-gradient(90deg, red ${c}, transparent 0)`);
- let hole = tpos.map(c => `radial-gradient(circle at ${c}, red var(--r), transparent 0)`)

.wrap(role='group' aria-labelledby='multi-lbl' 
  style=`${thumbs.map((c, i) => `--v${i}: ${c.val}`).join('; ')}; 
    --min: ${min}; --max: ${max};
    --fill: ${fill.join(', ')}; 
    --hole: ${hole.join(', ')}`)
  // -same wrapper content as before

这将生成以下标记。

<div class='wrap' role='group' aria-labelledby='multi-lbl' 
  style='--v0: -15; --v1: 20; --v2: -35; --v3: 45; 
    --min: -50; --max: 50;
    --fill: 
      linear-gradient(90deg, 
        red calc(var(--r) + (var(--v0) - var(--min))/var(--dif)*var(--uw)), 
        transparent 0), 
      linear-gradient(90deg, 
        red calc(var(--r) + (var(--v1) - var(--min))/var(--dif)*var(--uw)), 
        transparent 0), 
       linear-gradient(90deg, 
         red calc(var(--r) + (var(--v2) - var(--min))/var(--dif)*var(--uw)), 
         transparent 0), 
       linear-gradient(90deg, 
         red calc(var(--r) + (var(--v3) - var(--min))/var(--dif)*var(--uw)), 
         transparent 0); 
     --hole: 
       radial-gradient(circle 
         at calc(var(--r) + (var(--v0) - var(--min))/var(--dif)*var(--uw)), 
         red var(--r), transparent 0), 
       radial-gradient(circle 
         at calc(var(--r) + (var(--v1) - var(--min))/var(--dif)*var(--uw)), 
         red var(--r), transparent 0), 
       radial-gradient(circle 
         at calc(var(--r) + (var(--v2) - var(--min))/var(--dif)*var(--uw)), 
         red var(--r), transparent 0), 
       radial-gradient(circle 
         at calc(var(--r) + (var(--v3) - var(--min))/var(--dif)*var(--uw)), 
         red var(--r), transparent 0)'>
  <!-- same content as before -->
</div>

在 CSS 中,我们在两个伪元素上设置了一个 `mask`,并为每个伪元素提供不同的值。我们还在它们上对 `mask` 层进行了异或操作。

在 `::before` 的情况下,`mask` 是 `radial-gradient()` 圆盘的列表,与完全不透明的层(充当反转器,将圆盘变成圆形孔)进行异或操作。对于 `::after`,它是填充 `linear-gradient()` 层的列表。

.wrap {
  /* same as before */
  
  &::before, &::after {
    content: '';
    /* same as before */
    
    --mask: linear-gradient(red, red), var(--hole);

    /* non-standard WebKit version */
    -webkit-mask: var(--mask);
    -webkit-mask-composite: xor;

    /* standard version, supported in Firefox */
    mask: var(--mask);
    mask-composite: exclude;
  }
	
  &::after {
    background: #95a;
    --mask: var(--fill);
  }
}

请参阅 thebabydino 在 CodePen 上创建的 Pen (@thebabydino)。

最后一步是调整轨道、填充 `height`,并在它们的网格单元格内(以及滑块)垂直居中对齐。

.wrap {
  /* same as before */
  
  &::before, &::after {
    /* same as before */
    align-self: center;
    height: 6px;
  }
}

现在我们有了我们想要的多滑块滑块!

请参阅 thebabydino 在 CodePen 上创建的 Pen (@thebabydino)。