Skip to content

Instantly share code, notes, and snippets.

@b-aleksei
Last active June 14, 2020 11:27
Show Gist options
  • Save b-aleksei/e626e4e8e3f1339f62cb4fa8323fd874 to your computer and use it in GitHub Desktop.
Save b-aleksei/e626e4e8e3f1339f62cb4fa8323fd874 to your computer and use it in GitHub Desktop.
Double-range
// for IE11
const supportsTemplate = 'content' in document.createElement('template');
// the function takes a template and returns a DOM element
const createElement = supportsTemplate
? function (html) {
const template = document.createElement('template');
template.innerHTML = html;
return template.content.firstElementChild;
}
: function (html) {
const div = document.createElement('div');
div.innerHTML = html;
return div.firstElementChild;
};
export default createElement;
import './style.css';
import createElement from './helpers.js';
import tmpl from './template.js';
export default class DoubleRange {
constructor (elemPlaceholder) {
this.render(elemPlaceholder);
this.thumbs = {
from: this.elem.querySelector('.double-range__thumb--from'),
to: this.elem.querySelector('.double-range__thumb--to')
};
this.inputs = {
from: this.elem.querySelector('.double-range__input--from'),
to: this.elem.querySelector('.double-range__input--to')
};
this.values = {
from: this.elem.querySelector('.double-range__output--from'),
to: this.elem.querySelector('.double-range__output--to')
};
this.indicator = {
track: this.elem.querySelector('.double-range__track'),
startFill: 0,
endFill: 0
}
this.eventNames = {
pointerdown: 'pointerdown',
pointerup: 'pointerup',
pointermove: 'pointermove'
}
if (!window.PointerEvent) {
this.eventNames.pointerdown = 'mousedown'
this.eventNames.pointerup = 'mouseup'
this.eventNames.pointermove = 'mousemove'
}
this.currentKey = null;
this.shift = null;
this.thumbsWidth = null;
// Assume both inputs have equal max values on start
this.maxValue = this.inputs.to.max;
// Use form to check reset
this.form = this.elem.closest('form');
this.setThumbPosition = this.setThumbPosition.bind(this);
this.pointerDown = this.pointerDown.bind(this);
this.moveThumb = this.moveThumb.bind(this);
this.stopDrag = this.stopDrag.bind(this);
this.init();
}
get rightEdge () {
return this.elem.offsetWidth - this.thumbsWidth;
}
// replace template from js to html
render (elemPlaceholder) {
this.elem = createElement(tmpl);
elemPlaceholder.replaceWith(this.elem);
}
init () {
this.thumbsWidth = this.thumbs.from.offsetWidth;
this.addEvents();
this.setThumbPosition();
}
setThumbPosition (params = {}) {
const { inputKey } = params;
const reset = !inputKey;
for (const key in this.thumbs) {
if (!reset) {
if (inputKey !== key) {
continue;
}
}
const input = this.inputs[key];
const inputValue = +input.value;
const thumb = this.thumbs[key];
const valueElem = this.values[key];
// Glass wall for inputs
if (key === 'from') {
const compareTo = this.inputs.to;
const compareToValue = +compareTo.value;
if (inputValue >= compareToValue) {
input.value = compareToValue - Number(input.step);
}
} else {
const compareTo = this.inputs.from;
const compareToValue = +compareTo.value;
if (inputValue <= compareToValue) {
input.value = compareToValue + Number(input.step);
}
}
// Set thumb position
const value = input.value || input.defaultValue;
let left = value / this.maxValue * this.rightEdge;
left = left.toFixed() + 'px'
thumb.style.left = left;
// Set label texts
valueElem.style.left = left;
valueElem.innerHTML = value;
key === 'from' ? this.indicator.startFill = left : this.indicator.endFill = left
}
// set color between thumbs
this.indicator.track.style.backgroundImage = `linear-gradient(90deg, transparent ${this.indicator.startFill},
currentColor ${this.indicator.startFill} ${this.indicator.endFill}, transparent ${this.indicator.endFill})`
}
// Events
addEvents () {
for (const key in this.inputs) {
const input = this.inputs[key];
const thumb = this.thumbs[key];
thumb.ondragstart = () => false;
input.addEventListener('input', () => {
this.setThumbPosition({ inputKey: key });
});
}
this.elem.addEventListener(this.eventNames.pointerdown, this.pointerDown);
window.addEventListener('resize', this.setThumbPosition);
this.form.addEventListener('reset', () => {
setTimeout(this.setThumbPosition);
});
}
pointerDown (e) {
const key = e.target.dataset.key;
if (!key) {
return;
}
e.preventDefault();
const current = this.thumbs[key];
this.currentKey = key;
current.setPointerCapture(e.pointerId);
this.shift = e.clientX - current.offsetLeft;
this.elem.addEventListener(this.eventNames.pointermove, this.moveThumb);
this.elem.addEventListener(this.eventNames.pointerup, this.stopDrag);
}
moveThumb (e) {
const left = e.clientX - this.shift;
const key = this.currentKey;
const value = left / this.rightEdge * this.maxValue;
this.inputs[key].value = value;
this.inputs[key].focus();
this.inputs[key].dispatchEvent(new Event('input'));
}
stopDrag () {
this.elem.removeEventListener(this.eventNames.pointermove, this.moveThumb);
this.elem.removeEventListener(this.eventNames.pointerup, this.stopDrag);
}
}
// Using
const doubleRangeElem = document.querySelector('.double-range');
if (doubleRangeElem instanceof HTMLElement) {
new DoubleRange(doubleRangeElem);
}
const data = {
contClass: '',
min: 0,
max: 500,
step: 10,
valueMin: 50,
valueMax: 400,
nameMin: 'value_from',
nameMax: 'value_to'
};
const createDoubleRange = function (param = {}) {
const { contClass, min, max, step, valueMin, valueMax, nameMin, nameMax } = param;
return `
<fieldset class="double-range ${contClass}">
<legend class="visually-hidden">Double range</legend>
<span
class="double-range__track"
></span>
<label class="double-range__label">
<span
class="
double-range__output
double-range__output--from"></span>
<input
class="
double-range__input
double-range__input--from"
type="range"
min="${min}"
max="${max}"
step="${step}"
value="${valueMin}"
name="${nameMin}"
data-key="from"
>
<span
class="
double-range__thumb
double-range__thumb--from"
data-key="from"
></span>
</label>
<label class="double-range__label">
<span
class="
double-range__output
double-range__output--to"></span>
<input
class="
double-range__input
double-range__input--to"
type="range"
min="${min}"
max="${max}"
step="${step}"
value="${valueMax}"
name="${nameMax}"
data-key="to"
>
<span
class="
double-range__thumb
double-range__thumb--to"
data-key="to"
></span>
</label>
</fieldset>
`;
};
export default createDoubleRange(data);
<form action="https://echo.htmlacademy.ru/">
<!-- Double range -->
<fieldset class="double-range">
<legend class="visually-hidden">Double range</legend>
<span class="double-range__track"></span>
<label class="double-range__label">
<span class="double-range__output double-range__output--from" style="left: 20%">200</span>
<input class=" double-range__input double-range__input--from" type="range">
<span class="double-range__thumb double-range__thumb--from" style="left: 20%"></span>
</label>
<label class="double-range__label">
<span class="double-range__output double-range__output--to" style="left: 70%">450</span>
<input class="double-range__input double-range__input--to" type="range">
<span class="double-range__thumb double-range__thumb--to" style="left: 70%"></span>
</label>
</fieldset>
<!-- // Double range -->
<button type="reset">Reset</button>
<button type="submit">Submit</button>
</form>
.double-range {
margin: 0;
padding: 0;
position: relative;
height: 10rem;
border: none;
}
.double-range__track {
position: absolute;
z-index: 1;
top: 0;
bottom: 0;
margin: auto;
width: 100%;
height: .45rem;
background-color: #DDD;
background-image: linear-gradient(to right, transparent 20%, currentColor 20% 70%, transparent 70%);
background-position: 2px 0;
border-radius: .25rem;
pointer-events: none;
color: blue;
}
.double-range__input {
position: absolute;
height: 1px;
width: 100%;
opacity: 0;
}
.double-range__output {
position: absolute;
text-align: center;
bottom: 100%;
width: 35px;
}
.double-range__thumb {
position: absolute;
z-index: 2;
top: 0;
bottom: 0;
margin: auto;
width: 1.5rem;
height: 1.5rem;
border-radius: 50%;
background-image: linear-gradient(teal, turquoise);
touch-action: none;
cursor: pointer;
}
.double-range__input:focus + .double-range__thumb {
box-shadow: 0 0 0 4px rgba(0, 0, 0, .25);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment