Skip to content

Instantly share code, notes, and snippets.

@b-aleksei
Last active January 10, 2021 18:30
Show Gist options
  • Save b-aleksei/7bec0b91f6ede75954e64631ddd343fb to your computer and use it in GitHub Desktop.
Save b-aleksei/7bec0b91f6ede75954e64631ddd343fb to your computer and use it in GitHub Desktop.
wc-slider
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Slider Demo</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<wc-slider min="50" max="200" step="0" value="100"></wc-slider>
<script type="module" src="slider.js"></script>
</body>
</html>
import Template from './template.js';
export default class Slider 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',
};
window.test = this
this.root.addEventListener(this.eventNames.pointerdown, this);
this.dom.input.addEventListener('input', this);
const resizeObserver = new ResizeObserver(() => {
this.renderSlider(this.dom.input.value)
})
resizeObserver.observe(this)
}
static get observedAttributes() {
return ['value'];
}
attributeChangedCallback(name, oldVal, newValue) {
if (name === 'value' && this._initialized) {
this.dom.input.value = newValue;
this.renderSlider(this.dom.input.value);
}
if (!this._initialized) {
this.dom.input.min = this.min
this.dom.input.max = this.max
this.dom.input.step = this.step
this.dom.input.value = this.value
this._initialized = true
}
}
renderSlider(value) {
const val = value - this.min;
const translate = (val / this.valueRange) * this.trackWidth;
this.dom.thumb.style.transform = `translate3d(${translate}px, 0, 0)`;
this.dom.thumb.dataset.translate = `${translate}`;
this.dom.track.style.setProperty('--end-fill', translate + 'px');
}
handleEvent(e) {
switch (e.type) {
case this.eventNames.pointerdown:
if (e.target.closest('.thumb')) {
this._shift = e.clientX - this.dom.thumb.dataset.translate;
} else {
this._shift = this.offsetLeft + (this.dom.thumb.offsetWidth / 2);
}
this.dom.thumb.setPointerCapture(e.pointerId);
this.dom.thumb.addEventListener(this.eventNames.pointermove, this);
this.dom.thumb.addEventListener(this.eventNames.pointerup, this);
case this.eventNames.pointermove:
const x = e.clientX - this._shift;
const value = ((x / this.trackWidth) * this.valueRange) + this.min;
this.dom.input.focus();
this.dom.input.value = value;
this.value = this.dom.input.value;
break;
case this.eventNames.pointerup:
this.dom.thumb.removeEventListener(this.eventNames.pointermove, this);
this.dom.thumb.removeEventListener(this.eventNames.pointerup, this);
break;
case 'input':
this.value = this.dom.input.value;
}
}
get trackWidth() {
return this.offsetWidth - this.dom.thumb.offsetWidth;
}
get valueRange() {
return this.max - this.min
}
set value(val) {
this.setAttribute('value', val);
}
get value() {
return +this.getAttribute('value');
}
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('wc-slider')) {
customElements.define('wc-slider', Slider);
}
export default {
mapDOM(scope) {
return {
track: scope.querySelector('.track'),
thumb: scope.querySelector('.thumb'),
input: scope.querySelector('input[type="range"]'),
}
},
render() {
return `${this.css()}
${this.html()}`;
},
html() {
return `
<fieldset>
<legend class="visually-hidden">Slider range</legend>
<div class="track"></div>
<label class="thumb">
<input class="visually-hidden" type="range" name="range">
</label>
</fieldset>`;
},
css() {
return `
<style>
*,
*::before,
*::after {
box-sizing: border-box;
}
:host {
--range-height: 3rem;
--track-height: 0.25rem;
--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 {
--end-fill: 0;
position: absolute;
z-index: 1;
top: 0;
bottom: 0;
margin: auto;
width: 100%;
height: var(--track-height);
background-color: var(--track-color);
background-image: linear-gradient(to right, var(--track-active-color) var(--end-fill), transparent var(--end-fill));
border-radius: .25rem;
cursor: pointer;
}
.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