Beispiel:Min-Max-Schieberegler.html
<!DOCTYPE html> <html lang="de"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Min-Max-Schieberegler</title> <style> output { display: block; } button { font-size: 1em; } double-range-input { border: thin dotted black; margin: 1em; width: 20em; height: 3em; display: block; } double-range-input[name=dr2] { --trackInnerColor: green; --trackOuterColor: lightgreen; --thumbColor: red; --thumbFocusColor: yellow; --thumbSize: 1.2em; --trackHeight: 1em; box-sizing: border-box; background-color: darkgray; border-radius: .5em; padding: .5em; } double-range-input[name=dr4]::part(valuedisplay) { border: thin solid blue; padding: .1em; } </style>
<script> 'use strict';
// Custom-Element double-range-input anlegen customElements.define('double-range-input', class extends HTMLElement {
// Einige private Eigenschaften #style; #doubleSlider; #inputLow; #inputHigh; #valueDisplay; #Attributes; #internals;
// double-range-input soll Formularelement sein static formAssociated = true;
// Festlegen, welche Attribute überwacht werden sollen static get observedAttributes() { return ['min', 'max', 'step', 'showvalues', 'value']; }
constructor() { super();
// Zugriff auf die ElementInternals, wird benötigt, um auf die Werte zuzugreifen, die mit dem Formular versendet werden. this.#internals = this.attachInternals();
// Aria-Rolle slider setzen this.#internals.ariaRole = 'slider';
// Schatten-DOM anlegen const shadow = this.attachShadow({mode: 'closed'}); this.#style = document.createElement('style'); shadow.appendChild(this.#style); this.#doubleSlider = document.createElement('div'); this.#doubleSlider.id = 'wrapper'; this.#doubleSlider.part = 'doubleslider'; shadow.appendChild(this.#doubleSlider);
// Defaultwerte der Attribute setzen this.#Attributes = { min: 0, max: 100, step: 1, showvalues: 'ever', currentValue: '25,75' }
// Eventhandler this.#doubleSlider.addEventListener('input', this.#handleInput.bind(this)); this.#doubleSlider.addEventListener('pointerdown', this.#handlePointerdown.bind(this)); this.#doubleSlider.addEventListener('pointerover', this.#handlePointerover.bind(this)); this.#doubleSlider.addEventListener('pointerout', this.#handlePointerout.bind(this)); this.#doubleSlider.addEventListener('change', this.#handleChange.bind(this)); }
connectedCallback() { // CSS und Elemente anlegen this.#style.innerHTML = this.#css(); this.#doubleSlider.innerHTML = this.#html(); this.#inputLow = this.#doubleSlider.querySelector('#Low'); this.#inputHigh = this.#doubleSlider.querySelector('#High'); this.#valueDisplay = this.#doubleSlider.querySelector('output');
this.#update(); }
attributeChangedCallback(name, oldValue, newValue) { switch(name) { case 'min': this.#Attributes.min = Number(newValue); if (this.#inputLow) this.#inputLow.min = Number(newValue); if (this.#inputHigh) this.#inputHigh.min = Number(newValue); break; case 'max': this.#Attributes.max = Number(newValue); if (this.#inputLow) this.#inputLow.max = Number(newValue); if (this.#inputHigh) this.#inputHigh.max = Number(newValue); break; case 'step': this.#Attributes.step = Number(newValue); if (this.#inputLow) this.#inputLow.step = Number(newValue); if (this.#inputHigh) this.#inputHigh.step = Number(newValue); break; case 'value': this.#setValue(newValue); break; case 'showvalues': if(['ever', 'active', 'never'].includes(newValue)) this.#Attributes.showvalues = newValue; break; } }
get form() { return this.#internals.form; }
set name(name) { this.setAttribute('name', name); }
get name() { return this.getAttribute('name'); }
set min(min) { this.setAttribute('min', min); }
get min() { return this.#Attributes.min; }
set max(max) { this.setAttribute('max', max); } get max() { return this.#Attributes.max; }
set step(step) { this.setAttribute('step', step); }
get step() { return this.#Attributes.step; }
set showvalues(showvalues) { this.setAttribute('showvalues', showvalues); }
get showvalues() { return this.#Attributes.showvalues; }
set value(value) { this.#setValue(value); }
get value() { const valueLow = this.#inputLow.value; const valueHigh = this.#inputHigh.value; const currentValue = `${valueLow},${valueHigh}`; return currentValue; }
#setValue(value) { if(value.split(',').length == 2) { let valueLow = Number(value.split(',')[0]); let valueHigh = Number(value.split(',')[1]); if ( valueLow > valueHigh ) { let temp = valueLow; valueLow = valueHigh; valueHigh = temp; } valueLow =this.#round(valueLow,this.#Attributes.step); valueHigh = this.#round(valueHigh,this.#Attributes.step); this.#Attributes.currentValue = `${valueLow},${valueHigh}`; this.#internals.setFormValue(this.#Attributes.currentValue); if (this.#valueDisplay) this.#valueDisplay.innerHTML = `${valueLow} – ${valueHigh}`; if (this.#inputLow) this.#inputLow.value = valueLow; if (this.#inputHigh) this.#inputHigh.value = valueHigh; if (this.#inputLow && this.#inputHigh) { this.#trackColor(); } } }
#update() { const currentValue = `${this.#inputLow.value},${this.#inputHigh.value}`; this.#internals.setFormValue(currentValue); this.#trackColor(); this.#valueDisplay.innerHTML = `${this.#inputLow.value} – ${this.#inputHigh.value}`; }
#trackColor() { const valueLow = Number(this.#inputLow.value); const valueHigh = Number(this.#inputHigh.value); const percentLow = (valueLow - this.#Attributes.min) / (this.#Attributes.max - this.#Attributes.min) * 100; const percentHigh = (valueHigh - this.#Attributes.min) / (this.#Attributes.max - this.#Attributes.min) * 100; this.#doubleSlider.style.setProperty('--percentLow', percentLow + '%'); this.#doubleSlider.style.setProperty('--percentHigh', percentHigh + '%'); }
#round(zahl, epsilon) { if (epsilon>=1) { return Math.round(zahl/epsilon)*epsilon; } else { const n = Math.ceil(-Math.log10(epsilon)); return Number(zahl.toFixed(n)); } }
// Eventhandler // input: Für unterer Wert <= oberer Wert begrenzen sorgen, // Werte an Internals übergeben und anzeigen #handleInput(inputEvent) { if(inputEvent.target == this.#inputLow && Number(this.#inputHigh.value) < Number(this.#inputLow.value)) { this.#inputHigh.value = this.#inputLow.value; } else if(inputEvent.target == this.#inputHigh && Number(this.#inputHigh.value) < Number(this.#inputLow.value)) { this.#inputLow.value = this.#inputHigh.value; } this.#update(); }
// pointerdown: den näher am Pointer liegenden Schieber zum Pointer schieben, // Werte anzeigen #handlePointerdown(pointerdownEvent) { // Find the horizontal position that was clicked let clickValue = this.#Attributes.min + (this.#Attributes.max - this.#Attributes.min) * pointerdownEvent.offsetX / pointerdownEvent.target.offsetWidth; clickValue = this.#round(clickValue,this.#Attributes.step); const middleValue = (Number(this.#inputHigh.value) + Number(this.#inputLow.value))/2; if(clickValue < middleValue) { this.#inputLow.value = clickValue; } else { this.#inputHigh.value = clickValue; } this.#update(); this.dispatchEvent(new Event('input', { bubbles: true })); this.dispatchEvent(new Event('change', { bubbles: true })); }
// pointerover: Bei Bedarf Werteanzeige einblenden #handlePointerover(pointeroverEvent) { if(this.#Attributes.showvalues == 'active') this.#valueDisplay.className = 'ever'; }
// pointerout: Den Schiebern den Focus nehmen und bei Bedarf Werteanzeige ausblenden #handlePointerout(pointeroutEvent) { this.#inputLow.blur(); this.#inputHigh.blur(); this.#valueDisplay.className = this.#Attributes.showvalues; }
// change durchreichen #handleChange(changeEvent) { this.dispatchEvent(new Event('change', { bubbles: true })); }
#html() { return ` <label for='Low' slot='labellow'>Unterer Wert</label> <input id='Low' type='range' min=${this.#Attributes.min} max=${this.#Attributes.max} step=${this.#Attributes.step} value=${this.#Attributes.currentValue.split(',')[0]}></input> <label for='High' slot='labelhigh'>Oberer Wert</label> <input id='High' type='range' min=${this.#Attributes.min} max=${this.#Attributes.max} step=${this.#Attributes.step} value=${this.#Attributes.currentValue.split(',')[1]}></input> <output part='valuedisplay' role='status' aria-live='assertive' class='${this.#Attributes.showvalues}'></output> `; }
#css() { return `
- host {
--trackInnerColor: lightblue; --trackOuterColor: lightgray; --thumbColor: blue; --thumbFocusColor: white; --thumbSize: 1.7em; --trackHeight: .3em; display: inline-block; width: 10em; height: 2em; }
- wrapper {
position: relative; display: grid; width: 100%; height: 100%; border: none; padding: 0; margin: 0; background-size: auto var(--trackHeight) !important; background-repeat: no-repeat !important; background-position-y: 50% !important; background: linear-gradient(to right, var(--trackOuterColor) var(--percentLow), var(--trackInnerColor) var(--percentLow), var(--trackInnerColor) var(--percentHigh), var(--trackOuterColor) var(--percentHigh) ); }
- wrapper label {
grid-area: 1 / 1 / 1 / 1; clip: rect(0 0 0 0); clip-path: inset(50%); width: 1px; height: 1px; overflow: hidden; white-space: nowrap; }
- wrapper output {
position:absolute; padding: 0; left: 50%; transform: translate(-50%, -.8em); background-color: white; pointer-events: none; display: none; }
- wrapper output.ever {
display: block; }
- wrapper:focus-within output.active {
display: block; }
- wrapper input {
grid-area: 1 / 1 / 1 / 1; -webkit-appearance: none; -moz-appearance: none; appearance: none; width: 100%; background-color: transparent; pointer-events: none; }
- wrapper input::-webkit-slider-runnable-track {
-webkit-appearance: none; appearance: none; }
- wrapper input::-moz-range-track {
-moz-appearance: none; appearance: none; }
- wrapper input::-webkit-slider-thumb {
-webkit-appearance: none; appearance: none; height: var(--thumbSize); width: var(--thumbSize); cursor: pointer; pointer-events: auto; border-radius: 50%; background-color: var(--thumbColor); }
- wrapper input::-moz-range-thumb {
-moz-appearance: none; appearance: none; height: var(--thumbSize); width: var(--thumbSize); cursor: pointer; pointer-events: auto; border-radius: 50%; background-color: var(--thumbColor); }
- wrapper input:is(:focus,:hover)::-webkit-slider-thumb {
background-color: var(--thumbFocusColor); border: medium solid var(--thumbColor); }
- wrapper input:is(:focus,:hover)::-moz-range-thumb {
background-color: var(--thumbFocusColor); border: medium solid var(--thumbColor); } `; }
}); // double-range-input
// Diverse Tests
document.addEventListener('DOMContentLoaded', () => {
const out = document.querySelector('output'); const form = document.querySelector('form'); const button = form.querySelector('button');
// Element anlegen
const label = document.createElement('label');
// label.innerHTML = 'Wert 4: Geben Sie Minimal- und Maximalwert an.
Die Vorgaben werden alle 5s geändert.';
label.innerHTML = 'Wert 4: Geben Sie Minimal- und Maximalwert an.';
const dr4 = document.createElement('double-range-input');
// Attributzuweisungen funktionieren auch mit setAttribute
dr4.name = 'dr4';
dr4.min = -1;
dr4.max = 1;
dr4.step = 0.01;
dr4.value = '-0.5, 0.5';
form.insertBefore(dr4,button);
form.insertBefore(label,dr4);
const sliders = document.querySelectorAll('double-range-input');
// Startwerte abfragen sliders.forEach((v,i,a) => { console.log(i,v.name,v.value,v.form); if(i == 0) out.innerText = 'Startwerte:'; out.innerText += ` Element ${i}: ${v.name} ${v.value};`; });
// Werte bei input-Event abfragen form.addEventListener('input', event => { console.log('input', event.target.name, event.target.value); out.innerText = `input: Element ${event.target.name}: ${event.target.value};`; });
// Werte bei change-Event abfragen form.addEventListener('change', event => { console.log('change', event.target.name, event.target.value); out.innerText += ` change: Element ${event.target.name}: ${event.target.value};`; });
// Submit abfangen und Formularwerte anzeigen
form.addEventListener('submit', (event) => {
event.preventDefault();
const data = new FormData(event.target);
console.log([...data.entries()]);
out.innerText = ;
[...data.entries()].forEach(v => out.innerHTML += `${v[0]}: ${v[1]}
`)
});
// Value dynamisch ändern //setInterval(() => { let value = sliders[3].value; let valueLow = Number(value.split(',')[0]); let valueHigh = Number(value.split(',')[1]); let min = sliders[3].min; let max = sliders[3].max; valueLow += (Math.random()-.5)*(max-min)/10; valueHigh += (Math.random()-.5)*(max-min)/10; value = `${valueLow},${valueHigh}`; sliders[3].value = value; //}, 5000);
}); </script>
</head>
<body>
Min-Max-Schieberegler
<form method="get">
<label for="dr1">Wert 1: Geben Sie Minimal- und Maximalwert an.</label> <double-range-input name="dr1"></double-range-input> <label for="dr2">Wert 2: Geben Sie Minimal- und Maximalwert an.</label> <double-range-input name="dr2" min=10 max=60 showvalues="never"> <slot name='labellow'>Nicht weniger als</slot> <slot name='labelhigh'>Nicht mehr als</slot> </double-range-input> <label for="dr3">Wert 3: Geben Sie Minimal- und Maximalwert an.</label> <double-range-input name="dr3" value="20,30" min=-10 max=60 showvalues="active"></double-range-input>
<button>Submit</button>
</form>
<output></output>
</body> </html>