以下是 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,我们可以在所有现代浏览器中修复这些问题,而无需过去所需的艰苦努力。
好文章,谢谢!
什么,我不能分叉 CSS-Trick 的笔? 但是我需要执行我的“分叉以添加元视口标签”例程,以便在移动设备上进行测试。
抱歉! 这支笔被设为了私密,但也可以公开,所以现在是公开的(因此可以分叉)。
精彩的撰写,谢谢 Chris。 我不太理解将控件封装在
<
table> 元素中的必要性?
抱歉,由于使用了代码括号,评论被弄乱了!
…
我并不完全理解将输入控件封装在表格元素中的必要性?
当您在触摸屏上使用它时,您的拇指会遮住数字。 该数字应该以某种方式偏移。
不错! 但是触摸屏怎么办?