滑动噩梦:理解 Range 输入

Avatar of Ana Tudor
Ana Tudor

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 200 美元的免费额度!

您可能已经看到过许多关于如何设置 range 输入样式的教程。虽然这篇文章也是关于这个主题的,但它并不是关于如何获得任何特定的视觉效果。相反,它深入探讨了浏览器的不一致性,详细说明了每个浏览器如何显示屏幕上的滑块。理解这一点很重要,因为它有助于我们清楚地了解我们是否可以使我们的滑块在所有浏览器中看起来和行为一致,以及哪些样式是必要的。

查看 Range 输入内部

在任何其他操作之前,我们需要确保浏览器公开了 Range 输入**内部**的 DOM。

在 Chrome 中,我们打开 DevTools,转到**设置**,**首选项**,**元素**,并确保启用**显示用户代理 Shadow DOM**选项。

Series of Chrome screenshots illustrating the steps described above.
Chrome 屏幕截图序列,说明上述步骤。

在 Firefox 中,我们转到 about:config 并确保 devtools.inspector.showAllAnonymousContent 标志设置为 true

Series of Firefox screenshots illustrating the steps described above.
Firefox 屏幕截图序列,说明上述步骤。

很长一段时间,我一直相信 Edge 无法查看这些元素内部的内容。但在使用它的过程中,我发现只要有意志(以及一些运气),总有办法!我们需要打开 DevTools,然后转到要检查的 range input,右键单击它,选择**检查元素**,然后 bam,DOM 浏览器面板现在显示了我们滑块的结构!

Series of Edge screenshots illustrating the steps described above.
Edge 屏幕截图序列,说明上述步骤。

显然,这是一个bug。但它也极其有用,所以我并不抱怨。

内部结构

从一开始,我们就可以看到潜在问题的根源:每个浏览器内部都有非常不同的东西。

在 Chrome 中,在 Shadow DOM 的顶部,我们有一个我们无法再访问的 div。这在 /deep/ 支持的时候是可能的,但随后穿透 Shadow Barrier 的能力被认为是一个 bug,因此曾经有用的功能被删除了。在这个 div 内部,我们还有另一个用于轨道的 div,在轨道 div 内,我们还有第三个 div 用于滑块。最后两个都用 id 属性明确标记,但另一件我发现奇怪的事情是,虽然我们可以用 ::-webkit-slider-runnable-track 访问轨道,用 ::-webkit-slider-thumb 访问滑块,但只有轨道 div 有一个具有此值的 pseudo 属性。

Chrome screenshot of the structure we have inside a range input.
Chrome 中的内部结构。

在 Firefox 中,我们也看到内部有三个 div 元素,但这次它们不是嵌套的——所有三个都是兄弟元素。此外,它们只是普通的 div 元素,没有用任何属性标记,因此在第一次查看它们时,我们无法分辨哪个是哪个组件。幸运的是,在检查器中选择它们会在页面上突出显示相应的组件,这就是我们如何知道第一个是轨道,第二个是进度,第三个是滑块的方式。

Firefox screenshot of the structure we have inside a range input.
Firefox 中的内部结构。

我们可以用 ::-moz-range-track 访问轨道(第一个 div),用 ::-moz-range-progress 访问进度(第二个 div),用 ::-moz-range-thumb 访问滑块(最后一个 div)。

Edge 中的结构要复杂得多,这在某种程度上允许对滑块样式进行更大程度的控制。但是,我们只能使用以 -ms- 为前缀的 ID 访问元素,这意味着我们也无法访问许多元素,这些元素具有我们通常需要更改的内置样式,例如实际 input 和其轨道之间的元素上的 overflow: hidden 或滑块父元素上的 transition

Edge screenshot of the structure we have inside a range input.
Edge 中的内部结构。

拥有不同的结构并且无法访问内部的所有元素以按我们希望的方式设置所有样式,意味着即使必须为每个浏览器使用不同的伪元素来帮助设置单个样式,在所有浏览器中获得相同的结果也可能非常困难,甚至是不可能的。

我们应该始终力求将单个样式保持在最低限度,但有时这根本不可能,因为设置相同的样式可能会由于具有不同的结构而产生非常不同的结果。例如,在轨道上设置诸如 opacityfilter 甚至 transform 之类的属性也会影响 Chrome 和 Edge 中的滑块(其中它是轨道的子元素/后代),但在 Firefox 中不会(其中它是其兄弟元素)。

我发现设置通用样式的最有效方法是使用 Sass 混合,因为以下方法不起作用

input::-webkit-slider-runnable-track, 
input::-moz-range-track, 
input::-ms-track { /* common styles */ }

为了使其工作,我们需要这样编写

input::-webkit-slider-runnable-track { /* common styles */ }
input::-moz-range-track { /* common styles */ }
input::-ms-track { /* common styles */ }

但这重复太多,而且维护起来很麻烦。这就是使混合解决方案成为最合理的选择的原因:我们只需要编写一次通用样式,因此,如果我们决定修改通用样式中的某些内容,则只需要在一个地方进行更改——在混合中。

@mixin track() { /* common styles */ }

input {
  &::-webkit-slider-runnable-track { @include track }
  &::-moz-range-track { @include track }
  &::-ms-track { @include track }
}

请注意,我在这里使用的是 Sass,但您可以使用任何其他预处理器。只要它避免重复并使代码更易于维护,您喜欢什么都可以。

初始样式

接下来,我们来看一下滑块及其组件附带的一些默认样式,以便更好地理解需要显式设置哪些属性以避免浏览器之间的视觉不一致。

事先警告:事情很混乱且复杂。不仅不同浏览器有不同的默认值,而且更改一个元素上的属性可能会以意想不到的方式更改另一个元素(例如,当设置 background 也更改 color 并添加 border 时)。

WebKit 浏览器和 Edge(因为,是的,Edge 也应用了许多 WebKit 前缀的内容)也对某些属性(例如与尺寸、边框和背景相关的属性)有两个级别的默认值,如果我们可以这样称呼它们——在设置 -webkit-appearance: none 之前(如果没有它,我们在这些浏览器中设置的样式将不起作用)和设置之后。重点将放在设置 -webkit-appearance: none 后的默认值上,因为在 WebKit 浏览器中,我们无法在不设置此属性的情况下设置 range 输入的样式,并且我们进行所有这些操作的全部原因是为了理解如何在设置滑块样式时使我们的生活更轻松。

请注意,在 range input 和滑块上设置 -webkit-appearance: none(由于某种原因,轨道默认情况下已设置)会导致滑块在 Chrome 和 Edge 中完全消失。为什么会发生这种情况,我们将在本文后面稍作讨论。

实际的 Range 输入元素

我考虑检查的第一个属性 box-sizing 碰巧在所有浏览器中都具有相同的值——content-box。我们可以通过在 DevTools 的**计算**选项卡中查找 box-sizing 属性来查看这一点。

Comparative screenshots of DevTools in the three browsers showing the computed values of box-sizing for a range input.
range inputbox-sizing,所有三个浏览器的比较视图(从上到下:Chrome、Firefox、Edge)。

可悲的是,这并不能说明即将发生的事情。一旦我们查看提供元素框的属性(marginborderpaddingwidthheight),这一点就会变得很明显。

默认情况下,Chrome 和 Edge 中的 margin2px,Firefox 中为 0 .7em

在我们继续之前,让我们看看我们是如何获得上述值的。我们得到的计算长度值始终是 px 值。

但是,Chrome 向我们展示了浏览器样式是如何设置的(用户代理样式表规则在灰色背景上设置)。有时我们获得的计算值不是显式设置的,因此这没有用,但在这种特殊情况下,我们可以看到 margin 确实设置为 px 值。

Screenshot of Chrome DevTools showing where to look for how browser styles were set.
在 Chrome 中跟踪浏览器样式,margin 案例。

Firefox 在某些情况下也允许我们跟踪浏览器样式的来源,如下面的屏幕截图所示

Screenshot of Firefox DevTools showing where to look for how browser styles were set.
在 Firefox 中跟踪浏览器样式以及这对于我们的 range inputmargin 如何失败。

但是,在这种特殊情况下,这不起作用,因此我们可以做的是查看 DevTools 中的计算值,然后检查这些计算值是否在以下情况之一中发生变化

  1. 更改 inputhtml 上的 font-size 时,这意味着它设置为 emrem 值。
  2. 更改视口时,这表示该值使用 % 值或视口单位设置。不过,在很多情况下,这可能可以安全地跳过。
Gif recording showing how changing the font-size on the range input changes the margin value in Firefox.
更改 Firefox 中 range inputfont-size 也会更改其 margin 值。

Edge 也是如此,我们可以在其中跟踪用户样式的来源,但不能跟踪浏览器样式,因此我们需要检查计算出的 px 值是否依赖于其他任何内容。

Gif recording showing how changing the font-size on the range input doesn't change the margin value in Edge.
更改 Edge 中 range inputfont-size 不会更改其 margin 值。

无论如何,这意味着如果我们希望在所有浏览器中实现一致的外观,我们需要在input[type='range']中显式设置margin属性。

既然我们提到了font-size,让我们也检查一下它。当然,这也是不一致的。

首先,Chrome 中的计算值为13.3333px,尽管小数可能暗示它是计算结果(我们将一个数字除以 3 的倍数),但它似乎被设置为这样,并且不依赖于视口尺寸或父级或根font-size

Screenshot of Chrome DevTools showing the user agent rule where the font-size for inputs is set.
Chrome 中的范围inputfont-size

Firefox 显示了相同的计算值,但它似乎来自将font简写设置为-moz-field,我一开始对此感到非常困惑,尤其是在background-color设置为-moz-Field的情况下,因为 CSS 关键字不区分大小写,它们应该相同。但是如果它们相同,那么它怎么能成为这两个属性的有效值呢?显然,此关键字是某种使输入看起来像当前操作系统上的任何输入的别名

Screenshot of Firefox DevTools showing how the font-size for inputs is set.
Firefox 中的范围inputfont-size

最后,Edge 为其计算值提供了16px,这似乎是从其父级继承而来或设置为1em,如下面的记录所示。

Recording of Edge DevTools showing the computed value of  font-size for inputs and how this changes when changing the font-size of the parent.
Edge 中的范围inputfont-size

这很重要,因为我们通常希望使用em单位来设置滑块和控件(及其组件)的尺寸,以便它们相对于页面上文本的大小保持不变——当我们增加文本大小时它们看起来不会太小,当我们减小文本大小时它们看起来也不会太大。如果我们要以em单位设置尺寸,那么浏览器之间存在明显的font-size差异会导致我们的范围input在某些浏览器中变小,在其他浏览器中变大。

出于这个原因,我总是确保在实际的滑块上显式设置font-size。或者我可能会设置font简写,即使其他与字体相关的属性在此处此时并不重要。也许将来会重要,但稍后我们讨论刻度标记和刻度标记标签时会详细介绍。

在我们继续讨论边框之前,让我们先看看color属性。在 Chrome 中,它是rgb(196,196,196)(如此设置),这使得它比silverrgb(192,192,192)/ #c0c0c0)略亮,而在 Edge 和 Firefox 中,计算值为rgb(0,0,0)(纯黑色)。我们无法知道 Edge 中此值是如何设置的,但在 Firefox 中,它是通过另一个类似的关键字-moz-fieldtext设置的。

Comparative screenshots of DevTools in the three browsers showing the computed values of color for a range input.
范围inputcolor,三个浏览器的对比外观(从上到下:Chrome、Firefox、Edge)。

border在 Chrome 中设置为initial,等效于none medium currentcolorborder-styleborder-widthborder-color的值)。medium边框的精确厚度取决于浏览器,但它至少与所有地方的thin边框一样厚。尤其是在 Chrome 中,我们在这里得到的计算值为0

Screenshot of Chrome DevTools showing how the border for inputs is set.
Chrome 中的范围inputborder

在 Firefox 中,我们也为border设置了none medium currentcolor值,尽管这里的medium似乎等效于0.566667px,这是一个不依赖于元素或根font-size或视口尺寸的值。

Screenshot of Firefox DevTools showing how the border for inputs is set.
Firefox 中的范围inputborder

我们无法看到 Edge 中所有内容是如何设置的,但border-styleborder-width的计算值分别为none0。当我们更改color属性时,border-color会发生变化,这意味着,就像在其他浏览器中一样,它被设置为currentcolor

Recording of Edge DevTools showing the computed values of border properties for inputs and how border-color changes when changing the element's color property.
Edge 中的范围inputborder

padding在 Chrome 和 Edge 中均为0

Comparative screenshots of DevTools in Chrome and Edge browsers showing the computed values of padding for a range input.
范围inputpadding,Chrome(顶部)和 Edge(底部)的对比外观。

但是,如果我们想要像素完美的渲染结果,则需要显式设置它,因为在 Firefox 中它被设置为1px

Screenshot of Firefox DevTools showing how the padding for inputs is set.
Firefox 中的范围inputpadding

现在让我们绕个弯,在尝试理解尺寸值的含义之前检查一下背景。在这里,我们发现 Edge 和 Firefox 中的计算值为transparent/ rgba(0, 0, 0, 0),但在 Chrome 中为rgb(255,255,255)(纯白色)。

Comparative screenshots of DevTools in the three browsers showing the computed values of background-color for a range input.
范围inputbackground-color,三个浏览器的对比外观(从上到下:Chrome、Firefox、Edge)。

而且……最后,让我们看看尺寸。我把它留到最后,因为这里的事情开始变得非常混乱。

Chrome 和 Edge 都为width的计算值提供了129px。与之前的属性不同,我们无法在 Chrome 中看到任何地方设置了它,这通常会让我认为它依赖于父级,像所有block元素一样水平拉伸以适应(这里肯定不是这种情况)或依赖于子元素。在“计算”面板中,还有一个-webkit-logical-width属性取相同的值129px。我一开始对此有点困惑,但事实证明它是书写模式相关的等效项——换句话说,它是水平书写模式的width,垂直书写模式的height

Gif recording showing how changing the font-size on the range input doesn't change its width value in Chrome.
更改 Chrome 中范围inputfont-size不会更改其width值。

无论如何,它在任一浏览器中都不依赖于input本身或根元素的font-size,也不依赖于视口尺寸。

Gif recording showing how changing the font-size on the range input doesn't change its width value in Edge.
更改 Edge 中范围inputfont-size不会更改其width值。

Firefox 这里有点不同,为默认width返回160px的计算值。但是,此计算值确实依赖于范围inputfont-size——它似乎是12em

Gif recording showing how changing the font-size on the range input also changes its width value in Firefox.
更改 Firefox 中范围inputfont-size也会更改其width值。

对于height,Chrome 和 Edge 再次达成一致,为我们提供了21px的计算值。就像width一样,我无法在 Chrome DevTools 中的 user agent 样式表中看到任何地方设置了它,这通常发生在元素的高度取决于其内容时。

Gif recording showing how changing the font-size on the range input doesn't change its height value in Chrome.
更改 Chrome 中范围inputfont-size不会更改其height值。

此值在任一浏览器中也不依赖于font-size

Gif recording showing how changing the font-size on the range input doesn't change its height value in Edge.
更改 Edge 中范围inputfont-size不会更改其height值。

Firefox 再次有所不同,将17.3333px作为计算值,并且,同样,这取决于inputfont-size——它是1.3em

Gif recording showing how changing the font-size on the range input also changes its height value in Firefox.
更改 Firefox 中范围inputfont-size也会更改其height值。

但这不比margin情况更糟糕,对吧?好吧,到目前为止,还没有!但这正要发生变化,因为我们现在要继续讨论轨道组件了。

范围轨道组件

关于实际input尺寸,我们还有一个尚未考虑的可能性:它们受其组件的影响。因此,让我们在轨道上显式设置一些尺寸,看看是否会影响滑块的大小。

显然,在这种情况下,对于实际的滑块,width没有任何变化,但在轨道width方面,我们可以发现更多不一致之处,默认情况下,在所有三个浏览器中,轨道都会拉伸以填充父级inputcontent-box

在 Firefox 中,如果我们显式设置width,轨道上的任何width,则轨道将采用我们提供的此width,扩展到其父滑块外部或缩小到内部,但始终与其垂直居中对齐。这还不错,但遗憾的是,事实证明 Firefox 是唯一在此处以正常方式运行的浏览器。

Gif recording showing how changing the width on the track component doesn't influence the width of the range input in Firefox only that of the track. Furthermore, the track and the actual range input are always middle aligned horizontally.
在 Firefox 中,在轨道上显式设置width会更改轨道的width,但不会更改父滑块的width

在 Chrome 中,我们设置的轨道width被完全忽略,并且看起来没有合理的方法使它具有不依赖于父滑块的值。

Gif recording showing how changing the width on the track component doesn't do anything in Chrome.
更改轨道的width在 Chrome 中没有任何作用(计算值保持129px)。

至于不合理的方法,使用transform: scaleX(factor)似乎是使轨道比其父滑块更宽或更窄的唯一方法。请注意,这样做也会导致一些副作用。拇指也会水平缩放,并且其运动仅限于 Chrome 和 Edge 中缩小的轨道(因为拇指是这些浏览器中轨道的子元素),但在 Firefox 中则不然,其中其大小保持不变,并且其运动仍然仅限于输入,而不是缩小的轨道(因为轨道和拇指在这里是同级元素)。轨道上的任何横向paddingbordermargin也会被缩放。

继续讨论 Edge,轨道再次采用我们设置的任何width

Gif recording showing how Edge allows us to change the width of the track without changing that of the parent slider.
Edge 还允许我们设置与父滑块不同的轨道width

但这与 Firefox 的情况不同。虽然在轨道上设置大于父滑块的width会使其向外扩展,但两者没有垂直居中对齐。相反,轨道的左边界与其实际范围input父级的左内容边界左对齐。这种对齐不一致本身并不是什么大问题——仅在::-ms-track上设置margin-left就可以解决它。

但是,Edge 中父滑块content-box外部的所有内容都会被剪裁掉。这等效于在实际的input上将overflow设置为hidden,这将剪裁掉padding-box外部的所有内容,而不是content-box。因此,无法通过在滑块上设置overflow: visible来修复它。

此剪裁是由input和轨道之间的元素具有overflow: hidden引起的,但是,由于我们无法访问这些元素,因此我们也无法解决此问题。在某些情况下,将所有内容设置为没有任何组件(包括其box-shadow)超出范围的content-box是一个选项,但并非总是如此。

对于height,Firefox 的行为与其对width的行为类似。轨道垂直扩展或缩小到我们设置的height,而不会影响父滑块,并且始终与其垂直垂直居中对齐。

Gif recording showing how changing the height on the track component doesn't influence the height of the range input in Firefox only that of the track. Furthermore, the track and the actual range input are always middle aligned vertically.
在 Firefox 中,在轨道上显式设置height会更改轨道的height,但不会更改父滑块的height

在实际input或轨道上没有设置任何样式的情况下,此height的默认值为.2em

Gif recording showing how changing the font-size on the track changes its computed height in Firefox.
更改轨道上的font-size会更改 Firefox 中其计算的height

width的情况不同,Chrome 允许滑块轨道采用我们设置的height值,并且如果我们在这里没有使用%值,它还会使父滑块的content-box扩展或收缩,从而使轨道的border-box完美地嵌套其中。当使用%值时,实际的滑块和轨道会在垂直方向上居中对齐。

Gif recording showing how changing the height on the track component doesn't influence the height of the range input in Chrome if the value we set is a % value. Otherwise, the track expands or shrinks such that the track perfectly fits in. Furthermore, in the % case, the track and the actual range input are always middle-aligned vertically.
在轨道上显式设置%单位的height会更改 Chrome 中轨道的height,但不会更改父滑块的height。使用其他单位时,实际的范围input会在垂直方向上扩展或收缩,以使轨道完美地嵌套其中。

在没有设置任何自定义样式的情况下,我们获得的height的计算值与滑块相同,并且不会随font-size更改。

Gif recording showing how changing the font-size on the track doesn't change its computed height in Chrome.
更改轨道上的font-size不会更改 Chrome 中其计算出的height

Edge 怎么样?好吧,我们可以独立于父滑块更改轨道的height,并且两者都保持垂直居中对齐,但这仅在我们设置的轨道height小于实际input的初始height时才有效。高于此值时,轨道的计算height始终等于父范围的height

Gif recording showing how changing the height on the track component doesn't influence the height of the range input in Edge. The track and the actual range input are always middle aligned vertically. However, the height of the track is limited by that of the parent slider.
在 Edge 中,在轨道上显式设置height不会更改父滑块的height,并且两者居中对齐。但是,轨道的height受实际inputheight限制。

初始轨道高度为11px,此值不依赖于font-size或视口。

Gif recording showing how changing the font-size on the track doesn't change its computed height in Edge.
更改轨道上的font-size不会更改 Edge 中其计算出的height

接下来讨论一些不那么令人费解的内容,即box-sizing。Chrome 中为border-box,Edge 和 Firefox 中为content-box,因此,如果我们要设置非零的borderpadding,则需要显式设置box-sizing属性以使各方面保持一致。

Comparative screenshots of DevTools in the three browsers showing the computed values of box-sizing for the track.
轨道的box-sizing,三个浏览器(从上到下:Chrome、Firefox、Edge)的对比。

在所有三个浏览器中,轨道的默认marginpadding均为0——终于出现了一致性!

Comparative screenshots of DevTools in the three browsers showing the computed values of margin for the track.
轨道的box-sizing,三个浏览器(从上到下:Chrome、Firefox、Edge)的对比。

在所有三个浏览器中,color属性的值可以从父滑块继承。

Comparative screenshots of DevTools in Chrome and Firefox browsers showing the computed values of color for the track.
轨道的color,Chrome(顶部)和 Firefox(底部)的对比。

即便如此,Edge 仍然是这里特立独行的存在,将其更改为white,尽管将其设置为initial会将其更改为black,这是实际input的值。

Resetting the color to initial in Edge.
在 Edge 中将color重置为initial

在 Edge 中,在实际的input上设置-webkit-appearance: none会使轨道上color的计算值变为transparent(如果我们没有自己显式设置color值)。此外,一旦我们在轨道上添加了background,计算出的轨道color就会突然变为black

Adding a background on the track in Edge changes its computed color from white to black.
在 Edge 中添加background轨道后产生的意外后果。

在某种程度上,能够继承color属性对于主题设置很有用,尽管继承自定义属性在这里可以做更多的事情。例如,假设我们想使用银色表示次要内容,使用橙色表示我们想要突出显示的内容。我们可以在body上定义两个 CSS 变量,然后在页面上使用它们,甚至在我们的范围输入中使用它们。

body {
  --fading: #bbb;
  --impact: #f90
}

h2 { border-bottom: solid .125em var(--impact) }

h6 { color: var(--fading) }

[type='range']:focus { box-shadow: 0 0 2px var(--impact) }

@mixin track() { background: var(--fading) }

@mixin thumb() { background: var(--impact) }

遗憾的是,虽然这在 Chrome 和 Firefox 中有效,但 Edge 目前不允许将范围input上的自定义属性继承到其组件中。

Screenshots of the expected result (and what we get in Chrome and Firefox) vs. the result we get in Edge (neither the thumb or the track show up)
预期结果(左)与 Edge 中的结果(右)对比,其中没有显示轨道或滑块(在线演示)。

默认情况下,Chrome 或 Firefox 中的轨道上没有borderborder-width0border-stylenone)。

Comparative screenshots of DevTools in Chrome and Firefox browsers showing the computed values of border for the track.
轨道的border,Chrome(顶部)和 Firefox(底部)的对比。

如果我们在实际的输入上没有设置background,并且在轨道本身也没有设置background,则 Edge 中的轨道上没有border。但是,一旦更改,我们就会得到一个细的(1px)黑色轨道border

Adding a background on the track or actual input in Edge gives the track a solid 1px black border.
在 Edge 中添加轨道或父滑块background后的另一个意外后果。

默认的background-color显示为继承的白色,但随后在 Chrome 中,我们以某种方式获得了rgba(0,0,0,0)transparent)的计算值(在-webkit-appearance: none之前和之后)。这也让我好奇,我们之前是如何看到轨道的,因为没有background-colorbackground-image提供任何可见内容。Firefox 给我们rgb(153,153,153)#999)的计算值,而 Edge 给我们transparent(即使我们最初可能认为它是一种银色,但这并不是::-ms-track元素的background——稍后会详细介绍)。

Comparative screenshots of DevTools in the three browsers showing the computed values of background-color for the track.
轨道的background-color,三个浏览器(从上到下:Chrome、Firefox、Edge)的对比。

范围滑块组件

准备好迎接迄今为止最恼人的不一致了吗?在 Chrome 中,滑块移动 限制在轨道的content-box内,而在 Firefox 和 Edge 中,即使我们使轨道比输入长或短,滑块也限制在实际inputcontent-box内(Chrome 不允许这样做,它强制轨道的border-box在水平方向上适合滑块的content-box)。

Chrome 的行为如下所示

Chrome only moves the thumb within the left and right limits of the track's content-box.
Chrome 中滑块从一端到另一端的滑块运动记录。

填充是透明的,而内容框和边框是半透明的。我们使用橙色表示实际的滑块,红色表示轨道,紫色表示滑块。

对于 Firefox,情况略有不同

Firefox moves the thumb within the left and right limits of the actual range input's content-box.
Firefox 中滑块从一端到另一端的滑块运动记录(从上到下三个情况:轨道的border-box在水平方向上完美地适合滑块的content-box,它更长,它更短)。

在 Chrome 中,滑块是轨道的子元素,而在 Firefox 中,它是其同级元素,因此,从这个角度来看,Chrome 将滑块移动到轨道content-box的限制范围内是有道理的,而 Firefox 将其移动到滑块content-box的限制范围内是有道理的。但是,滑块也在 Edge 中位于轨道内,并且它仍然在滑块的content-box的限制范围内移动。

Animated gif. Shows how Edge moves the thumb within the left and right limits of the actual range input's content-box.
Edge 中滑块从一端到另一端的滑块运动记录(从上到下三个情况:轨道的border-box在水平方向上完美地适合滑块的content-box,它更长,它更短)。

虽然这乍一看非常奇怪,但这是因为 Edge 强制将轨道的position设置为static,即使我们使用!important将其设置为relative,我们也无法更改它。

Animated gif. Recording of the following steps: 1) checking the computed value of the position property on the track in Edge DevTools - it's static 2) setting ::-ms-track { position: relative } 3) checking the computed value again - it's still static 4) adding !important to the rule previously set on the track 5) checking the computed value a third time - annoyingly, it's still static!
尝试(并失败)更改 Edge 中轨道上position属性的值。

这意味着我们可以在所有浏览器中以完全相同的方式设置滑块的样式,但是如果其content-box在水平方向上与轨道的content-box不重合(因此,如果我们在轨道上设置了非零的横向paddingborder),则它在所有浏览器中的移动范围将不相同。

此外,如果我们水平缩放轨道,则 Chrome 和 Firefox 的行为与之前相同,滑块在 Chrome 中移动到现在已缩放的轨道的content-box的限制范围内,在 Firefox 中移动到实际inputcontent-box的限制范围内。但是,Edge 使滑块在一个宽度等于轨道border-box的间隔内移动,但从轨道padding-box的左边界开始,这可能是由transform属性创建了一个堆叠上下文造成的。

Edge moves the thumb within an interval equal to the scaled track's border-box, starting from the left limit of the padding-box
水平缩放轨道时 Edge 中滑块运动的记录。

在垂直方向上,Firefox 中的滑块与轨道垂直居中,Edge 中似乎也是垂直居中,尽管我在多次测试相同情况时得到了非常混乱的不同结果,并且一旦我们在实际的input和滑块上设置了-webkit-appearance: none以便我们可以设置滑块的样式,其border-box的顶部就会与轨道的content-box的顶部对齐。

虽然 Chrome 的决定乍一看很奇怪,在大多数情况下都很烦人,并且最近甚至导致了 Edge 中出现问题(但稍后会详细介绍),但它背后确实有一些逻辑。默认情况下,Chrome 中轨道的height由滑块的height决定,如果我们从这个角度来看,顶部对齐似乎不再完全是疯狂的了。

但是,我们通常希望滑块比轨道的height更大,并且与轨道垂直居中。我们可以使用我们在::-webkit-slider-thumb伪元素上设置的样式中的margin-top来更正 Chrome 的对齐方式。

不幸的是,这样我们就会破坏 Edge 中的垂直对齐方式。这是因为 Edge 现在也应用了通过::-webkit-slider-thumb设置的样式。至少我们可以选择在我们在::-ms-thumb上设置的样式中将margin-top重置为0。下面的演示展示了一个非常简单的示例。

查看 thebabydino 在 CodePen 上创建的 Pen@thebabydino)。

就像轨道的情况一样,box-sizing属性的值在 Chrome 中为border-box,在 Edge 和 Firefox 中为content-box,因此,为了在浏览器之间获得一致的结果,如果我们想要在滑块上设置非零的borderpadding,则需要显式设置它。

在所有三个浏览器中,marginpadding默认都为0

在滑块和滑块上都设置了-webkit-appearance: none之后(只在一个上设置不会改变任何内容),滑块的尺寸从10x21(不依赖于font-size的尺寸)重置为 Chrome 中的129x0。轨道和实际滑块的height也会重置为0,因为它们取决于其内容(内部的滑块,其height已变为0)。

Animated gif. Shows Chrome DevTools with the thumb selected. Changing the font-size on the thumb doesn't change its dimensions. Setting -webkit-appearance: none on both the thumb and the actual slider resets its dimensions to 129x0
Chrome 中的滑块盒模型。

这也是为什么在滑块上显式设置height会导致轨道采用相同height的原因。

根据 Chrome DevTools,在这两种情况下都没有border,即使在设置-webkit-appearance: none之前,它看起来确实有一个border

Screenshot. Before setting -webkit-appearance:none, it looks like there is a border on the thumb, even though Chrome DevTools says there isn't.
设置-webkit-appearance: none之前 Chrome 中滑块的外观。

如果这不是一个border,它可能是一个outline或一个没有模糊且具有正扩展的box-shadow。但是,根据Chrome DevTools,我们在拇指上既没有outline,也没有box-shadow

Screenshot. The computed value for outline in Chrome DevTools is none 0px rgb(196, 196, 196), while that for box-shadow is none.
Chrome DevTools 中 outlinebox-shadow 的计算值。

在 Edge 中设置-webkit-appearance: none 会使拇指尺寸从11x11(不依赖于font-size的值)变为0x0。显式地为拇指设置height会使轨道采用初始height11px)。

Animated gif. Shows Edge DevTools with the thumb selected. Changing the font-size on the thumb doesn't change its dimensions. Setting -webkit-appearance: none on both the thumb and the actual slider resets its dimensions to 0x0
Edge 中的拇指盒模型。

在 Edge 中,最初拇指上没有border。但是,在为实际的范围input或其任何组件设置background之后,我们突然得到一个solid 1px white的横向边框(左侧和右侧,但不是顶部和底部),在:active状态下视觉上变为black(即使 Edge DevTools 似乎没有注意到这一点)。设置-webkit-appearance: none会移除border-width

Animated gif. Shows Edge DevTools with the thumb selected. There is originally no border, but setting a background on either the slider or its components makes the lateral borders solid 1px white ones. Setting -webkit-appearance: none on both the thumb and the actual slider removes this border (as well as making both thumb dimensions 0).
Edge 中的拇指border

在 Firefox 中,如果没有在范围input或其组件上设置像background这样的属性,则拇指的尺寸为1.666x3.333,在这种情况下,它们不会随font-size更改。但是,如果我们在滑块上设置类似background: transparent的内容(或其组件上的任何background值),则拇指的widthheight都将变为1em

Animated gif. Shows Firefox DevTools with the thumb selected. Changing the font-size on the thumb doesn't change initially its dimensions. However, after setting a background on the actual input, the thumb dimensions become equal to the font-size (1em).
Firefox 中的拇指盒模型。

在 Firefox 中,如果我们相信我们在 DevTools 中看到的内容,我们最初有一个实心的粗灰色(rgb(153, 153, 153)border

Screenshot. Shows Firefox DevTools displaying the computed values for the slider thumb border.
Firefox DevTools 中的拇指border

然而,在视觉上,我在任何地方都找不到这个粗灰色的border

Screenshot of the slider in its initial state in Firefox, before setting a background on it or on any of its components. I cannot see any border on the thumb, even Firefox DevTools says there is a pretty thick one.
在 Firefox 中,在为其或其任何组件设置背景之前,滑块的初始外观。

在为实际的范围input或其组件之一设置background之后,拇指border实际上变得可以视觉检测到,并且似乎是.1em

Animated gif. Shows Firefox DevTools with the thumb selected. In DevTools we originally see a thickish grey border, with a different width on every side, but setting a background on either the slider or its components makes this border thinner an uniform around the thumb. Its width varies with the font-size and it seems to be .1em.
Firefox 中的拇指border

在 Chrome 和 Edge 中,border-radius始终为0

Screenshots. Top: screenshot of Chrome DevTools showing the computed value for the thumb's border-radius is 0. Bottom: screenshot of Edge DevTools showing the computed value for the thumb's border-radius is 0.
Chrome(顶部)和 Edge(底部)中的拇指border-radius

然而,在 Firefox 中,此属性的值为.5em,无论是在为范围input或其组件设置background之前还是之后,即使拇指的初始形状看起来不像具有圆角的矩形。

Animated gif. Shows Firefox DevTools with the thumb selected. In DevTools, we change the font-size on the thumb and, from the way the computed border-radius value changes, we get that it's set to .5em.
Firefox 中的拇指border-radius

Firefox 中拇指的奇怪初始形状让我怀疑它是否没有设置clip-path,但根据 DevTools 并非如此。

Screenshot. Shows Firefox DevTools with the thumb selected. The computed value for the clip-path property on the thumb is none.
Firefox 中的拇指clip-path

更有可能的是,拇指形状是由于-moz-field设置造成的,尽管至少在 Windows 10 上,这并不会使其看起来像其他所有滑块。

Screenshots. The initial appearance of the slider in Firefox vs. the appearance of a native Windows slider.
Firefox 中滑块的初始外观与原生 Windows 10 滑块的外观对比。

Chrome DevTools 报告拇指的background-colorrgba(0, 0, 0, 0)transparent),即使在设置-webkit-appearance: none之前它看起来是灰色的。我们似乎也没有一个可以解释设置-webkit-appearance: none之前拇指上的渐变或线条的background-image。Firefox DevTools 报告它为rgb(240, 240, 240),即使在没有显式地为实际的范围input或其任何组件设置background的情况下,它看起来是蓝色的。

Screenshots. Top: screenshot of Chrome DevTools showing the computed value for background-color on the thumb is rgba(0, 0, 0, 0) and the computed value for background-image is none. Bottom: screenshot of Firefox DevTools showing the computed value for background-color on the thumb is rgb(240, 240, 240).
Chrome(顶部)和 Firefox(底部)中的拇指background-color

在 Edge 中,background-color在设置-webkit-appearance: none之前为rgb(33, 33, 33),之后为transparent

Animated gif. Shows Edge DevTools with the thumb selected. The computed value for the thumb's background-color is rgb(33, 33, 33). In DevTools, we set -webkit-appearance: none on the actual slider and on the thumb. The computed value for the thumb's background-color becomes transparent.
Edge 中的拇指background-color

范围进度(填充)组件

我们只在 Firefox(::-moz-range-progress)和 Edge(::-ms-fill-lower)中为此提供了专用的伪元素。请注意,此元素在 Firefox 中是轨道的同级元素,在 Edge 中是其后代。这意味着它在 Firefox 中相对于实际的input进行大小调整,但在 Edge 中相对于轨道进行大小调整。

为了更好地理解这一点,请考虑轨道的border-box在滑块的content-box内完全水平地适应,并且轨道同时具有borderpadding

在 Firefox 中,进度组件的border-box的左限制始终与滑块的content-box的左限制一致。当当前滑块值为其最小值时,我们进度的border-box的右限制也与滑块的content-box的左限制一致。当当前滑块值为其最大值时,我们进度的border-box的右限制与滑块的content-box的右限制一致。

这意味着我们进度的border-boxwidth0变为滑块的content-boxwidth。一般来说,当拇指位于两个极限值之间距离的x%处时,我们进度的border-boxwidth为滑块的content-boxx%

这在下面的录制中显示。padding区域始终是透明的,而border区域和content-box是半透明的(实际的input为橙色,轨道为红色,进度为灰色,拇指为紫色)。

Animated gif. Shows the slider in Firefox with the thumb at the minimum value. The width of the border-box of the progress component is 0 in this case. We drag the thumb to the maximum slider value. The width of the border-box of the progress component equals that of the slider's content-box in this case.
Firefox 中::-moz-range-progress组件的width如何变化。

然而,在 Edge 中,填充的border-box的左限制始终与轨道的content-box的左限制一致,而填充的border-box的右限制始终与垂直分割拇指的border-box成两半的垂直线一致。这意味着当当前滑块值为其最小值时,填充的border-box的右限制是拇指的border-box的一半,位于轨道content-box的左限制右侧。当当前滑块值为其最大值时,填充的border-box的右限制是拇指的border-box的一半,位于轨道content-box的右限制左侧。

这意味着我们进度的border-boxwidth从拇指的border-box的一半减去轨道的左侧borderpadding变为轨道content-boxwidth加上轨道的右侧paddingborder减去拇指的border-box的一半。一般来说,当拇指位于两个极限值之间距离的x%处时,我们进度的border-boxwidth为其最小width加上其最大和最小width之间差值的x%

以下录制对此进行了说明,您可以使用此现场演示进行操作。

Animated gif. Shows the slider in Edge with the thumb at the minimum value. The width of the border-box of the progress component is half the width of the thumb's border-box minus the track's left border and padding in this case. We drag the thumb to the maximum slider value. The width of the border-box of the progress component equals that of the track's content-box plus the track's right padding and border minus half the width of the thumb's border-box.
Edge 中::-ms-fill-lower组件的width如何变化。

虽然上面对 Edge 方法的描述可能使其看起来更复杂,但我得出的结论是,这是改变此组件宽度的最佳方法,因为 Firefox 方法可能会导致一些问题。

例如,考虑我们为了跨浏览器一致性而在轨道上没有borderpadding,并且填充和拇指的border-boxheight都等于轨道的height的情况。此外,拇指是一个圆盘(border-radius: 50%)。

在 Edge 中,一切正常。

Animated gif illustrating how the case described above works in Edge using a slider with a grey track and orange progress.
我们的示例在 Edge 中的工作方式。

但在 Firefox 中,情况看起来很尴尬(现场演示)。

Animated gif illustrating how the case described above works in Firefox using a slider with a grey track and orange progress.
我们的示例在 Firefox 中的工作方式。

好消息是,在这种组件的情况下,我们没有其他令人讨厌且难以解决的不一致性。

box-sizing在两个浏览器中具有相同的计算值 - content-box

Screenshot. Top half shows Firefox DevTools with the progress component selected. The computed value for box-sizing is shown to be content-box. Bottom half shows Edge DevTools with the lower fill component selected. The computed value for box-sizing is shown to be content-box in this case too.
进度(填充)组件中box-sizing的计算值:Firefox(顶部)和 Edge(底部)。

在 Firefox 中,进度的height.2em,而paddingbordermargin均为0

Animated gif. Shows Firefox DevTools with the progress component selected. Changing the font-size on this component also changes its height, allowing us to see it was set as .2em.
Firefox 中进度的height

在 Edge 中,填充的height等于轨道的content-boxheightpaddingbordermargin均为0,就像在 Firefox 中一样。

Animated gif. Shows Edge DevTools with the fill component selected. The height of the fill is the same as that of the track's content-box. We set box-sizing: border-box on the track and give it a vertical padding to check this. The height of the fill shrinks accordingly.
Edge 中填充的height

最初,此元素的background在 Firefox 中为rgba(0, 0, 0, 0)transparent,这就是为什么我们一开始看不到它的原因)而在 Edge 中为rgb(0, 120, 115)

Screenshot. Top half shows Firefox DevTools with the progress selected. The computed value for the background-color of the progress is rgba(0, 0, 0, 0). Bottom half shows Edge DevTools with the lower fill selected. The computed value for the fill's background-color is rgb(0, 120, 115).
进度(填充)的background-color:Firefox(顶部)和 Edge(底部)。

在这两种情况下,color属性的计算值均为rgb(0, 0, 0)(纯black)。

Screenshot. Top half shows Firefox DevTools with the progress component selected. The computed value for color is shown to be rgb(0, 0, 0). Bottom half shows Edge DevTools with the lower fill component selected. The computed value for color is shown to be rgb(0, 0, 0) in this case too.
进度(填充)组件中color的计算值:Firefox(顶部)和 Edge(底部)。

WebKit 浏览器不提供此类组件,并且由于我们不再有访问和使用轨道::before::after伪元素的方法,因此我们模拟此组件的唯一选择仍然是在这些浏览器中在轨道的现有背景之上叠加一个额外的、非重复的background,并使此额外图层沿x轴的大小取决于范围input的当前值。

如今执行此操作的最简单方法是使用当前值--val CSS 变量,该变量保存滑块的当前值。每次滑块的值发生变化时,我们都会更新此变量,并将此顶层图层的background-size设为取决于--valcalc()值。这样,当范围input的值发生变化时,我们就不必重新计算任何内容 - 我们的calc()值是动态的,因此更新--val变量就足够了(不仅适用于此background-size,还适用于可能依赖于它的其他样式)。

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

如果::-moz-range-progress的增长方式对我们的特定用例看起来不好,也可以为 Firefox 执行此操作。

Edge 还提供了一个::-ms-fill-upper,它基本上是下部组件的补充,并且是我们最初在拇指右侧看到的银色background,而不是轨道的background(轨道是transparent)。

刻度和标签

Edge 是唯一默认显示刻度的浏览器。它们显示在轨道上,分隔两段、五段、十段、二十段,确切数量最初取决于轨道width。我们可以更改这些刻度的唯一样式是color属性,因为它继承自轨道(因此在轨道上设置color: transparent会移除 Edge 中的初始刻度)。

Screenshot. Shows Edge DevTools with the SVG group containing the tick lines selected. Unfortunately, I cannot access this group, its children, its SVG parent or the SVG container to modify their styles. I can only access the track (which is the SVG container's parent) via ::-ms-track. Since the color property is inherited and the tick lines use currentColor as the stroke value, changing the color on the track also changes the stroke of the tick lines.
在 Edge 中生成轨道上初始刻度的结构。

规范说明可以通过链接datalist元素添加刻度和标签,对于其option子元素,如果我们希望该特定刻度也具有标签,则可以为其指定label属性。

不幸的是,尽管在这一点上并不奇怪,但浏览器在这里也有自己的想法。Firefox 没有任何显示 - 没有刻度,没有标签。Chrome 显示刻度,但仅允许我们使用option值控制它们在滑块上的位置。它不允许我们以任何方式设置它们的样式,也不显示任何标签。

Screenshot. Shows the range input with the tick marks generated in Chrome when adding a datalist.
Chrome 中的刻度。

此外,在实际滑块上设置-webkit-appearance: none(这是为了能够设置其样式而需要执行的操作)会导致这些刻度消失。

Edge 也加入了这个行列,也不显示任何标签,并且对刻度的外观控制也不多。虽然添加datalist允许我们控制在轨道上显示哪些刻度,但我们无法对其进行样式设置,除了更改轨道组件上的color属性。

Screenshot. Shows the range input with the tick marks generated in Edge when adding a datalist.
Edge 中的刻度。

在 Edge 中,我们还有::-ms-ticks-before::-ms-ticks-after伪元素。它们基本上就是它们听起来的样子 - 轨道之前和之后的刻度。但是,我很难理解它们是如何工作的。

它们被display: none隐藏,因此将此属性更改为block会使它们可见,如果我们还显式设置滑块height,即使这样做不会更改它们自己的height

Animated gif. Illustrates the steps above to make the tick marks created by ::-ms-ticks-after visible.
如何在 Edge 中使由::-ms-ticks-after创建的刻度可见。

除此之外,我们可以设置诸如marginpaddingheightbackgroundcolor之类的属性来控制它们的外观。但是,我不知道如何控制单个刻度的粗细,如何为单个刻度提供渐变背景,或者如何使其中一些成为主刻度,一些成为次刻度。

因此,归根结底,如果我们想要一个不错的跨浏览器结果,我们最好的选择仍然是使用repeating-linear-gradient作为刻度,以及label元素作为这些刻度对应的值。

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

工具提示/当前值显示

Edge 是唯一通过::-ms-tooltip提供工具提示的浏览器,但这不会显示在 DOM 中,无法真正设置样式(我们只能选择通过在其上设置display: none来隐藏它),并且只能显示整数,因此对于范围在例如.1.4之间的范围input完全没用 - 它显示的所有值都是0

Animated gif. Dragging the thumb in Edge results in the tooltip displaying always 0 if both the minimum and the maximum are subunitary.
当范围限制都小于 1 时,::-ms-tooltip

因此,我们最好的选择是隐藏它,并对所有浏览器使用output元素,再次利用将当前滑块值存储到--val变量中,然后使用根据此变量计算出的calc()值作为位置的可能性。

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

方向

好消息是每个浏览器都允许我们创建垂直滑块。坏消息是,你可能已经猜到了……每个浏览器都提供了不同的方法来做到这一点,其中没有任何一个是规范中提出的(在范围input上设置width小于height)。WebKit 浏览器选择了-webkit-appearance: slider-vertical,Edge 选择了writing-mode: bt-lr,而 Firefox 通过orient属性及其值为'vertical'来控制这一点。

真正糟糕的消息是,对于 WebKit 浏览器,以这种方式使滑块垂直会让我们无法在其上设置任何自定义样式(因为设置自定义样式需要-webkit-appearance的值为none)。

我们最好的选择是将范围input的样式设置为水平,然后使用 CSS transform将其旋转。

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