<h3>Ticks & Labels</h3>
<div class="vr-2">
<input
id="range-slider-input"
type="range"
role="slider"
aria-valuemin="100"
aria-valuemax="1000"
aria-valuetext="$500"
min="100"
max="1000"
step="100"
value="400"
values='{
"100": "$100",
"200": "$200",
"300": "$300",
"400": "$400",
"500": "$500",
"600": "$600",
"700": "$700",
"800": "$800",
"900": "$900",
"1000": "$1000"
}'
>
</div>
<h3>Ticks with First & Last Labels Only</h3>
<div class="vr-2">
<input
id="range-slider-input-2"
type="range"
role="slider"
aria-valuemin="100"
aria-valuemax="1000"
aria-valuetext="$500"
min="100"
max="1000"
step="100"
value="600"
values='{
"100": "$100",
"200": "$200",
"300": "$300",
"400": "$400",
"500": "$500",
"600": "$600",
"700": "$700",
"800": "$800",
"900": "$900",
"1000": "$1000"
}'
first-last-labels-only
>
</div>
<h3>Just Ticks</h3>
<div class="vr-2">
<input
id="range-slider-input-3"
type="range"
role="slider"
aria-valuemin="100"
aria-valuemax="1000"
aria-valuetext="$500"
min="100"
max="1000"
step="100"
value="300"
values='{
"100": "$100",
"200": "$200",
"300": "$300",
"400": "$400",
"500": "$500",
"600": "$600",
"700": "$700",
"800": "$800",
"900": "$900",
"1000": "$1000"
}'
no-labels
>
</div>
<h3>No Labels or Ticks</h3>
<div class="vr-2">
<input
id="range-slider-input-4"
type="range"
role="slider"
aria-valuemin="100"
aria-valuemax="1000"
aria-valuetext="$500"
min="100"
max="1000"
step="100"
value="700"
>
</div>
@import url('https://fonts.googleapis.com/css?family=Raleway:400,600,800');
$blue: #2294d6;
$light-blue: #d8e0f3;
$light-gray: #eee;
@function remify($values) {
$rems: ();
@each $value in $values {
@if $value == 0 or $value == auto {
$rems: append($rems, $value);
} @else {
$rems: append(
$rems,
($value / 17) + rem
);
}
}
@return $rems;
}
body {
font-family: 'Raleway', Arial, Helvetica, sans-serif;
font-weight: 300;
padding: remify(32);
}
h3 {
font-size: remify(18);
font-weight: 800;
margin: 0 0 remify(24);
}
.vr-2 {
margin-bottom: remify(32);
}
.range-slider {
display: flex;
position: relative;
height: remify(55);
width: 100%;
input[type='range'] {
appearance: none;
appearance: none;
background: $light-blue;
width: calc(100% - 1px);
height: remify(5);
border-radius: remify(5);
border: 0;
margin: 0;
padding: 0;
position: relative;
z-index: 3;
&:focus {
outline: 0;
border-color: lighten($blue, 35%);
&::slider-thumb {
background-color: darken($blue, 10%);
}
&::range-thumb {
background-color: darken($blue, 10%);
}
&::thumb {
background-color: darken($blue, 10%);
}
}
}
::slider-thumb {
appearance: none;
border: remify(13) solid $blue;
background-color: white;
border-radius: 50%;
width: remify(36);
height: remify(36);
box-shadow: remify(0 0 0 2) white;
cursor: pointer;
}
::range-thumb {
appearance: none;
appearance: none;
border: remify(13) solid $blue;
background-color: white;
border-radius: 50%;
width: remify(16);
height: remify(16);
box-shadow: remify(0 0 0 2) white;
cursor: pointer;
}
::thumb {
border: remify(2) solid $blue;
background-color: white;
border-radius: 50%;
width: remify(8);
height: remify(8);
cursor: pointer;
}
::runnable-track {
appearance: none;
width: 0;
}
::range-track {
appearance: none;
width: 0; // Firefox needs an explicit width for this to not show.
}
::track {
border-color: transparent;
color: transparent;
background-color: transparent;
}
::fill-upper {
background: transparent;
}
::fill-lower {
background: $blue;
margin-left: remify(-2); // Fixes a gap in rendering in IE 11.
height: remify(20);
border-color: $blue;
border-width: remify(20 0);
}
::tooltip {
display: none;
}
}
.range-slider-ticks {
position: absolute;
top: remify(-5);
z-index: 2;
left: 0;
right: 0;
display: flex;
justify-content: space-between;
margin: 0 9px;
&__tick {
background: $light-blue;
display: inline-flex;
justify-content: center;
width: remify(15);
height: remify(15);
border-radius: 50%;
position: relative;
cursor: pointer;
}
&__label {
font-size: remify(14);
padding-top: remify(28);
&.is-selected {
font-weight: 800;
}
}
}
View Compiled
const createUniqueId = prefix => {
return `${prefix}-${Math.round(Math.random() * 100000)}`;
}
class RangeSlider {
constructor(el) {
this.input = document.querySelector(el);
this.el = this.input.getAttribute('id');
this.minValue = this.input.getAttribute('min');
this.maxValue = this.input.getAttribute('max');
this.numTicks = this.maxValue / this.minValue;
this.input.addEventListener('change', (e) => this.handleInputChange(e));
this.wrapInput().then(wrapperId => {
if (this.input.hasAttribute('values')) {
this.values = JSON.parse(this.input.getAttribute('values'));
this.createTicks(wrapperId);
}
});
}
handleInputChange(e) {
const value = e.target.value;
const ariaValueText = !this.values ? value : this.values[value];
this.input.setAttribute('aria-valuetext', ariaValueText);
if (this.input.hasAttribute('values')) {
this.setSelectedLabel(this.values[value]);
}
}
handleLabelClick(label, value, e) {
this.input.focus();
this.input.value = value;
this.input.setAttribute('value', value);
this.input.setAttribute('aria-valuetext', label);
this.setSelectedLabel(label);
}
setSelectedLabel(label) {
const selectedLabels = this.input.parentNode.querySelectorAll(`.range-slider-ticks__label`);
[].forEach.call(selectedLabels, el => {
el.classList[el.innerText === label ? 'add' : 'remove']('is-selected');
});
}
wrapInput() {
return new Promise(resolve => {
const wrapper = document.createElement('div');
const wrapperId = createUniqueId('range-slider');
wrapper.id = wrapperId;
wrapper.className = 'range-slider';
this.input.parentNode.replaceChild(wrapper, this.input);
document.querySelector(`#${wrapperId}`).appendChild(this.input);
resolve(wrapperId);
});
}
createTicks(wrapperId) {
return new Promise(resolve => {
let index = 0;
let tickLabelText;
const tickList = document.createElement('div');
const tickListId = createUniqueId('tick-list');
const noLabels = this.input.hasAttribute('no-labels');
const firstAndLastLabelsOnly = this.input.hasAttribute('first-last-labels-only');
tickList.id = tickListId;
tickList.className = 'range-slider-ticks';
document.querySelector(`#${wrapperId}`).appendChild(tickList);
for (const prop in this.values) {
if (this.values.hasOwnProperty(prop)) {
const isFirstOrLastLabel =
!!firstAndLastLabelsOnly
&& index > 0
&& index < Object.values(this.values).length - 1;
const tick = document.createElement('div');
const tickLabel = document.createElement('span');
tickLabel.className = `
range-slider-ticks__label ${this.input.value === prop ? 'is-selected' : ''}
`;
if (!noLabels) {
tickLabelText = document.createTextNode(
isFirstOrLastLabel ? '' : this.values[prop]
);
tickLabel.appendChild(tickLabelText);
}
tick.className = 'range-slider-ticks__tick';
tick.addEventListener('click', (e) => this.handleLabelClick(this.values[prop], prop, e));
tick.appendChild(tickLabel);
document.querySelector(`#${tickListId}`).appendChild(tick);
index += 1;
}
}
resolve();
});
}
}
[].forEach.call(document.querySelectorAll('input[type="range"]'), el => {
const id = `#${el.getAttribute('id')}`;
return new RangeSlider(id);
});
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.