Last active
January 10, 2021 18:30
-
-
Save b-aleksei/242fc52cb981727bdcc0ad9ef98a9803 to your computer and use it in GitHub Desktop.
WC-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
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); | |
} |
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
<!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> |
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 { | |
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