Skip to content

Instantly share code, notes, and snippets.

@b-aleksei
Last active August 7, 2021 18:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save b-aleksei/49549d9ca50a74b5d5a4716fe3a692e6 to your computer and use it in GitHub Desktop.
Save b-aleksei/49549d9ca50a74b5d5a4716fe3a692e6 to your computer and use it in GitHub Desktop.
range-native
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