Created
May 17, 2021 12:41
-
-
Save b-aleksei/5918a559256e2c12df8e672870acd6ad to your computer and use it in GitHub Desktop.
double-range
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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