一文带你掌握如何实现一个自定义Range Slider

1,590 阅读7分钟

在日常开发中我们经常会遇到一些要使用到滑块组件,例如:
产品:我们的新增基金组合创建要能实现输入框和滑块组件双向交互控制用户设置每个基金的比例。
设计:设计出了很好看很炫酷的Range Slider组件,甚至还根据比例的大小带着渐变色。
开发:臣妾做不到啊。

很多开发小伙伴在遇到此类问题的时候都会选择性挠头,因为浏览器自带的input标签的range展现的滑块太丑了,然后用第三方的Range Slider组件例如antd跟我们的设计又很不一样很难修改。接下来带大家打造自己的Range Slider组件从此不再为此挠头。

Range sliders (<input type="range">)是为了让用户选择范围值来替代输入<input type="number">
但是各个浏览器的表现往往又不同,但相同的是默认样式出奇的丑,完全不符合线上使用的标准,下面是Chrome、Firefox和Safari中的默认表现:

image.png

<input>元素很难设计。大多数用于设计样式的在线解决方案都依赖于 JavaScript 和一些混乱的代码。更糟糕的是,其中一些技术还破坏了元素的可访问性。

因此,看看如何仅使用 CSS 来做得更好,并且不影响可访问性。下面是我们使用CSS实现的结果:

input设置range属性后在各个浏览器的元素结构

首先剖析range input的结构。它是一个原生标签,每个浏览器都有自己的此类标签的实现。主要有两种不同的实现。
其中有一个适用于 Webkit 和 Blink 浏览器,例如 Chrome、Edge、Safari 和 Opera:

<input type="range" min="0" max="100" step="1" value="20">
  <div>
    <div pseudo="-webkit-slider-runnable-track" id="track">
      <div id="thumb">
    </div>
   </div>
  </div>
</input>

image.png

这是 Firefox 的:

<input type="range" min="0" max="100" step="1" value="20">
  <div></div>
  <div></div>
  <div></div>
</input>

image.png

当然IE也有自己的实现方式,但是万幸的是我们现在绝大多数人不需要再适配IE了,当然肯定还有部分小可怜,在此可怜他们一分钟。

浏览器之间的这种不一致使得任务变得困难,因为需要为每个实现提供不同的样式。

您需要记住的唯一一件事是,无论实现如何,我们始终将“Thumb”作为通用组件。

image.png

我只会设置此元素的样式,这将使自定义范围滑块易于自定义。

自定义input

第一步是使用appearance: none和其他一些常见属性来重置和禁用所有浏览器默认样式:

input {
  appearance :none;
  background: none;
  cursor: pointer;
}

在更复杂的场景中,需要添加更多代码,以防其他默认样式影响最终结果。只需确保有一个“naked”元素,没有任何视觉样式。

定义一些 CSS 变量,以便我们可以轻松地为范围滑块创建不同的样式:

input {
  --c: orange; /* active color */
  --g: 8px; /* the gap */
  --l: 5px; /* line thickness*/
  --s: 30px; /* thumb size*/

  width: 400px; /* input width */
  height: var(--s); 
  appearance :none;
  background: none;
  cursor: pointer;
}

这一步我们先来自定义一个Thumb的样式:

设置Thumb样式

首先设置Thumb的基础样式

<thumb selector> {
  height: var(--s);
  aspect-ratio: 1;
  border-radius: 50%;
  box-shadow: 0 0 0 var(--l) inset var(--c);
  appearance: none;
}

上面的设置简单明了,没有什么特别的地方,接下来看一下设置后的结果:

注意两个不同选择器的不同,上面提到过不同浏览器的表现不同:

/* Chrome, Edge, Safari, Opera */
input[type="range" i]::-webkit-slider-thumb { }
/* Firefox */
input[type="range"]::-moz-range-thumb { }

要怎样判断使用哪种选择器

使用浏览器的开发人员工具检查了输入的代码,以查看每个浏览器用于设置Thumb样式的选择器。

使用border-image给我们的样式添加一些魔法

现在将使用一个神奇的 CSS 技巧来完成Range Slider。它涉及使用border-image

border-image: linear-gradient(90deg,var(--_c) 50%,#ababab 0) 1/0 100vw/0 calc(100vw + var(--g));

可能看起来有点复杂,但让剖析一下这条线,你会发现它并不那么困难。上面是以下内容的简写:

border-image-source: linear-gradient(90deg,var(--c) 50%,#ababab 0); 
border-image-slice: 1;
border-image-width: 0 100vw;
border-image-outset: 0 calc(100vw + var(--g));

MDN 页面,我们读到:

CSS属性border-image在给定元素周围绘制图像。它取代了元素的常规边框。

我们的图像将是具有两种颜色的渐变 - 主要颜色(由 定义--c)和灰色。我们需要边框图像水平覆盖输入的整个空间,因此我们对左右宽度使用较大的值 ( 100vw),同时保持顶部和底部为 ( 0)。

但它border-image-width受到元素大小的限制。为了克服这个问题,我们还需要使用一个大的值来border-image-outset增加边框图像的可用空间。来自MDN的解释 :

CSSborder-image-outset属性设置元素的边框图像与其边框框的距离。

在元素边框框之外渲染的边框图像部分border-image-outset不会触发溢出滚动条,也不会捕获鼠标事件。

当第一次看到滑块时,看起来正在增加左侧的主颜色,但实际上正在滑动一个固定的渐变,该渐变溢出了元素。

下面演示会更加直观:

接下来添加overflow: hidden属性就大致接近我们想要的效果了:

最后一步是减小条形的大小以匹配我们由变量定义的大小--l。为此,我们将使用clip-path

clip-path:
    polygon(
       0     calc(50% + var(--l)/2),
      -100vw calc(50% + var(--l)/2),
      -100vw calc(50% - var(--l)/2),
       0     calc(50% - var(--l)/2),
       0 0,100% 0,
       100%  calc(50% - var(--l)/2),
       100vw calc(50% - var(--l)/2),
       100vw calc(50% + var(--l)/2),
       100%  calc(50% + var(--l)/2),
       100% 100%,0 100%);

下图概述了了解多边形形状的不同点。

剪切路径多边形概述

就是这样!有一个自定义Range Slider,其中包含几行代码,可以通过调整一些变量来轻松控制。

最后还需要一些交互动画

当我们与Range Slider交互时,有一些微妙的动画,它不需要大量代码,会增强滑块的用户体验。

首先,当点击Thumb时,我们将把Thumb从一个只有边框的圆圈变成一个完整的圆圈。为此,增加了 的点差值box-shadow。请记住,曾经box-shadow定义过Thumb的边框:

box-shadow: 0 0 0 var(--l) inset var(--c);

var(--l)使用var(--s)选择:active器和:focus-visible. 后者与键盘导航相关,让我们无论使用鼠标还是键盘都能获得相同的效果。

代码如下:

input[type="range" i]::-webkit-slider-thumb {
  box-shadow: 0 0 0 var(--l) inset var(--c);
  transition: .3s;
}

input[type="range" i]::-moz-range-thumb {
  box-shadow: 0 0 0 var(--l) inset var(--c);
  transition: .3s;
}

input:active::-webkit-slider-thumb,
input:focus-visible::-webkit-slider-thumb {
  box-shadow: 0 0 0 var(--s) inset var(--c);
}

input:active::-moz-range-thumb,
input:focus-visible::-moz-range-thumb {
  box-shadow: 0 0 0 var(--s) inset var(--c);
}

对于过渡来说有点漫长box-shadow,对吧?我们可以使用 CSS 变量对其进行优化:

input[type="range" i]::-webkit-slider-thumb {
  box-shadow: 0 0 0 var(--_b,var(--l)) inset var(--c);
  transition: .3s;
}

input[type="range" i]::-moz-range-thumb {
  box-shadow: 0 0 0 var(--_b,var(--l)) inset var(--c);
  transition: .3s;
}

input:active,
input:focus-visible {
  --_b:  var(--s);
}

使用变量来表达传播值,并且只是在:active和上更新该变量:focus-visible

还要为颜色添加一些动画。把它调暗一点:hover。为此,不会更新颜色,而是使用新color-mix()函数将其与黑色混合。这个技巧允许我们使用定义的主颜色,而--c不是手动为每个滑块定义新的深色:

--_c: color-mix(in srgb, var(--c), #000 var(--p,0%));

定义一个新变量来替换--c代码中的#000,然后通过使用变量调整黑色 ( ) 的百分比--p,可以控制颜色的“暗度”:

input:focus-visible,
input:hover{
  --p: 25%;
}

并非所有浏览器都支持此功能,因此强烈建议使用兼容写法:

@supports not (color: color-mix(in srgb,red,red)) {
  input {
    --_c: var(--c); /* if no support we keep the color as defined */
  }
}

小结

通过一些简单的技巧和一些常见的css属性实现了一个自定义的Range Slider,从此不再为此挠头而脱发!!!