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); }