range is interactive
This commit is contained in:
parent
d4c25abbc4
commit
d7f93c8a20
6 changed files with 164 additions and 69 deletions
55
app/app.css
55
app/app.css
|
@ -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;
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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"));
|
||||||
|
|
Loading…
Reference in a new issue