使用 CSS 和 JavaScript 自定义跨浏览器范围输入

Avatar of Steven Estrella
Steven Estrella

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

以下是 2015 年 Steven Estrella 的一篇客座文章,他在 2019 年 1 月对其进行了更新。Steven 与我分享了一种通过在 Daniel Stern 和其他人在此处探讨的一些技术之上编写少量 JavaScript 来创建自定义范围输入的技术。 我邀请 Steven 解释他的方法。

美酒和友谊经久弥新。 代码则另当别论。 情况会发生变化,有时会变得更容易。 2015 年的旧 CodePen 仍然有效,但它使用了完成手头任务不再需要的复杂程度。 因此,我创建了一个 新的 CodePen 来说明更新的方法,并询问了 CSS-Tricks 的朋友们是否可以更新这篇文章。

2014 年,Daniel Stern 撰写了一篇非常有用的文章,演示了如何 使用纯 CSS 和 HTML 对 HTML5 范围输入进行跨浏览器样式设置。 他甚至创建了一个名为 range.css 的便捷工具来生成 CSS。 但是,有时我们的设计可能需要超越 CSS 独自所能实现的范围。

在下面的示例中,我想尝试一下目前使用 JavaScript 操作范围输入时可能实现的功能。

查看 CodePen 上的
使用 CSS 和 JavaScript 的自定义范围输入 2019
,作者是 Steven Estrella (@sgestrella)
CodePen 上。

您是否注意到,当您拖动滑块时,轨道会填充渐变? 此外,滑块使用图像作为其背景,在您拖动时旋转,并显示值作为文本。 当然,还有允许水平或垂直滑块方向并保留键盘输入这一并非小事。 我们很快就会详细介绍这些内容。 最终,本教程将引导您完成代码和概念,以添加此类对 HTML5 范围输入的外观和功能的控制。

标记内容

我们的方法是使标准范围输入对用户不可见。 它仍将正常工作,并且同样易于访问,但其外观将被我们自己的样式化的 div 替换。 然后,我们将使用大约 50 行 JavaScript 将它们全部连接在一起。 当用户拖动不可见的范围输入滑块时,它将触发事件侦听器,这些侦听器调用一个函数并传输范围输入值。 因此,这将触发样式化 div 外观的变化。

好的,那么让我们从单个范围输入的标记开始

<div class="rangewrapper horizontal">
  <div class="sliderfill">
  <input class="customrange" type="range" min="0" max="100" value="50">
  </div>
  <div class="sliderthumb"></div>
  <div class="slidervalue">50</div>
</div>
  • 外部 .rangewrapper div 用于为其包含的 div 提供定位上下文。 第二个列出的类可以是水平的或垂直的。
  • .sliderfill 将设置为渐变背景,该背景在用户拖动滑块时会更改外观。
  • <input type="range"> 是实际的范围输入元素,我们将将其设置为非常低的透明度。
  • .sliderthumb 创建了 90 年代风格的斜角方形图像,看起来像大理石。
  • .slidervalue 样式化滑块输入的当前值。
  • .sliderthumb.slidervalue 将被绝对定位并设置为忽略指针事件。

在我们的示例中,我们将创建四个这样的 .rangewrapper 元素,最后一个元素设置为垂直方向。 我将三个水平滑块组合在一个 <div class="rangepresenter"> 中,它提供结构和标题的位置。 我将单个垂直滑块放置到 <div class="rangepresenter verticalsliders"> 中。 这两个 div 都存在于 <article class="content"> 中,该元素设置为在窄屏幕上显示为 1 列,在较大的屏幕上使用媒体查询显示为 2 列。 您可以在 code pen 中查看完整的 CSS,但以下是一些重点。

/* Modified Meyer Reset to smooth out browser differences*/
/* CSS variables to keep appearance consistent */
:root {
    /*variables for font color and other cosmetics*/
    /*variables for gradient color stops and sizing the sliders*/
    --accentcolor: rgb(0,128,128);
    --accentcoloralpha: rgba(0,128,128,0.5);
    --maxwidth: 800px;
    --lineheight: 1.3;
    --thumbsize: 40px;
    --tracksize: 300px;
    --trackheight: 28px;
    --trackradius: 6px;
    --innertrackradius: 4px;
}
/*Grid layout with media queries follows*/
/*Then cosmetic styles for the wrapper, headings, content, etc... */
/*Here is the heart of the matter.*/
.rangepresenter {
  width:var(--tracksize);
  height:auto;
}
/*The width is adjusted for vertical sliders and the height is specified.*/
/*Relative position is important to provide a positioning context for the 
vertical slider which will be rotated.*/
.rangepresenter.verticalsliders {
    max-width:calc(var(--tracksize)/2);
    min-height:calc(var(--tracksize) + 60px);
    position:relative;
 }
/*Relative position for rangewrapper is important to provide a positioning context 
for the thumb and value text. It also provides a background color for the slider track.*/
.rangewrapper {
  line-height:var(--lineheight);
  border:2px solid var(--maincolor);
  border-radius:var(--trackradius);
  margin:20px 0 40px 0;
  padding:0;
  position:relative;
  width:var(--tracksize);
  height:var(--trackheight);
  overflow:visible;
  background-color:#ffc;
}
/*The rangewrapper class is rotated when vertical. The position must then be 
calculated so it appears centered after the transformation.*/
.rangewrapper.vertical{
  transform-origin: 50% 50%;
  transform: rotate(-90deg);
  position:absolute;
  top:calc( (var(--tracksize)/2) + 30px);
  left:-50%;
  margin:0;
}
/*The border-radius for the inner fill of the slider track is just a bit smaller 
than the border-radius for the enclosing rangewrapper div.*/
.sliderfill {
  border:0 none;
  border-radius:var(--innertrackradius);
  margin:0;
  padding:0;
  height:100%;
}
/*The sliderthumb uses an image and it is absolutely position using a calculation. 
Pointer-events are set to none so it won't block dragging the range input thumb. 
We will use javascript to be sure the sliderthumb always appears at the same location 
as the real range input thumb.*/
.sliderthumb {
  width:var(--thumbsize);
  height:var(--thumbsize);
  background-image:url('https://s3-us-west-2.amazonaws.com/s.cdpn.io/358203/thumb.png');
  background-size: 100% 100%;
  background-repeat: no-repeat;
  background-color:transparent;
  position:absolute;
  left:0;
  top:calc(((var(--thumbsize) - var(--trackheight))/-2) - 2px);
  border:0 none;
  padding:0;
  pointer-events:none;
}
/*The slidervalue displays the current value of the slider at all times and is 
positioned absolutely just like the sliderthumb.*/
.slidervalue {
  width:var(--thumbsize);
  height:var(--thumbsize);
  line-height:var(--thumbsize);
  position:absolute;
  left:calc(50% - (var(--thumbsize)/2));
  top:calc(((var(--thumbsize) - var(--trackheight))/-2) - 2px);
  color:white;
  font-family:var(--mainfont);
  font-size:1.1rem;
  font-weight:normal;
  border:0 none;
  pointer-events:none;
}
/*When the slider is vertical we need to compensate by rotating the text 90 degrees.*/
.vertical .slidervalue {
  transform:rotate(90deg);
}
/*The customrange class sets each range input to a low opacity.*/
.customrange {
  cursor:pointer;
  height:100%;
  width:var(--tracksize);
  opacity:0.05;
}

JavaScript

最后,我们可以使用 JavaScript 将所有内容绑定在一起。 创建变量以保存滑块、滑块按钮、填充和值的 object 引用。 滑块的初始值放置在一个数组中。 文档加载后,将调用 init 函数,并更新滑块以反映 initialValue 数组。 添加事件侦听器以响应范围输入上的 input 和 change 事件。 它们触发 updateSlider 函数,该函数接收表示滑块编号的参数和表示在范围元素中选择的值的第二个参数。 然后,通过一些简单的数学运算,我们可以使用该值来定位和旋转滑块按钮,将值显示为文本,并在用户拖动滑块按钮时使用渐变填充轨道。

let sliders, sliderfills, thumbs, slidervalues;
let initialValue = [38,50,63,88]; //initial values for the sliders

document.addEventListener('DOMContentLoaded', function (e) { init();});

function init(){
  sliders = document.querySelectorAll(".customrange");
  sliderfills = document.querySelectorAll(".sliderfill");
  thumbs = document.querySelectorAll(".sliderthumb");
  slidervalues = document.querySelectorAll(".slidervalue");
  /* We need to change slider appearance to respond to both input and change events. */
  for (let i=0;i<sliders.length;i++){
    sliders[i].addEventListener("input",function(e){
      updateSlider(i,sliders[i].value);});
    sliders[i].addEventListener("change",function(e){
      updateSlider(i,sliders[i].value);});
    //set initial values for the sliders
    sliders[i].value = initialValue[i];
    //update each slider
    updateSlider(i,sliders[i].value);
  }
}
function updateSlider(fillindex,val){
  //sets the text display and location for each thumb and the slider fill  
  setThumbText(slidervalues[fillindex],val);
  setThumb(thumbs[fillindex],val);
  setSliderFill(sliderfills[fillindex],val);
}
function setThumbText(elem,val){
  let size = getComputedStyle(elem).getPropertyValue("--thumbsize");
  let newx = `calc(${val}% - ${parseInt(size)/2}px)`;
  elem.style.left = newx;
  elem.innerHTML = val;
}
function setThumb(elem,val){
  let size = getComputedStyle(elem).getPropertyValue("--thumbsize");
  let newx = `calc(${val}% - ${parseInt(size)/2}px)`;
  elem.style.left = newx;
  let degrees = 360 * (val/100);
  let rotation = `rotate(${degrees}deg)`;
  console.log(rotation);
	elem.style.transform = rotation;
}
function setSliderFill(elem,val){
  let fillcolor = getComputedStyle(elem).getPropertyValue("--accentcolor");
  let alphafillcolor = getComputedStyle(elem).getPropertyValue("--accentcoloralpha");
  // we create a linear gradient with a color stop based on the slider value
  let gradient = `linear-gradient(to right, ${fillcolor} 0%, 
${alphafillcolor} ${val}%, 
rgba(255,255,255,0.1) ${Number(val) + 1}%, 
rgba(255,255,255,0)  100%)`;
  elem.style.backgroundImage = gradient;
}

总结

长期以来,设置输入样式一直是一个巨大的挑战。 但是现在,使用少量的 CSS 和 JavaScript,我们可以在所有现代浏览器中修复这些问题,而无需过去所需的艰苦努力。

更多信息