Skip to content

Instantly share code, notes, and snippets.

@b-aleksei
Created May 17, 2021 12:41
Show Gist options
  • Save b-aleksei/5918a559256e2c12df8e672870acd6ad to your computer and use it in GitHub Desktop.
Save b-aleksei/5918a559256e2c12df8e672870acd6ad to your computer and use it in GitHub Desktop.
double-range
export default class Dbrange {
constructor(selector) {
this.$el = this.getHtmlElement(selector);
if (!this.$el) return;
this.dom = this.mapDOM(this.$el);
this.eventNames = {
pointerdown: 'pointerdown',
pointermove: 'pointermove',
pointerup: 'pointerup',
};
this._rangeKeys = ['low', 'high'];
this.$el.addEventListener(this.eventNames.pointerdown, this);
this.$el.addEventListener('input', this);
const resizeObserver = new ResizeObserver(() => {
this.updateSlider();
});
resizeObserver.observe(this.$el);
this.init();
}
init() {
this._rangeKeys.forEach(key => {
this.dom.inputs[key].min = this.$el.dataset.min;
this.dom.inputs[key].max = this.$el.dataset.max;
this.dom.inputs[key].step = this.$el.dataset.step;
this.dom.inputs[key].value = this.$el.dataset[key];
});
}
renderSlider(key, value) {
const val = value - this.$el.dataset.min;
const colorEdge = key === 'low' ? '--start-fill' : '--end-fill';
const translate = Math.round((val / this.valueRange) * this.trackWidth);
this.dom.thumbs[key].style.transform = `translate3d(${translate}px, 0, 0)`;
this.dom.thumbs[key].dataset.translate = `${translate}`;
this.dom.outputs[key].dataset.value = value;
this.dom.track.style.setProperty(colorEdge, translate + 'px');
}
updateSlider() {
this._rangeKeys.forEach(key => {
this.renderSlider(key, this.dom.inputs[key].value);
});
}
handleEvent(e) {
const thumb = e.target.closest?.('.thumb');
if (thumb) {
switch (e.type) {
case this.eventNames.pointerdown:
this._shift = e.clientX - thumb.dataset.translate;
thumb.setPointerCapture(e.pointerId);
thumb.addEventListener(this.eventNames.pointermove, this);
thumb.addEventListener(this.eventNames.pointerup, this);
break;
case this.eventNames.pointermove:
const key = thumb.dataset.key;
this.dom.inputs[key].focus();
const x = e.clientX - this._shift;
const value = ((x / this.trackWidth) * this.valueRange) + Number(this.$el.dataset.min);
this.setRangeValue(key, value);
break;
case this.eventNames.pointerup:
thumb.removeEventListener(this.eventNames.pointermove, this);
thumb.removeEventListener(this.eventNames.pointerup, this);
break;
case 'input':
this.setRangeValue(e.target.dataset.key, e.target.value);
}
}
}
setRangeValue(key, value) {
if (key === 'low') {
const compareTo = this.dom.inputs.high;
const compareToValue = +compareTo.value;
if (value >= compareToValue) {
value = compareToValue - compareTo.step;
}
}
if (key === 'high') {
const compareFrom = this.dom.inputs.low;
const compareToValue = +compareFrom.value;
if (value <= compareToValue) {
value = compareToValue + Number(compareFrom.step);
}
}
this.dom.inputs[key].value = value;
this.renderSlider(key, this.dom.inputs[key].value);
this.$el.dataset[key] = value;
}
getHtmlElement(selector, ctx = document) {
const node = typeof selector === 'string' ? ctx.querySelector(selector) : selector;
if (node instanceof HTMLElement) {
return node;
}
}
mapDOM(scope) {
return {
track: scope.querySelector('.track'),
thumbs: {
low: scope.querySelector('.thumb[data-key="low"]'),
high: scope.querySelector('.thumb[data-key="high"]'),
},
outputs: {
low: scope.querySelector('output[data-key="low"]'),
high: scope.querySelector('output[data-key="high"]'),
},
inputs: {
low: scope.querySelector('input[data-key="low"]'),
high: scope.querySelector('input[data-key="high"]'),
},
};
}
get trackWidth() {
return this.dom.track.offsetWidth - this.dom.thumbs.high.offsetWidth;
}
get valueRange() {
return this.$el.dataset.max - this.$el.dataset.min;
}
}
/*
<div class="double-range" data-min="1000" data-max="100000" data-step="1000" data-low="20000" data-high="50000">
<fieldset class="double-range-inner">
<legend class="visually-hidden">диапазон цен</legend>
<output data-key="low" data-value="">От</output>
<output data-key="high" data-value="">До</output>
<div class="range-wrap">
<div class="track"></div>
<label class="thumb" data-key="low" aria-label="минимальное значение">
<input class="visually-hidden" type="range" name="value_from" data-key="low">
</label>
<label class="thumb" data-key="high" aria-label="максимальное значение">
<input class="visually-hidden" type="range" name="value_to" data-key="high">
</label>
</div>
</fieldset>
</div>
.double-range {
--thumb-size: 1rem;
--thumb-color: #5F3EC0;
--thumb-focus: 0 0 0 6px rgba(0, 0, 0, .25);
--track-color: #B4B4B4;
--track-active-color: var(--thumb-color);
display: block;
max-width: 345px;
.double-range-inner {
position: relative;
display: grid;
grid-template-columns: 1fr 1fr;
column-gap: 10px;
margin: 12px 0;
padding: 0;
border: none;
}
.range-wrap {
position: relative;
grid-column: 1 / -1;
height: 18px;
margin: 25px 10px 0;
}
.track {
--start-fill: 20%;
--end-fill: 70%;
position: absolute;
z-index: 1;
top: 0;
bottom: 0;
margin: auto;
width: 100%;
height: .25rem;
background-color: var(--track-color);
background-image: linear-gradient(to right, transparent var(--start-fill),
var(--track-active-color) var(--start-fill) var(--end-fill), transparent var(--start-fill));
background-position: 2px 0;
border-radius: .25rem;
pointer-events: none;
}
output {
display: flex;
align-items: center;
padding-left: 16px;
border: 1px solid #B4B4B4;
border-radius: 4px;
height: 50px;
&::after {
content: attr(data-value);
margin-left: 15px;
@media (min-width: $viewport-m) {
margin-left: 5px;
}
}
}
.thumb {
position: absolute;
top: 0;
bottom: 0;
width: var(--thumb-size);
height: var(--thumb-size);
margin: auto;
padding: 2px;
border-radius: 50%;
border: 1px solid var(--thumb-color);
//background-color: var(--thumb-color);
box-shadow: inset 0 0 0 2px white, inset 0 0 0 15px var(--thumb-color);
touch-action: none;
cursor: pointer;
transition: box-shadow .3s;
z-index: 2;
&:focus-within {
box-shadow: var(--thumb-focus), inset 0 0 0 2px white, inset 0 0 0 15px var(--thumb-color);
}
}
}
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment