range is interactive

This commit is contained in:
Ondrej Zara 2019-04-26 13:48:23 +02:00
parent d4c25abbc4
commit d7f93c8a20
6 changed files with 164 additions and 69 deletions

View file

@ -96,31 +96,59 @@ select {
font-weight: normal; font-weight: normal;
} }
x-range { x-range {
--thumb-size: 8px;
--thumb-color: #fff;
--thumb-shadow: #000;
--track-size: 4px;
--track-color: gray;
--elapsed-color: lightgray;
--remaining-color: transparent;
--radius: calc(var(--track-size)/2);
width: 144px; width: 144px;
height: 16px; height: 16px;
position: relative; position: relative;
--thumb: 6px;
--track: 4px;
padding: 0 var(--thumb);
} }
x-range .-track, x-range .-track,
x-range .-elapsed { x-range .-elapsed,
x-range .-remaining {
position: absolute; position: absolute;
top: calc(50% - var(--track)/2); top: calc(50% - var(--track-size)/2);
height: var(--track); height: var(--track-size);
left: 0; border-radius: var(--radius);
} }
x-range .-track { x-range .-track {
width: 100%; width: 100%;
left: 0;
background-color: gray;
}
x-range .-elapsed {
left: 0;
background-color: var(--elapsed-color);
}
x-range .-remaining {
right: 0;
background-color: var(--remaining-color);
}
x-range .-inner {
position: absolute;
left: var(--thumb-size);
right: var(--thumb-size);
top: 0;
bottom: 0;
} }
x-range .-thumb { x-range .-thumb {
all: unset;
position: absolute; position: absolute;
top: 50%; top: 50%;
border-radius: 50%;
width: calc(2*var(--thumb));
height: calc(2*var(--thumb));
background-color: #fff;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
border-radius: 50%;
width: calc(2*var(--thumb-size));
height: calc(2*var(--thumb-size));
background-color: var(--thumb-color);
box-shadow: 0 0 2px var(--thumb-shadow);
}
x-range[disabled] {
opacity: 0.5;
} }
main { main {
flex-grow: 1; flex-grow: 1;
@ -179,10 +207,9 @@ nav ul li.active {
#player:not([data-flags~=repeat]) .repeat { #player:not([data-flags~=repeat]) .repeat {
opacity: 0.5; opacity: 0.5;
} }
#player [type=range] { #player x-range {
margin: 0;
width: 0;
flex-grow: 1; flex-grow: 1;
--elapsed-color: var(--primary);
} }
#player .art { #player .art {
margin-right: 0; margin-right: 0;

View file

@ -7,10 +7,9 @@
&[data-state=play] .play { display: none; } &[data-state=play] .play { display: none; }
&:not([data-flags~=random]) .random, &:not([data-flags~=repeat]) .repeat { opacity: 0.5; } &:not([data-flags~=random]) .random, &:not([data-flags~=repeat]) .repeat { opacity: 0.5; }
[type=range] { x-range {
margin: 0;
width: 0; // fails to shrink otherwise
flex-grow: 1; flex-grow: 1;
--elapsed-color: var(--primary);
} }
.art { .art {

View file

@ -1,36 +1,63 @@
x-range { x-range {
--thumb-size: 8px;
--thumb-color: #fff;
--thumb-shadow: #000;
--track-size: 4px;
--track-color: gray;
--elapsed-color: lightgray;
--remaining-color: transparent;
--radius: calc(var(--track-size)/2);
width: 144px; width: 144px;
height: 16px; height: 16px;
position: relative; position: relative;
--thumb: 6px; .-track, .-elapsed, .-remaining {
--track: 4px;
padding: 0 var(--thumb);
.-track, .-elapsed {
position: absolute; position: absolute;
top: calc(50% - var(--track)/2); top: calc(50% - var(--track-size)/2);
height: var(--track); height: var(--track-size);
left: 0; border-radius: var(--radius);
} }
.-track { .-track {
width: 100%; width: 100%;
left: 0;
background-color: gray;
} }
.-elapsed { .-elapsed {
left: 0;
background-color: var(--elapsed-color);
}
.-remaining {
right: 0;
background-color: var(--remaining-color);
}
.-inner {
position: absolute;
left: var(--thumb-size);
right: var(--thumb-size);
top: 0;
bottom: 0;
} }
.-thumb { .-thumb {
all: unset;
position: absolute; position: absolute;
top: 50%; top: 50%;
transform: translate(-50%, -50%);
border-radius: 50%; border-radius: 50%;
width: calc(2*var(--thumb)); width: calc(2*var(--thumb-size));
height: calc(2*var(--thumb)); height: calc(2*var(--thumb-size));
background-color: #fff; background-color: var(--thumb-color);
box-shadow: 0 0 2px var(--thumb-shadow);
}
transform: translate(-50%, -50%); &[disabled] {
opacity: 0.5;
} }
} }

View file

@ -15,7 +15,7 @@
<div class="subtitle"></div> <div class="subtitle"></div>
<div class="timeline"> <div class="timeline">
<span class="elapsed"></span> <span class="elapsed"></span>
<input type="range" /> <x-range></x-range>
<span class="duration"></span> <span class="duration"></span>
</div> </div>
</div> </div>
@ -28,8 +28,7 @@
</div> </div>
<div class="volume"> <div class="volume">
<button class="mute" data-icon="volume-high"></button> <button class="mute" data-icon="volume-high"></button>
<input type="range" /> <x-range></x-range>
<x-range min="0" />
</div> </div>
</div> </div>
<div class="misc"> <div class="misc">

View file

@ -1,58 +1,101 @@
import * as html from "./html.js"; import * as html from "./html.js";
class Range extends HTMLElement { class Range extends HTMLElement {
static get observedAttributes() { return ["min", "max", "value"]; } static get observedAttributes() { return ["min", "max", "value", "disabled"]; }
constructor() { constructor() {
super(); super();
this._data = { this._dom = {};
min: 0,
max: 100,
value: 50
};
this.track = html.node("span", {className:"-track"}); this.innerHTML = `
this.elapsed = html.node("span", {className:"-elapsed"}); <span class="-track"></span>
this.thumb = html.node("span", {className:"-thumb"}); <span class="-elapsed"></span>
<span class="-remaining"></span>
<div class="-inner">
<button class="-thumb"></button>
</div>
`;
this.appendChild(this.track); Array.from(this.querySelectorAll("[class^='-']")).forEach(node => {
this.appendChild(this.elapsed); let name = node.className.substring(1);
this.appendChild(this.thumb); this._dom[name] = node;
});
this._update(); this._update();
this.addEventListener("mousedown", this);
} }
set value(value) { set min(min) { this.setAttribute("min", min); }
value = Math.max(value, this._data.min); set max(max) { this.setAttribute("max", max); }
value = Math.min(value, this._data.max); set value(value) { this.setAttribute("value", value); }
this._data.value = value; set disabled(disabled) {
this._update(); disabled ? this.setAttribute("disabled", "") : this.removeAttribute("disabled");
} }
set min(min) { get value() { return (this.hasAttribute("value") ? this.getAttribute("value") : "50"); }
this._data.min = Math.min(min, this._data.max); get valueAsNumber() { return Number(this.value); }
this._update(); get min() { return this.getAttribute("min"); }
} get max() { return this.getAttribute("max"); }
get disabled() { return this.hasAttribute("disabled"); }
set max(max) {
this._data.max = Math.max(max, this._data.max);
this._update();
}
attributeChangedCallback(name, oldValue, newValue) { attributeChangedCallback(name, oldValue, newValue) {
this[name] = newValue; switch (name) {
case "min":
case "max":
case "value":
this._update();
break;
}
} }
get value() { return this._data.value; } handleEvent(e) {
get valueAsNumber() { return Number(this._data.value); } switch (e.type) {
get min() { return this._data.min; } case "mousedown":
get max() { return this._data.max; } if (this.disabled) { return; }
document.addEventListener("mousemove", this);
document.addEventListener("mouseup", this);
this._setToMouse(e);
break;
case "mousemove":
this._setToMouse(e);
break;
case "mouseup":
document.removeEventListener("mousemove", this);
document.removeEventListener("mouseup", this);
this.dispatchEvent(new CustomEvent("change"));
break;
}
}
_update() { _update() {
let frac = (this._data.value - this._data.min) / (this._data.max - this._data.min); let min = Number(this.min || 0);
this.thumb.style.left = `${frac * 100}%`; let max = Number(this.max || 100);
this.elapsed.style.width = `${frac * 100}%`; let frac = (this.valueAsNumber - min) / (max - min);
this._dom.thumb.style.left = `${frac * 100}%`;
this._dom.remaining.style.left = `${frac * 100}%`;
this._dom.elapsed.style.width = `${frac * 100}%`;
}
_setToMouse(e) {
let rect = this._dom.inner.getBoundingClientRect();
let x = e.clientX;
x = Math.max(x, rect.left);
x = Math.min(x, rect.right);
let min = Number(this.min || 0);
let max = Number(this.max || 100);
let frac = (x-rect.left)/(rect.right-rect.left);
let value = min + frac * (max-min);
value = Math.round(value); // fixme
if (value == this.valueAsNumber) { return; }
this.value = value.toString();
this.dispatchEvent(new CustomEvent("input"));
} }
} }

View file

@ -112,8 +112,8 @@ export function init(n) {
let all = node.querySelectorAll("[class]"); let all = node.querySelectorAll("[class]");
Array.from(all).forEach(node => DOM[node.className] = node); Array.from(all).forEach(node => DOM[node.className] = node);
DOM.progress = DOM.timeline.querySelector("[type=range]"); DOM.progress = DOM.timeline.querySelector("x-range");
DOM.volume = DOM.volume.querySelector("[type=range]"); DOM.volume = DOM.volume.querySelector("x-range");
DOM.play.addEventListener("click", e => command("play")); DOM.play.addEventListener("click", e => command("play"));
DOM.pause.addEventListener("click", e => command("pause 1")); DOM.pause.addEventListener("click", e => command("pause 1"));