From d7f93c8a20488f1c4ef64d7a1f0a63ac2a5e2d9f Mon Sep 17 00:00:00 2001 From: Ondrej Zara Date: Fri, 26 Apr 2019 13:48:23 +0200 Subject: [PATCH] range is interactive --- app/app.css | 55 ++++++++++++++++------ app/css/player.less | 5 +- app/css/range.less | 53 +++++++++++++++------ app/index.html | 5 +- app/js/lib/range.js | 111 ++++++++++++++++++++++++++++++-------------- app/js/player.js | 4 +- 6 files changed, 164 insertions(+), 69 deletions(-) diff --git a/app/app.css b/app/app.css index a9160ad..35827c2 100644 --- a/app/app.css +++ b/app/app.css @@ -96,31 +96,59 @@ select { font-weight: normal; } 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; height: 16px; position: relative; - --thumb: 6px; - --track: 4px; - padding: 0 var(--thumb); } x-range .-track, -x-range .-elapsed { +x-range .-elapsed, +x-range .-remaining { position: absolute; - top: calc(50% - var(--track)/2); - height: var(--track); - left: 0; + top: calc(50% - var(--track-size)/2); + height: var(--track-size); + border-radius: var(--radius); } x-range .-track { 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 { + all: unset; position: absolute; top: 50%; - border-radius: 50%; - width: calc(2*var(--thumb)); - height: calc(2*var(--thumb)); - background-color: #fff; 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 { flex-grow: 1; @@ -179,10 +207,9 @@ nav ul li.active { #player:not([data-flags~=repeat]) .repeat { opacity: 0.5; } -#player [type=range] { - margin: 0; - width: 0; +#player x-range { flex-grow: 1; + --elapsed-color: var(--primary); } #player .art { margin-right: 0; diff --git a/app/css/player.less b/app/css/player.less index b2dd044..edab6ac 100644 --- a/app/css/player.less +++ b/app/css/player.less @@ -7,10 +7,9 @@ &[data-state=play] .play { display: none; } &:not([data-flags~=random]) .random, &:not([data-flags~=repeat]) .repeat { opacity: 0.5; } - [type=range] { - margin: 0; - width: 0; // fails to shrink otherwise + x-range { flex-grow: 1; + --elapsed-color: var(--primary); } .art { diff --git a/app/css/range.less b/app/css/range.less index a6d6e01..e20475a 100644 --- a/app/css/range.less +++ b/app/css/range.less @@ -1,36 +1,63 @@ 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; height: 16px; position: relative; - --thumb: 6px; - --track: 4px; - padding: 0 var(--thumb); - - .-track, .-elapsed { + .-track, .-elapsed, .-remaining { position: absolute; - top: calc(50% - var(--track)/2); - height: var(--track); - left: 0; + top: calc(50% - var(--track-size)/2); + height: var(--track-size); + border-radius: var(--radius); } .-track { width: 100%; + left: 0; + background-color: gray; } .-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 { + all: unset; position: absolute; top: 50%; + transform: translate(-50%, -50%); border-radius: 50%; - width: calc(2*var(--thumb)); - height: calc(2*var(--thumb)); - background-color: #fff; + 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); + } - transform: translate(-50%, -50%); + &[disabled] { + opacity: 0.5; } } diff --git a/app/index.html b/app/index.html index a28bfb7..242a05e 100644 --- a/app/index.html +++ b/app/index.html @@ -15,7 +15,7 @@
- +
@@ -28,8 +28,7 @@
- - +
diff --git a/app/js/lib/range.js b/app/js/lib/range.js index 291dd82..86d77df 100644 --- a/app/js/lib/range.js +++ b/app/js/lib/range.js @@ -1,58 +1,101 @@ import * as html from "./html.js"; class Range extends HTMLElement { - static get observedAttributes() { return ["min", "max", "value"]; } + static get observedAttributes() { return ["min", "max", "value", "disabled"]; } constructor() { super(); - this._data = { - min: 0, - max: 100, - value: 50 - }; + this._dom = {}; - this.track = html.node("span", {className:"-track"}); - this.elapsed = html.node("span", {className:"-elapsed"}); - this.thumb = html.node("span", {className:"-thumb"}); + this.innerHTML = ` + + + +
+ +
+ `; - this.appendChild(this.track); - this.appendChild(this.elapsed); - this.appendChild(this.thumb); + Array.from(this.querySelectorAll("[class^='-']")).forEach(node => { + let name = node.className.substring(1); + this._dom[name] = node; + }); this._update(); + + this.addEventListener("mousedown", this); } - set value(value) { - value = Math.max(value, this._data.min); - value = Math.min(value, this._data.max); - this._data.value = value; - this._update(); + set min(min) { this.setAttribute("min", min); } + set max(max) { this.setAttribute("max", max); } + set value(value) { this.setAttribute("value", value); } + set disabled(disabled) { + disabled ? this.setAttribute("disabled", "") : this.removeAttribute("disabled"); } - set min(min) { - this._data.min = Math.min(min, this._data.max); - this._update(); - } - - set max(max) { - this._data.max = Math.max(max, this._data.max); - this._update(); - } + get value() { return (this.hasAttribute("value") ? this.getAttribute("value") : "50"); } + get valueAsNumber() { return Number(this.value); } + get min() { return this.getAttribute("min"); } + get max() { return this.getAttribute("max"); } + get disabled() { return this.hasAttribute("disabled"); } attributeChangedCallback(name, oldValue, newValue) { - this[name] = newValue; + switch (name) { + case "min": + case "max": + case "value": + this._update(); + break; + } } - get value() { return this._data.value; } - get valueAsNumber() { return Number(this._data.value); } - get min() { return this._data.min; } - get max() { return this._data.max; } + handleEvent(e) { + switch (e.type) { + case "mousedown": + 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() { - let frac = (this._data.value - this._data.min) / (this._data.max - this._data.min); - this.thumb.style.left = `${frac * 100}%`; - this.elapsed.style.width = `${frac * 100}%`; + let min = Number(this.min || 0); + let max = Number(this.max || 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")); } } diff --git a/app/js/player.js b/app/js/player.js index 22243e0..77df796 100644 --- a/app/js/player.js +++ b/app/js/player.js @@ -112,8 +112,8 @@ export function init(n) { let all = node.querySelectorAll("[class]"); Array.from(all).forEach(node => DOM[node.className] = node); - DOM.progress = DOM.timeline.querySelector("[type=range]"); - DOM.volume = DOM.volume.querySelector("[type=range]"); + DOM.progress = DOM.timeline.querySelector("x-range"); + DOM.volume = DOM.volume.querySelector("x-range"); DOM.play.addEventListener("click", e => command("play")); DOM.pause.addEventListener("click", e => command("pause 1"));