445 lines
12 KiB
JavaScript
445 lines
12 KiB
JavaScript
|
const NS = "http://www.w3.org/2000/svg";
|
||
|
|
||
|
/**
|
||
|
* Svg Class
|
||
|
*/
|
||
|
class Svg {
|
||
|
constructor(w, h) {
|
||
|
this.width = w;
|
||
|
this.height = h;
|
||
|
|
||
|
this.node = document.createElementNS(NS, "svg");
|
||
|
|
||
|
this.render();
|
||
|
}
|
||
|
|
||
|
render() {
|
||
|
this.node.setAttributeNS(null, "width", this.width);
|
||
|
this.node.setAttributeNS(null, "height", this.height);
|
||
|
}
|
||
|
|
||
|
getNode() {
|
||
|
return this.node;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Circle Class
|
||
|
*/
|
||
|
class Circle {
|
||
|
constructor(r, s, min = 0, max = 360, bg = false, zone = false) {
|
||
|
this.radius = r;
|
||
|
this.stroke = s;
|
||
|
this.minAngle = min;
|
||
|
this.maxAngle = max;
|
||
|
this.px = this.radius;
|
||
|
this.py = this.radius;
|
||
|
this.bg = bg;
|
||
|
this.zone = zone;
|
||
|
this.offset = 0;
|
||
|
this.progress = 0;
|
||
|
|
||
|
this.node = document.createElementNS(NS, "circle");
|
||
|
|
||
|
this.render();
|
||
|
}
|
||
|
|
||
|
render() {
|
||
|
this.setArc();
|
||
|
|
||
|
this.node.setAttributeNS(null, "r", this.radius - (this.stroke / 2));
|
||
|
this.node.setAttributeNS(null, "cx", this.px);
|
||
|
this.node.setAttributeNS(null, "cy", this.py);
|
||
|
this.node.setAttributeNS(null, "stroke-width", this.stroke);
|
||
|
}
|
||
|
|
||
|
setArc() {
|
||
|
this.arc = 2 * Math.PI * (this.radius - (this.stroke / 2));
|
||
|
this.gap = this.arc - this.arc * ((this.maxAngle - this.minAngle) / 360);
|
||
|
|
||
|
this.node.setAttributeNS(
|
||
|
null,
|
||
|
"stroke-dasharray",
|
||
|
this.bg ? `${this.arc - this.gap}, ${this.gap}` : this.arc
|
||
|
);
|
||
|
|
||
|
this.setProgress();
|
||
|
}
|
||
|
|
||
|
getNode() {
|
||
|
return this.node;
|
||
|
}
|
||
|
|
||
|
setProgress(progress) {
|
||
|
if (progress === undefined) {
|
||
|
progress = this.progress;
|
||
|
}
|
||
|
|
||
|
let offset = this.arc - this.arc * progress;
|
||
|
|
||
|
if (!this.bg) {
|
||
|
offset = this.arc - (this.arc - this.gap) * progress;
|
||
|
}
|
||
|
|
||
|
this.node.setAttributeNS(null, "data-progress", progress);
|
||
|
this.node.setAttributeNS(null, "stroke-dashoffset", offset);
|
||
|
|
||
|
this.progress = progress;
|
||
|
this.offset = offset;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
class RProgress {
|
||
|
start(to, from, duration) {
|
||
|
this.frame = false;
|
||
|
|
||
|
if (from === undefined) {
|
||
|
from = this.config.progress;
|
||
|
}
|
||
|
|
||
|
if (duration === undefined) {
|
||
|
duration = 5000;
|
||
|
}
|
||
|
|
||
|
let st = Date.now();
|
||
|
|
||
|
// Scroll function
|
||
|
const animate = () => {
|
||
|
let progress,
|
||
|
now = Date.now(),
|
||
|
ct = now - st;
|
||
|
|
||
|
// Cancel after allotted interval
|
||
|
if (ct > duration) {
|
||
|
cancelAnimationFrame(this.frame);
|
||
|
this.setProgress(to, false);
|
||
|
this.config.onChange.call(this, 100, ct, duration);
|
||
|
this.config.onComplete.call(this, to);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
progress = easings[this.config.easing](ct, from, to - from, duration);
|
||
|
|
||
|
this.setProgress(progress, false);
|
||
|
|
||
|
this.config.onChange.call(this, progress, ct, duration);
|
||
|
|
||
|
// requestAnimationFrame
|
||
|
this.frame = requestAnimationFrame(animate);
|
||
|
};
|
||
|
|
||
|
this.config.onStart.call(this);
|
||
|
animate();
|
||
|
}
|
||
|
|
||
|
pause() {
|
||
|
cancelAnimationFrame(this.frame);
|
||
|
}
|
||
|
|
||
|
stop() {
|
||
|
cancelAnimationFrame(this.frame);
|
||
|
this.remove();
|
||
|
}
|
||
|
|
||
|
render(element) {
|
||
|
if (!this.rendered) {
|
||
|
element =
|
||
|
typeof element === "string" ? document.querySelector(element) : element;
|
||
|
|
||
|
element.appendChild(this.container);
|
||
|
|
||
|
this.setPosition();
|
||
|
|
||
|
this.rendered = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
remove() {
|
||
|
const parent = this.container.parentNode;
|
||
|
if (this.rendered && parent) {
|
||
|
parent.removeChild(this.container);
|
||
|
|
||
|
this.rendered = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
show() {
|
||
|
this.container.classList.remove("done");
|
||
|
}
|
||
|
|
||
|
hide() {
|
||
|
this.container.classList.add("done");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* RadialProgress Class
|
||
|
*/
|
||
|
class RadialProgress extends RProgress {
|
||
|
constructor(config) {
|
||
|
super();
|
||
|
|
||
|
|
||
|
const defaultConfig = {
|
||
|
r: 50,
|
||
|
s: 10,
|
||
|
x: 0,
|
||
|
y: 0,
|
||
|
cap: "butt",
|
||
|
padding: 0,
|
||
|
progress: 0,
|
||
|
minAngle: 0,
|
||
|
maxAngle: 360,
|
||
|
rotation: 0,
|
||
|
color: "rgba(255, 255, 255, 1.0)",
|
||
|
zoneColor: "rgba(51, 105, 30, 1)",
|
||
|
bgColor: "rgba(0, 0, 0, 0.4)",
|
||
|
easing: "easeLinear",
|
||
|
onStart: () => {},
|
||
|
onChange: () => {},
|
||
|
onComplete: () => {},
|
||
|
onTimeout: () => {},
|
||
|
};
|
||
|
|
||
|
this.config = Object.assign({}, defaultConfig, config);
|
||
|
|
||
|
this.init();
|
||
|
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
init() {
|
||
|
const arc = this.config.maxAngle - this.config.minAngle;
|
||
|
|
||
|
this.config.padding *= 2;
|
||
|
|
||
|
this.config.w = (this.config.r * 2) + this.config.padding;
|
||
|
this.config.h = (this.config.r * 2) + this.config.padding;
|
||
|
|
||
|
this.svg = new Svg(this.config.w, this.config.h);
|
||
|
|
||
|
this.dials = {
|
||
|
bg: new Circle(
|
||
|
this.config.r + (this.config.padding / 2),
|
||
|
this.config.s + this.config.padding,
|
||
|
this.config.minAngle,
|
||
|
this.config.maxAngle,
|
||
|
true
|
||
|
),
|
||
|
fg: new Circle(
|
||
|
this.config.r,
|
||
|
this.config.s,
|
||
|
this.config.minAngle,
|
||
|
this.config.maxAngle
|
||
|
)
|
||
|
};
|
||
|
|
||
|
if ( this.config.zone ) {
|
||
|
this.zoneArc = (this.config.zone / 100) * arc;
|
||
|
this.zonePos = getRandomInt(0, arc - this.zoneArc);
|
||
|
|
||
|
const startPos = (this.zonePos / arc) * 100;
|
||
|
this.zoneMin = startPos;
|
||
|
this.zoneMax = startPos + this.config.zone;
|
||
|
|
||
|
this.dials.zone = new Circle(
|
||
|
this.config.r,
|
||
|
this.config.s,
|
||
|
0,
|
||
|
this.zoneArc,
|
||
|
true
|
||
|
)
|
||
|
}
|
||
|
|
||
|
this.svg.getNode().appendChild(this.dials.bg.getNode());
|
||
|
this.svg.getNode().appendChild(this.dials.fg.getNode());
|
||
|
|
||
|
if ( this.dials.zone ) {
|
||
|
this.svg.getNode().appendChild(this.dials.zone.getNode());
|
||
|
this.dials.zone.getNode().setAttributeNS(null, "stroke", this.config.zoneColor);
|
||
|
|
||
|
this.dials.zone.getNode().style.transform = `rotate(${
|
||
|
this.zonePos
|
||
|
}deg)`;
|
||
|
|
||
|
this.dials.zone.getNode().style.transformOrigin = `50% 50% 0`;
|
||
|
}
|
||
|
|
||
|
|
||
|
const fgNode = this.dials.fg.getNode();
|
||
|
fgNode.setAttributeNS(null, "cx", (this.config.w / 2));
|
||
|
fgNode.setAttributeNS(null, "cy", (this.config.h / 2));
|
||
|
fgNode.setAttributeNS(null, "stroke-linecap", this.config.cap);
|
||
|
fgNode.setAttributeNS(null, "stroke", this.config.color);
|
||
|
|
||
|
this.dials.bg.getNode().setAttributeNS(null, "stroke", this.config.bgColor);
|
||
|
|
||
|
|
||
|
const container = document.createElement("div");
|
||
|
container.classList.add("ui-dial");
|
||
|
|
||
|
const indicator = document.createElement("div");
|
||
|
indicator.classList.add("ui-indicator");
|
||
|
|
||
|
const label = document.createElement("div");
|
||
|
label.classList.add("ui-label");
|
||
|
|
||
|
this.setRotation(this.config.rotation);
|
||
|
|
||
|
container.appendChild(this.svg.getNode());
|
||
|
container.appendChild(indicator);
|
||
|
container.appendChild(label);
|
||
|
|
||
|
this.container = container;
|
||
|
this.indicator = indicator;
|
||
|
this.label = label;
|
||
|
|
||
|
this.setPosition(this.config.x, this.config.y);
|
||
|
this.setProgress();
|
||
|
}
|
||
|
|
||
|
setPosition(x, y) {
|
||
|
this.config.x = x;
|
||
|
this.config.y = y;
|
||
|
|
||
|
const size = (this.config.r * 2);
|
||
|
|
||
|
this.container.style.width = `${(this.config.r * 2) + this.config.padding}px`;
|
||
|
this.container.style.height = `${(this.config.r * 2) + this.config.padding}px`;
|
||
|
this.container.style.left = `${
|
||
|
(this.config.x * window.innerWidth) - (size / 2) - (this.config.padding / 2)
|
||
|
}px`;
|
||
|
this.container.style.top = `${
|
||
|
(this.config.y * window.innerHeight) - (size / 2) - (this.config.padding / 2)
|
||
|
}px`;
|
||
|
}
|
||
|
|
||
|
setEndAngle(angle) {
|
||
|
this.config.maxAngle = angle;
|
||
|
|
||
|
this.dials.bg.maxAngle = angle;
|
||
|
this.dials.bg.setArc();
|
||
|
|
||
|
this.dials.fg.maxAngle = angle;
|
||
|
this.dials.fg.setArc();
|
||
|
|
||
|
this.dials.bg.setProgress();
|
||
|
this.dials.fg.setProgress();
|
||
|
}
|
||
|
|
||
|
setRotation(angle) {
|
||
|
this.config.rotation = angle;
|
||
|
this.svg.getNode().style.transform = `rotate(${
|
||
|
this.config.rotation - 90
|
||
|
}deg)`;
|
||
|
}
|
||
|
|
||
|
setProgress(progress, e) {
|
||
|
if (progress === undefined) {
|
||
|
progress = this.config.progress;
|
||
|
}
|
||
|
|
||
|
this.dials.fg.setProgress(progress / 100);
|
||
|
|
||
|
this.progress = progress;
|
||
|
|
||
|
if (e === undefined) {
|
||
|
this.config.onChange.call(this, progress);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
class LinearProgress extends RProgress {
|
||
|
constructor(config) {
|
||
|
super();
|
||
|
|
||
|
const defaultConfig = {
|
||
|
w: 300,
|
||
|
h: 10,
|
||
|
s: 10,
|
||
|
x: 0,
|
||
|
y: 0,
|
||
|
cap: "butt",
|
||
|
padding: 0,
|
||
|
progress: 0,
|
||
|
color: "rgba(255, 255, 255, 1.0)",
|
||
|
bgColor: "rgba(0, 0, 0, 0.4)",
|
||
|
easing: "easeLinear",
|
||
|
onStart: () => {},
|
||
|
onChange: () => {},
|
||
|
onComplete: () => {}
|
||
|
};
|
||
|
|
||
|
this.config = Object.assign({}, defaultConfig, config);
|
||
|
this.progress = 0;
|
||
|
|
||
|
this.init();
|
||
|
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
init() {
|
||
|
this.container = document.createElement("div");
|
||
|
this.container.classList.add("linear-progress");
|
||
|
|
||
|
this.bg = document.createElement("div");
|
||
|
this.fg = document.createElement("div");
|
||
|
|
||
|
this.bg.classList.add("linear-progress-bg");
|
||
|
this.fg.classList.add("linear-progress-fg");
|
||
|
|
||
|
this.label = document.createElement("div");
|
||
|
this.label.classList.add("linear-progress-label");
|
||
|
|
||
|
this.container.appendChild(this.bg);
|
||
|
this.container.appendChild(this.fg);
|
||
|
this.container.appendChild(this.label);
|
||
|
}
|
||
|
|
||
|
setPosition() {
|
||
|
let contCss = `width:${this.config.w}px;height:${this.config.h}px;left:${this.config.x * window.innerWidth - this.config.w / 2}px;top:${this.config.y * window.innerHeight - this.config.h / 2}px;`;
|
||
|
let bgCss = `background-color:${this.config.bgColor};padding:${this.config.padding}px;left:${-this.config.padding}px;top:${-this.config.padding}px;`;
|
||
|
|
||
|
this.container.style.cssText = contCss;
|
||
|
|
||
|
this.fg.style.backgroundColor = this.config.color;
|
||
|
|
||
|
if ( this.config.cap == 'round' ) {
|
||
|
this.fg.style.borderRadius = `${this.config.h / 2}px`
|
||
|
this.fg.style.transform = "scale(1, 1)";
|
||
|
|
||
|
bgCss += `border-radius:${this.config.h / 2 + this.config.padding}px;`;
|
||
|
} else {
|
||
|
this.fg.style.width = "100%";
|
||
|
}
|
||
|
|
||
|
|
||
|
this.bg.style.cssText = bgCss;
|
||
|
}
|
||
|
|
||
|
setProgress(progress, e) {
|
||
|
if (progress === undefined) {
|
||
|
progress = this.config.progress;
|
||
|
}
|
||
|
|
||
|
const p = (progress / 100);
|
||
|
|
||
|
if ( this.config.cap == 'round' ) {
|
||
|
this.fg.style.width = `${progress}%`;
|
||
|
} else {
|
||
|
this.fg.style.transform = `scale(${p}, 1)`;
|
||
|
}
|
||
|
|
||
|
this.progress = progress;
|
||
|
|
||
|
if (e === undefined) {
|
||
|
this.config.onChange.call(this, progress);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function getRandomInt(min, max) {
|
||
|
min = Math.ceil(min);
|
||
|
max = Math.floor(max);
|
||
|
return Math.floor(Math.random() * (max - min) + min);
|
||
|
}
|