I am new to front end development and thought to recreate the GUI of Fruity Balance just for practice. I would love to hear any advice on best practices. I am also wondering if any improvements could be made regarding scalability and extensibility (e.g. more knobs to be added in the future)?
clamp = (x, min, max) => { return Math.min(Math.max(x, min), max) } class Knob { constructor(knobElement, min, max) { this.knobElement = knobElement; this.min = min; this.max = max; this.mouseDownY = 0; this.currentRotation = 0; this.mousePressed = false; this.knobElement.addEventListener("mousedown", (e) => { this.mousePressed = true; this.mouseDownY = e.clientY; this.currentRotation = parseInt(getComputedStyle(this.knobElement).getPropertyValue("--rotation")); // Change cursor document.body.style.cursor = "ns-resize"; }) document.addEventListener("mouseup", () => { this.mousePressed = false; // Revert cursor document.body.style.cursor = ""; }) document.addEventListener("mousemove", (e) => { if (!this.mousePressed) return; let newRotation = clamp(this.mouseDownY - e.clientY + this.currentRotation, this.min, this.max); this.knobElement.style.setProperty("--rotation", newRotation.toString() + "deg"); }) } } const knobElement1 = document.getElementsByClassName("knob")[0]; const knobElement2 = document.getElementsByClassName("knob")[1]; const knob1 = new Knob(knobElement1, -180, 180); const knob2 = new Knob(knobElement2, 0, 360); body { margin: 0; width: 100vw; height: 100vh; display: flex; justify-content: center; align-items: center; } .container { width: 400px; height: 260px; display: flex; justify-content: space-evenly; align-items: center; background: radial-gradient(circle at top, #767676 0%, #252525 100%); position: relative; } .meter { padding: 5px; width: 65px; height: 200px; border: 1.5px solid black; background: #767676; display: flex; justify-content: space-between; align-items: end; } .rec{ width: 42%; height: 60%; background: #daff9a; } .knob { --rotation: 80deg; width: 90px; height: 90px; display: grid; position: relative; cursor: ns-resize; } .knob-dot-purple { width: 5%; height: 5%; border-radius: 100%; background: #ac71d4; position: absolute; left: 50%; top: -5%; transform: translate(-50%, -50%); } .knob-dot-blue { width: 5%; height: 5%; border-radius: 100%; background: #7fe2f1; position: absolute; left: 105%; top: 50%; transform: translate(-50%, -50%); } .knob-light-purple { /* --rotation: 30%; */ width: 95%; height: 95%; border-radius: 100%; background: /* Forwards */ conic-gradient(from 0deg, #ac71d4 var(--rotation), transparent var(--rotation)), /* Backwards */ conic-gradient(from var(--rotation), #ac71d4 calc(-1*var(--rotation)), transparent calc(-1*var(--rotation))); position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); } .knob-light-blue { /* --rotation: 80%; */ width: 95%; height: 95%; border-radius: 100%; background: conic-gradient(from 180deg, #7fe2f1 var(--rotation), transparent var(--rotation)); position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); } .knob-layer-3 { width: 100%; height: 100%; border-radius: 100%; background: #626262; position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); box-shadow: inset 0em 0em 0.1em 0.1em rgb(82, 82, 82); } .knob-layer-2 { width: 85%; height: 85%; border-radius: 100%; background: linear-gradient(to bottom, #DDDDDD -10%, #444444 50%, #444444 50%, #4f4f4f 100%); position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); box-shadow: 0em 0.3em 0.1em 0.0001em rgba(0, 0, 0, 0.5), 0em 0.05em 0.1em 0.15em rgba(0, 0, 0, 0.1); } .knob-layer-1 { width: 65%; height: 65%; border-radius: 100%; background: linear-gradient(to bottom, #b9b9b9 0%, #828282 100%); position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); box-shadow: 0em 0.05em 0.1em 0.15em rgb(59, 59, 59), inset 0em 0.2em 0.1em -0.1em #d5d5d5; } .text { font-family: Optima; color: #DDDDDD; position: absolute; transform: translate(-50%, -50%); user-select: none; } .text.balance { font-size: 1.5em; left: 21%; top: 18%; } .text.volume { font-size: 1.5em; left: 79%; top: 18%; } .text.pan { font-size: 1em; left: 21%; top: 82%; } .text.db { font-size: 1em; left: 79%; top: 82%; } <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Fruity Balance</title> <link rel="stylesheet" href="css/style.css"> </head> <body> <div class="container"> <div class="knob"> <div class="knob-dot-purple"></div> <div class="knob-layer-3"></div> <div class="knob-light-purple"></div> <div class="knob-layer-2"></div> <div class="knob-layer-1"></div> </div> <div class="meter"> <div class="rec"></div> <div class="rec"></div> </div> <div class="knob"> <div class="knob-dot-blue"></div> <div class="knob-layer-3"></div> <div class="knob-light-blue"></div> <div class="knob-layer-2"></div> <div class="knob-layer-1"></div> </div> <div class="text balance">Balance</div> <div class="text volume">Volume</div> <div class="text pan">Centered</div> <div class="text db">0.0dB 1.00</div> </div> <script src="js/script.js"></script> </body> </html>