Skip to content

Instantly share code, notes, and snippets.

@b-aleksei
Last active January 10, 2021 18:30
Show Gist options
  • Save b-aleksei/242fc52cb981727bdcc0ad9ef98a9803 to your computer and use it in GitHub Desktop.
Save b-aleksei/242fc52cb981727bdcc0ad9ef98a9803 to your computer and use it in GitHub Desktop.
WC-double-range
import Template from './template.js';
export default class Dbrange extends HTMLElement {
constructor() {
super();
this.root = this.attachShadow({mode: 'open'});
this.root.innerHTML = Template.render();
this.dom = Template.mapDOM(this.root);
this.eventNames = {
pointerdown: 'pointerdown',
pointermove: 'pointermove',
pointerup: 'pointerup',
};
this._rangeKeys = ['from', 'to'];
this.root.addEventListener(this.eventNames.pointerdown, this);
this.root.addEventListener('input', this);
const resizeObserver = new ResizeObserver(() => {
this.updateSlider()
})
resizeObserver.observe(this)
}
static get observedAttributes() {
return ['from', 'to'];
}
attributeChangedCallback(name, oldVal, newValue) {
if (this._initialized) {
if (name === 'from' || name === 'to') {
this.dom.inputs[name].value = newValue;
this.renderSlider(name, this.dom.inputs[name].value);
}
} else {
this._rangeKeys.forEach(key => {
this.dom.inputs[key].min = this.min
this.dom.inputs[key].max = this.max
this.dom.inputs[key].step = this.step
this.dom.inputs[key].value = this[key]
})
this._initialized = true
}
}
renderSlider(key, value) {
const val = value - this.min;
const colorEdge = key === 'from' ? '--start-fill' : '--end-fill';
const translate = (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].style.transform = `translate3d(${translate}px, 0, 0)`;
this.dom.outputs[key].innerText = 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) + this.min;
this.dom.inputs[key].value = value
this.setRangeValue(key, this.dom.inputs[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 === 'from') {
const compareTo = this.dom.inputs.to;
const compareToValue = +compareTo.value;
if (value >= compareToValue) {
value = compareToValue - compareTo.step;
}
} else {
const compareTo = this.dom.inputs.from;
const compareToValue = +compareTo.value;
if (value <= compareToValue) {
value = compareToValue + Number(compareTo.step);
}
}
this[key] = value
}
get trackWidth() {
return this.offsetWidth - this.dom.thumbs.to.offsetWidth;
}
get valueRange() {
return this.max - this.min
}
set from(val) {
this.setAttribute('from', val);
}
get from() {
return +this.getAttribute('from');
}
set to(val) {
this.setAttribute('to', val);
}
get to() {
return +this.getAttribute('to');
}
set min(val) {
this.setAttribute('min', val);
}
get min() {
return +this.getAttribute('min');
}
set max(val) {
this.setAttribute('max', val);
}
get max() {
return +this.getAttribute('max');
}
set step(val) {
this.setAttribute('step', val);
}
get step() {
return +this.getAttribute('step');
}
}
if (!customElements.get('double-range')) {
customElements.define('double-range', Dbrange);
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Dbrange Demo</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<double-range min="50" max="200" step="0" from="75" to="155"></double-range>
<script type="module" src="dbrange.js"></script>
</body>
</html>
export default {
mapDOM(scope) {
return {
track: scope.querySelector('.track'),
thumbs: {
from: scope.querySelector('.thumb[data-key="from"]'),
to: scope.querySelector('.thumb[data-key="to"]')
},
outputs: {
from: scope.querySelector('output[data-key="from"]'),
to: scope.querySelector('output[data-key="to"]')
},
inputs: {
from: scope.querySelector('input[data-key="from"]'),
to: scope.querySelector('input[data-key="to"]')
},
}
},
render() {
return `${this.css()}
${this.html()}`;
},
html() {
return `
<fieldset>
<legend class="visually-hidden">Double range</legend>
<div class="track"></div>
<output data-key="from"></output>
<output data-key="to"></output>
<label class="thumb" data-key="from" aria-label="установите мин. значение">
<input class="visually-hidden"
type="range"
name="value_from"
data-key="from"
>
</label>
<label class="thumb" data-key="to" aria-label="установите макс. значение">
<input class="visually-hidden"
type="range"
name="value_to"
data-key="to"
>
</label>
</fieldset>
`;
},
css() {
return `<style>
*,
*::before,
*::after {
box-sizing: border-box;
}
:host {
--range-height: 3rem;
--thumb-size: 1rem;
--thumb-color: rgba(0, 0, 0, 0.87);
--thumb-focus: 0 0 0 6px rgba(0, 0, 0, .25);
--track-color: #ddd;
--track-active-color: var(--thumb-color);
display:block;
max-width: 500px;
margin: 50px;
}
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
border: 0;
clip: rect(0 0 0 0);
overflow: hidden;
}
fieldset {
position: relative;
margin: 0;
padding: 0;
height: var(--range-height);
user-select: none;
border: none;
}
.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 {
position: absolute;
text-align: center;
bottom: 100%;
font-size: 0.8em;
}
.thumb {
position: absolute;
z-index: 2;
top: 0;
bottom: 0;
margin: auto;
width: var(--thumb-size);
height: var(--thumb-size);
border-radius: 50%;
background-color: var(--thumb-color);
touch-action: none;
cursor: pointer;
transition: box-shadow 0.3s;
}
.thumb:focus-within {
box-shadow: var(--thumb-focus);
}
</style>`;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment