Last active
August 7, 2021 18:36
-
-
Save b-aleksei/49549d9ca50a74b5d5a4716fe3a692e6 to your computer and use it in GitHub Desktop.
range-native
This file contains 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 Range { | |
constructor(selector) { | |
this.$el = this.getHtmlElement(selector); | |
if (!this.$el) return; | |
this.min = +this.$el.dataset.min ?? 0; | |
this.max = +this.$el.dataset.max ?? 100; | |
this.dom = this.mapDOM(this.$el); | |
this.eventNames = { | |
pointerdown: 'pointerdown', | |
pointermove: 'pointermove', | |
pointerup: 'pointerup', | |
}; | |
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.dom.input.min = `${this.min}`; | |
this.dom.input.max = this.$el.dataset.max; | |
this.dom.input.step = this.$el.dataset.step; | |
this.dom.input.value = this.$el.dataset.value; | |
} | |
renderSlider(value) { | |
const val = value - this.min; | |
const translate = Math.round((val / this.valueRange) * this.trackWidth); | |
this.dom.thumb.style.transform = `translate3d(${translate}px, 0, 0)`; | |
this.dom.thumb.dataset.translate = `${translate}`; | |
this.dom.output.dataset.value = value; | |
this.dom.track.style.setProperty('--fill', translate + 'px'); | |
} | |
updateSlider() { | |
this.renderSlider(this.dom.input.value); | |
} | |
handleEvent(e) { | |
const thumb = e.target.closest?.('.thumb'); | |
const track = e.target.closest('.track'); | |
if (track && e.type === this.eventNames.pointerdown) { | |
const trackLength = e.clientX - track.getBoundingClientRect().left; | |
const value = this.computeValue(trackLength); | |
this.setRangeValue(value); | |
} | |
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: | |
this.dom.input.focus(); | |
const x = e.clientX - this._shift; | |
const value = this.computeValue(x); | |
this.setRangeValue(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.value); | |
} | |
} | |
} | |
computeValue(shift) { | |
return ((shift / this.trackWidth) * this.valueRange) + this.min; | |
} | |
setRangeValue(value) { | |
this.dom.input.value = value; | |
this.renderSlider(this.dom.input.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'), | |
thumb: scope.querySelector('.thumb'), | |
output: scope.querySelector('output'), | |
input: scope.querySelector('input'), | |
}; | |
} | |
get trackWidth() { | |
return this.dom.track.offsetWidth - this.dom.thumb.offsetWidth; | |
} | |
get valueRange() { | |
return this.max - this.min; | |
} | |
} | |
// Using | |
const rangeElem = document.querySelector('.range'); | |
if (rangeElem) { | |
new Range(rangeElem); | |
} | |
/* | |
<div class="range" data-min="1000" data-max="100000" data-step="1000" data-value="20000"> | |
<output data-value="">От</output> | |
<div class="range-slider"> | |
<div class="track"></div> | |
<label class="thumb" aria-label="минимальное значение"> | |
<input class="visually-hidden" type="range" name="value_from"> | |
</label> | |
</div> | |
</div> | |
.visually-hidden { | |
position: absolute; | |
white-space: nowrap; | |
width: 1px; | |
height: 1px; | |
overflow: hidden; | |
border: 0; | |
padding: 0; | |
clip: rect(0 0 0 0); | |
clip-path: inset(50%); | |
margin: -1px; | |
} | |
.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); | |
position: relative; | |
display: flex; | |
flex-direction: column; | |
max-width: 345px; | |
.range-slider { | |
position: relative; | |
height: 18px; | |
margin: 25px 10px 0; | |
} | |
.track { | |
--fill: 20%; | |
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, var(--track-active-color) 0 var(--fill), transparent var(--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: 1024px) { | |
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%; | |
background-color: var(--thumb-color); | |
touch-action: none; | |
cursor: pointer; | |
z-index: 2; | |
} | |
} | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment