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>