Effortless smooth values and ranges
const slide = new Slider("#slider", {
val: 50,
min: 0,
max: 100,
col: colors(1)
});
const slide = new Slider("#slider", {
val: 50,
max: 100,
stp: 10,
col: colors(1)
});
const slide = new Slider("#slider", {
max: 100,
val: [8, 16, 32, 64],
col: colors(4),
});
// two values + one color = range
const slide = new Slider("#slider", {
val: [25, 75],
col: colors(1)
});
// minimum range = 10
const slide = new Slider("#slider", {
val: [25, 75],
rng: 10,
col: colors(1)
});
// n values + n / 2 colors = ranges
const slide = new Slider("#slider", {
val: [5, 10, 20, 35, 50, 80],
col: colors(3),
});
// n values + (n - 1) colors = linked ranges
const slide = new Slider("#slider", {
val: [10, 50, 70],
col: colors(2)
});
// 3x linked ranges
const slide = new Slider("#slider", {
val: [10, 20, 50, 70],
col: colors(3)
});
// time format function
const slide = new Slider("#slider", {
max: 5400, // seconds
val: 3600, // seconds
fmt: s =>
(s < 3600 ? [60, 1] : [3600, 60, 1])
.map(x =>
`0${~~(s / x) % 60}`.slice(-2))
.join(":"),
col: colors(1)
});
const slide = new Slider("#slider", {
min: 0,
max: 0.1,
stp: 0.001,
val: [0.016, 0.032, 0.064],
col: colors(3)
});
const colors = (num = 1) => [
"ff595e", "ff924c", "ffca3a",
"c5ca30", "8ac926", "52a675",
"1982c4", "4267ac", "6a4c93"]
.sort(() => Math.random() - 0.5)
.slice(0, num)
.map(col => "#" + col);
:root {
--slideHeight: 36px; /* slider height */
--trackHeight: 6px; /* slide track height */
--focusHeight: 10px; /* sliding track height */
--trackColor: #e5e7eb; /* track background color */
--textColor: #6b7280; /* min & max labels color */
--fontSize: 14px; /* labels font size */
--animate: 0.2s ease; /* sliding state animation */
}
<div id="slider"></div>
<div id="slides"></div>
body {
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.slider {
width: 360px;
max-width: 360px;
max-width: 90%;
}
#slides {
font-size: var(--fontSize);
pointer-events: none;
}
const slides = document
.querySelector("#slides");
const disp = dat =>
slides.innerHTML = [dat]
.flat()
.map(
(val, idx) =>
`<font color="${slide.colors[idx]}">${JSON.stringify(val)}</font>`
)
.join(" ");
slide
.on(
"slide",
evt =>
disp(evt.detail)
);
slide
.on(
"change",
evt =>
disp(evt.detail)
);
disp(slide.values);
/**
* @force
* @export
* @class Slider :
*/
class Slider {
/**
* @construct
* @param {HTMLElement|string} container :
* @param {!SliderOptions=} opts :
*/
constructor(container, opts = {}) {
this.dec = String(opts.stp || "")
.includes(".")
? String(opts.stp)
.split(".")[1].length
: 0;
opts = {
min: 0,
max: 100,
stp: 1,
num: 1,
col: "#4A90E2",
fmt: v =>
v.toFixed(this.dec),
val: null,
rng: 0,
...opts
};
this.track
= this.ranges
= this.valueBox
= this.labelBox
= this.minLabel
= this.maxLabel
= this.valueLabels
= this._frameLoop
= null;
this._startVal = 0;
this._startPos = 0;
this._step = opts.stp;
this._range = opts.max - opts.min;
this._disp = opts.fmt;
this._colors = [opts.col].flat();
this._ranged = opts.rng;
this._activeIdx = -1;
this._isMoving = false;
this._frameLoop = null;
const values = opts.val
? [opts.val].flat()
: this._defaultValues(opts.num);
this._num = values.length;
this._vals = Array(this._num)
.fill(0);
this._targetVals = Array(this._num)
.fill(0);
this._min = opts.min;
this._max = opts.max;
this.wrap = container;
if(typeof container === "string")
this.wrap = document
.querySelector(container);
this.wrap.classList.add("slider");
this.wrap.tabIndex = 0;
this._createElements();
this._setupEvents();
this.values = values;
this._evts = new NativeEventTarget();
}
_createElements() {
const c = cls =>
Object.assign(
document.createElement("div"),
{
className: cls
}
);
[
this.track,
this.valueBox,
this.labelBox,
this.minLabel,
this.maxLabel
] = ["track", "vals", "lbls", "", ""].map(c);
this.ranges = Array(Math.max(
this._num,
this._colors.length
))
.fill(0)
.map(() =>
c("range"));
this.valueLabels = Array(this._num)
.fill(0)
.map(() =>
c("val"));
this.valueBox.append(...this.valueLabels);
this.wrap.append(this.valueBox);
this.track.append(...this.ranges);
this.wrap.append(this.track);
this.labelBox.append(
this.minLabel,
this.maxLabel
);
this.wrap.append(this.labelBox);
this.minLabel.innerText = this._disp(this._min);
this.maxLabel.innerText = this._disp(this._max);
}
_setupEvents() {
const move = evt =>
this._activeIdx !== -1 && this._onMove(evt),
up = evt =>
this._activeIdx !== -1 && this._onUp(evt);
this.wrap.addEventListener(
"pointerdown",
evt =>
this._onDown(evt)
);
["pointermove", "pointerup", "pointercancel"].forEach(evt =>
this.wrap.addEventListener(
evt,
evt === "pointermove" ? move : up
));
// this.wrap.addEventListener("keydown", evt => this._onKey(evt));
}
_defaultValues(n) {
const stp = this._range / (n + 1);
return Array(n)
.fill(0)
.map((_, i) =>
this._roundToStep(this._min + stp * (i + 1)));
}
/**
* @export
* @getter
* @type {number|Array} values : slider values
*/
get values() {
const vals = this._vals.map(v =>
this._roundToStep(v));
if(this._num === 1)
return vals[0];
if(this._colors.length >= this._num)
return vals;
if(this._colors.length === this._num - 1) {
const sorted = [...vals].sort((a, b) =>
a - b);
return Array(sorted.length - 1)
.fill(0)
.map((_, i) =>
[sorted[i], sorted[i + 1]]);
}
if(this._num % 2 === 0) {
const ranges = [];
for(let i = 0; i < vals.length; i += 2) {
ranges.push([vals[i], vals[i + 1]].sort((a, b) =>
a - b));
}
return ranges.length === 1 ? ranges[0] : ranges;
}
const sorted = [...vals].sort((a, b) =>
a - b);
return Array(sorted.length - 1)
.fill(0)
.map((_, i) =>
[sorted[i], sorted[i + 1]]);
}
/**
* @setter
* @type {number|Array} values : slider values
*/
set values(v) {
// IGNORE IF USER IS SLIDING ?
const newVals = [v]
.flat()
.map(val =>
(Array.isArray(val) ? val : [val]))
.flat();
if(newVals.length !== this._num) {
throw new Error(this._num + " values expected");
}
this._vals = newVals.map(v =>
this._forceRange(+v));
this._targetVals = [...this._vals];
this._updateUI();
}
/**
* @export
* @getter
* @type {Array} colors : slider colors
*/
get colors() {
return this._colors;
}
/**
* @setter
* @type {Array} colors : slider colors
*/
set colors(c) {
this._colors = c;
this._updateUI();
}
_calculateLabelPositions(pos) {
const rWidth = this.wrap.offsetWidth,
minGap = 4;
const labels = pos
.map((p, i) =>
({
origPos: p,
origPx: (p / 100) * rWidth,
idx: i,
fullWidth: this.valueLabels[i].offsetWidth,
halfWidth: this.valueLabels[i].offsetWidth / 2
}))
.sort((a, b) =>
a.origPx - b.origPx);
const result = [...pos];
const processEdge = (l, left) => {
const edgePx = left ? l.halfWidth : rWidth - l.halfWidth,
off = left ? 1 : -1;
l.origPx = edgePx;
result[l.idx] = (edgePx / rWidth) * 100;
labels
.filter(
o =>
o !== l
&& Math.abs(o.origPx - l.origPx) < l.fullWidth + minGap
)
.forEach((n, i) => {
n.origPx = edgePx + (i + 1) * (minGap + 2) * off;
result[n.idx] = (n.origPx / rWidth) * 100;
});
};
labels
.filter(l =>
l.origPx < l.halfWidth)
.forEach(l =>
processEdge(
l,
true
));
labels
.filter(l =>
l.origPx > rWidth - l.halfWidth)
.forEach(l =>
processEdge(
l,
false
));
for(let iter = 0, hasOverlap = true; hasOverlap && iter < 50; iter++) {
hasOverlap = false;
for(let i = 0; i < labels.length - 1; i++) {
const [c, n] = [labels[i], labels[i + 1]],
minSpace = (c.fullWidth + n.fullWidth) / 2 + minGap,
space = n.origPx - c.origPx;
if(space < minSpace) {
hasOverlap = true;
const adj = (minSpace - space) / 2,
lAdj = Math.min(
adj,
c.origPx - c.halfWidth
),
rAdj = Math.min(
adj,
rWidth - n.halfWidth - n.origPx
);
c.origPx -= lAdj;
n.origPx += rAdj;
result[c.idx] = (c.origPx / rWidth) * 100;
result[n.idx] = (n.origPx / rWidth) * 100;
}
}
}
return result;
}
_updateUI() {
const pos = this._vals.map(
v =>
((v - this._min) / this._range) * 100
),
cLen = this._colors.length,
sorted = this._vals
.map((v, i) =>
({
v, i
}))
.sort((a, b) =>
b.v - a.v)
.map(x =>
x.i);
if(cLen >= this._num) {
pos.forEach((p, i) =>
this._updateLine(
this.ranges[i],
0,
p,
this._colors[i],
sorted.indexOf(i) + 1
));
}
else if(cLen === this._num - 1) {
const sVals = [...this._vals].sort((a, b) =>
a - b);
for(let i = 0; i < this._num - 1; i++) {
const [p1, p2] = [
((sVals[i] - this._min) / this._range) * 100,
((sVals[i + 1] - this._min) / this._range) * 100
];
this._updateLine(
this.ranges[i],
p1,
p2 - p1,
this._colors[i],
i + 1
);
}
}
else if(this._num % 2 === 0) {
for(let i = 0; i < this._num; i += 2) {
const [p1, p2] = [pos[i], pos[i + 1]],
[l, r] = p1 < p2 ? [p1, p2] : [p2, p1];
this._updateLine(
this.ranges[i / 2],
l,
r - l,
this._colors[(i / 2) % cLen],
i / 2 + 1
);
}
}
else {
const sVals = this._vals
.map((v, i) =>
({
v, i
}))
.sort((a, b) =>
a.v - b.v);
for(let i = 0; i < sVals.length - 1; i++) {
const [p1, p2] = [pos[sVals[i].i], pos[sVals[i + 1].i]];
this._updateLine(
this.ranges[i],
Math.min(
p1,
p2
),
Math.abs(p2 - p1),
this._colors[i],
i + 1
);
}
}
const adjPos = this._calculateLabelPositions(pos);
this._vals.forEach((val, i) => {
let cIdx;
if(cLen >= this._num) {
cIdx = i;
}
else if(cLen === this._num - 1) {
const sorted = [...this._vals]
.map((v, idx) =>
({
v, idx
}))
.sort((a, b) =>
a.v - b.v);
const myPos = sorted.findIndex(x =>
x.idx === i);
cIdx = Math.min(
myPos,
cLen - 1
);
}
else if(this._num % 2 === 0) {
cIdx = Math.floor(i / 2) % cLen;
}
else {
const sortedVals = [
...new Set([...this._vals].sort((a, b) =>
a - b))
];
cIdx = Math.min(
sortedVals.indexOf(val),
cLen - 1
);
}
const label = this.valueLabels[i];
label.innerText = this._disp(this._roundToStep(val));
this._updateLabel(
label,
adjPos[i],
this._colors[cIdx]
);
});
}
_updateLine(el, lft, wth, col, zdx) {
Object.assign(
el.style,
{
left: lft + "%",
width: wth + "%",
backgroundColor: col,
zIndex: zdx
}
);
}
_updateLabel(el, lft, col) {
const [rW, lW] = [this.wrap.offsetWidth, el.offsetWidth],
pxPos = (lft / 100) * rW,
edge = lW / 2;
let tX = -50;
if(pxPos < edge)
tX = -((pxPos / edge) * 50);
else if(pxPos > rW - edge)
tX = -100 + ((rW - pxPos) / edge) * 50;
Object.assign(
el.style,
{
left: lft + "%",
color: col,
transform: `translateX(${tX}%)`
}
);
}
_roundToStep(v) {
return this._forceRange(
+parseFloat(this._step * Math.round(v / this._step))
.toFixed(
this.dec
)
);
}
_forceRange(v) {
return Math.max(
this._min,
Math.min(
this._max,
v
)
);
}
_onDown(e) {
const rect = this.wrap.getBoundingClientRect(),
pos = (e.clientX - rect.left) / rect.width,
val = this._min + pos * this._range;
this._activeIdx = this._vals.reduce(
(c, _, i) =>
Math.abs(val - this._vals[i]) < Math.abs(val - this._vals[c])
? i
: c,
0
);
[this._startVal, this._startPos] = [this._vals[this._activeIdx], pos];
this._isMoving = false;
this._slideState(true);
this.wrap.setPointerCapture(e.pointerId);
}
_onMove(e) {
const rect = this.wrap.getBoundingClientRect(),
pos = (e.clientX - rect.left) / rect.width,
delta = pos - this._startPos;
let newVal = this._roundToStep(this._startVal + delta * this._range);
if(
this._ranged
&& this._num % 2 === 0
&& this._colors.length < this._num
) {
const pIdx
= this._activeIdx % 2 === 0
? this._activeIdx + 1
: this._activeIdx - 1,
pVal = this._vals[pIdx],
isLower = this._activeIdx % 2 === 0;
if(isLower ? newVal > pVal : newVal < pVal) {
this._targetVals[pIdx] = newVal;
this._targetVals[this._activeIdx] = pVal;
[this._activeIdx, this._startVal, this._startPos] = [
pIdx,
pVal,
pos
];
return this._animate();
}
newVal = isLower
? Math.min(
newVal,
pVal - this._ranged
)
: Math.max(
newVal,
pVal + this._ranged
);
}
if(!this._isMoving && newVal !== this._targetVals[this._activeIdx]) {
this._isMoving = true;
this._dispatch("start");
}
this._targetVals[this._activeIdx] = newVal;
this._animate();
}
_onUp(e) {
this.wrap.releasePointerCapture(e.pointerId);
[this._activeIdx, this._isMoving] = [-1, false];
this._slideState(false);
}
_onKey(evt) {
// console.log("key", evt.key);
/*
handle multiple tabindex to select all cursors
*/
const keySteps = [
["ArrowLeft", "ArrowDown"],
["ArrowRight", "ArrowUp"]
];
const idx = keySteps.findIndex(keys =>
keys.includes(evt.key));
if(idx != -1) {
const dir = idx * 2 - 1;
// NOT RESPECTING MIN RANGE
this._targetVals[0] = this._forceRange(this._targetVals[0] + dir * this._step);
// this._updateUI();
// this._changes();
this._animate();
}
}
_animate() {
if(this._frameLoop)
return;
const step = () => {
let [needsUpdate, valueChanged] = [false, false];
const EASE = 0.2;
const PRECISION = this._step / 10;
this._vals.forEach((val, i) => {
const target = this._targetVals[i];
if(val !== target) {
const delta = target - val;
if(Math.abs(delta) < PRECISION) {
this._vals[i] = target;
valueChanged = true;
}
else {
this._vals[i] = val + delta * EASE;
needsUpdate = true;
}
}
});
if(needsUpdate || valueChanged) {
this._updateUI();
this._dispatch("slide");
if(needsUpdate) {
this._frameLoop = requestAnimationFrame(step);
}
else {
this._frameLoop = null;
this._changes();
this._isMoving = false;
}
}
else
this._frameLoop = null;
};
this._frameLoop = requestAnimationFrame(step);
}
_slideState(stt) {
this.wrap.classList.toggle(
"sliding",
stt
);
}
/**
* @force
* @export
* @method on :
* @param {string} evt :
* @param {Function} handler :
*/
on(evt, handler) {
this._evts.addEventListener(
evt,
handler
);
}
/**
* @force
* @export
* @method off :
* @param {string} evt :
* @param {Function} handler :
*/
off(evt, handler) {
this._evts.removeEventListener(
evt,
handler
);
}
_dispatch(type) {
this._evts.dispatchEvent(
new CustomEvent(
type,
{
detail: this.values
}
)
);
}
_changes() {
// this._aria();
this._dispatch("change");
}
_aria() {
this._sing(
"min",
this._min
);
this._sing(
"max",
this._max
);
// aria-valuenow="20.0"
}
_sing(fld, val) {
this.wrap
.setAttribute(
"aria-value" + fld,
val
);
}
destroy() {
this._frameLoop && cancelAnimationFrame(this._frameLoop);
// off listeners
this.wrap.remove();
}
}
// closure compiler warning fix
var NativeEventTarget = self["EventTarget"];
:root {
--slideHeight: 44px; /* slider height */
--trackHeight: 6px; /* slide track height */
--focusHeight: 10px; /* sliding track height */
--trackColor: #e5e7eb; /* track background color */
--textColor: #6b7280; /* min & max labels color */
--fontSize: 14px; /* labels font size */
--animate: 0.2s ease; /* sliding state animation */
/* PRECALC LABELS OFFSETS */
--textSpc: calc(var(--trackHeight) * 2 / 3);
--textOff: calc((var(--fontSize) + var(--textSpc)) * -1);
--slidSpc: calc(var(--focusHeight) * 2 / 3);
--slidOff: calc((var(--fontSize) + var(--slidSpc)) * -1);
}
/* DARK THEME */
@media (prefers-color-scheme: dark) {
:root {
--trackColor: #363636;
--textColor: #6b7280;
}
}
/* SLIDER WRAP */
.slider {
position: relative;
touch-action: none;
height: var(--slideHeight);
color: var(--textColor);
cursor: pointer;
outline: none;
}
.slider div {
user-select: none;
-webkit-user-select: none;
}
/* TRACK & RANGES */
.slider .track {
position: absolute;
top: 50%;
width: 100%;
height: var(--trackHeight);
transform: translateY(-50%);
background: var(--trackColor);
will-change: height;
transition: height var(--animate);
}
.slider .range {
position: absolute;
height: 100%;
will-change: left, width;
}
/* LABELS */
.slider .vals,
.slider .lbls {
position: absolute;
top: 50%;
width: 100%;
font-size: var(--fontSize);
line-height: var(--fontSize);
transition: transform var(--animate);
}
/* VALUES */
.slider .vals {
transform: translateY(var(--textOff));
}
.slider .val {
position: absolute;
will-change: left, transform;
white-space: nowrap;
}
/* MIN MAX */
.slider .lbls {
display: flex;
align-items: center;
justify-content: space-between;
transform: translateY(var(--textSpc));
}
/* SLIDING STATE */
.slider.sliding .track {
height: var(--focusHeight);
}
.slider.sliding .vals {
transform: translateY(var(--slidOff));
}
.slider.sliding .lbls {
transform: translateY(var(--slidSpc));
}
Just seeking ♪♬
Swipe or click
<div id="seektrack"></div>
body {
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.seektrack {
width: 360px;
max-width: 360px;
max-width: 90%;
}
const seekDatTrack = new SeekTrack(
document.querySelector("#seektrack"),
v => console.log(v));
seekDatTrack.dur = 180;
seekDatTrack.val = 100;
/**
* @force
* @export
* @class SeekTrack : seek that track
*/
class SeekTrack {
/**
* @construct
* @param {HTMLElement} wrap :
* @param {Function} seek :
*/
constructor(wrap, seek) {
[this._track, this._timed, this._texts, this._start, this._total]
= ["track", "timed", "texts", "start", "total"].map(cls => {
const el = document.createElement("div");
el.classList.add(cls);
return el;
});
// Init state
this._min
= this._max
= this._val
= this._aim
= this._startPos
= this._startVal
= 0;
this._down
= this._moved
= this._anm
= false;
this._lst
= this._frm
= this._rect
= null;
this._cbk = seek;
this._wrap = wrap;
this._wrap.className = "seektrack";
this._track.append(this._timed);
this._texts.append(
this._start,
this._total
);
this._wrap.append(
this._texts,
this._track
);
this._wrap["onpointerdown"] = e => {
this._rect = this._wrap.getBoundingClientRect();
this._down = true;
this._moved = false;
this._startPos = e.clientX - this._rect.left;
this._startVal = this._val;
this._wrap.setPointerCapture(e.pointerId);
};
this._wrap["onpointermove"] = e => {
if(!this._down)
return;
this._moved = true;
const pos = (e.clientX - this._rect.left) / this._rect.width;
const delta = pos - this._startPos / this._rect.width;
this._aim = Math.max(
this._min,
Math.min(
this._max,
Math.round(this._startVal + delta * this._max)
)
);
this._animate();
};
this._wrap["onpointerup"] = this._wrap["onpointercancel"] = e => {
if(!this._down)
return;
this._down = false;
this._wrap.releasePointerCapture(e.pointerId);
if(!this._moved) {
const clickPos = (e.clientX - this._rect.left) / this._rect.width;
this._aim = Math.max(
this._min,
Math.min(
this._max,
Math.round(clickPos * this._max)
)
);
this._animate();
}
};
this._updateUI();
}
_fmt(s) {
return (s < 3600 ? [60, 1] : [3600, 60, 1])
.map(x =>
`0${~~(s / x) % 60}`.slice(-2))
.join(":");
}
_animate() {
if(this._frm)
cancelAnimationFrame(this._frm);
this._anm = true;
const step = () => {
const delta = this._aim - this._val;
if(Math.abs(delta) < 0.2) {
this._val = this._aim;
this._updateUI();
this._frm = null;
this._anm = false;
if(this.val !== this._lst) {
this._lst = this.val;
this._cbk(this.val);
}
}
else {
this._val += delta * 0.2;
this._updateUI();
this._frame(step);
}
};
this._frame(step);
}
_frame(cb) {
this._frm = requestAnimationFrame(cb);
}
_updateUI() {
this._timed.style.width = (this._val / this._max) * 100 + "%";
this._start.textContent = this._fmt(this._val);
this._total.textContent = this._fmt(this._max - this._val);
}
/**
* @force
* @export
* @getter
* @type {number} val : current time
*/
get val() {
return Math.round(this._val);
}
/**
* @setter
*/
set val(v) {
if(this._down || this._anm)
return;
this._val = this._aim = Math.max(
this._min,
Math.min(
this._max,
+v
)
);
this._updateUI();
}
/**
* @force
* @export
* @getter
* @type {number} dur : total value
*/
get dur() {
return this._max;
}
/**
* @setter
*/
set dur(v) {
this._max = v;
this._updateUI();
}
destroy() {
if(this._frm)
cancelAnimationFrame(this._frm);
this._wrap.remove();
}
}
.seektrack {
position: relative;
width: 100%;
height: 100%;
font-size: 13px;
color: #6b7280;
cursor: pointer;
touch-action: none;
user-select: none;
}
.seektrack .track {
position: relative;
width: 100%;
height: 6px;
background: #c8c8c8;
}
.seektrack .timed {
width: 0%;
height: 100%;
will-change: width;
background: #4A90E2;
}
.seektrack .texts {
width: 100%;
display: flex;
justify-content: space-between;
pointer-events: none;
padding-bottom: .3rem;
}
2K JS + 0.4K CSS
/**
* Nico Pr — https://nicopr.fr
* SEEKTRACK STANDALONE RELEASE v0.1.0
* 27/04/2025 01:18:32
*/
(function(){
function c(a){a.o&&cancelAnimationFrame(a.o);a.s=!0;const e=()=>{const b=a.l-a.g;.2>Math.abs(b)?(a.g=a.l,d(a),a.o=null,a.s=!1,a.val!==a.H&&(a.H=a.val,a.J(a.val))):(a.g+=.2*b,d(a),a.o=requestAnimationFrame(e))};a.o=requestAnimationFrame(e)}function d(a){a.D.style.width=a.g/a.h*100+"%";a.I.textContent=f(a.g);a.F.textContent=f(a.h-a.g)}function f(a){return(3600>a?[60,1]:[3600,60,1]).map(e=>`0${~~(a/e)%60}`.slice(-2)).join(":")}
class g{constructor(a,e){[this.G,this.D,this.C,this.I,this.F]=["track","timed","texts","start","total"].map(b=>{const l=document.createElement("div");l.classList.add(b);return l});this.u=this.h=this.g=this.l=this.A=this.B=0;this.m=this.v=this.s=!1;this.H=this.o=this.j=null;this.J=e;this.i=a;this.i.className="seektrack";this.G.append(this.D);this.C.append(this.I,this.F);this.i.append(this.C,this.G);this.i.onpointerdown=b=>{this.j=this.i.getBoundingClientRect();this.m=!0;this.v=!1;this.A=b.clientX-
this.j.left;this.B=this.g;this.i.setPointerCapture(b.pointerId)};this.i.onpointermove=b=>{this.m&&(this.v=!0,this.l=Math.max(this.u,Math.min(this.h,Math.round(this.B+((b.clientX-this.j.left)/this.j.width-this.A/this.j.width)*this.h))),c(this))};this.i.onpointerup=this.i.onpointercancel=b=>{this.m&&(this.m=!1,this.i.releasePointerCapture(b.pointerId),this.v||(this.l=Math.max(this.u,Math.min(this.h,Math.round((b.clientX-this.j.left)/this.j.width*this.h))),c(this)))};d(this)}get val(){return Math.round(this.g)}set val(a){this.m||
this.s||(this.g=this.l=Math.max(this.u,Math.min(this.h,+a)),d(this))}get dur(){return this.h}set dur(a){this.h=a;d(this)}}var h=g,k=["SeekTrack"],m=this||self;k[0]in m||"undefined"==typeof m.execScript||m.execScript("var "+k[0]);for(var n;k.length&&(n=k.shift());)k.length||void 0===h?m[n]&&m[n]!==Object.prototype[n]?m=m[n]:m=m[n]={}:m[n]=h;}).call(this);
.seektrack{position:relative;width:100%;height:100%;font-size:13px;color:#6b7280;cursor:pointer;touch-action:none;user-select:none}.seektrack .track{position:relative;width:100%;height:6px;background:#c8c8c8}.seektrack .timed{width:0%;height:100%;will-change:width;background:#4a90e2}.seektrack .texts{width:100%;display:flex;justify-content:space-between;pointer-events:none;padding-bottom:.3rem}