CODE of ANIMATION to be STUDIED (Create by CLAUDE): SISTEM ENCODER MOTOR - INTERAKTIF (PURE JS) aka Ilustrasi visual sederhana/diagram blok atau skema sensor pada motor listrik untuk menunjukkan bagaimana pengukuran kecepatan sudut dilakukan dalam sistem servo/stepper
Sistem Encoder Motor
Poros Motor
Disk Encoder (berlubang)
Proses Perhitungan
Pulsa ON/OFF
0
→
Δθ (rad)
0.000
÷
Δt (detik)
0.000
=
ω (rad/s)
0
Cara Kerja:
- LED memancarkan cahaya ke sensor melalui disk.
- Saat lubang lewat, cahaya terdeteksi → Pulsa ON.
- Saat bagian padat lewat, cahaya terblokir → Pulsa OFF.
- Setiap pulsa = perubahan sudut (Δθ).
- Kecepatan sudut (ω) = Δθ / Δt.
SOURCE-CODE
<!--===============================-->
<!--SISTEM ENCODER MOTOR - INTERAKTIF (PURE JS)-->
<!--===============================-->
<style>
.encoder-root {
width: 100%;
min-height: 100vh;
box-sizing: border-box;
background: linear-gradient(to bottom right, #0f172a, #1e293b);
padding: 40px 16px;
display: flex;
flex-direction: column;
align-items: center;
font-family: Arial, Helvetica, sans-serif;
color: #e5e7eb;
}
.encoder-title {
font-size: 32px;
font-weight: bold;
margin-bottom: 32px;
text-align: center;
}
.encoder-label-blue {
color: #60a5fa;
font-weight: 600;
margin-bottom: 8px;
}
.encoder-label-green {
color: #34d399;
font-weight: 600;
margin-bottom: 12px;
}
.encoder-shaft {
width: 8px;
height: 70px;
background: linear-gradient(#4b5563, #9ca3af);
margin: 0 auto;
}
.encoder-disk-wrapper {
text-align: center;
margin-top: 20px;
}
.encoder-disk {
width: 260px;
height: 260px;
border-radius: 50%;
background: linear-gradient(to bottom right, #4b5563, #6b7280);
border: 5px solid #6b7280;
position: relative;
box-shadow: 0 0 30px rgba(0,0,0,0.4);
margin: 0 auto;
transition: transform 0.016s linear;
}
.encoder-center {
width: 35px;
height: 35px;
background: #1f2937;
border-radius: 50%;
border: 2px solid #4b5563;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.encoder-hole {
width: 22px;
height: 22px;
background: #020617;
border-radius: 50%;
border: 1px solid #111827;
position: absolute;
transform: translate(-50%, -50%);
}
.encoder-row-led-sensor {
display: flex;
align-items: center;
justify-content: center;
gap: 40px;
margin-top: 24px;
}
.encoder-icon-label {
text-align: center;
}
.encoder-icon {
font-size: 28px;
margin-bottom: 4px;
transition: transform 0.2s;
}
.encoder-led-active { color:#facc15; animation:encoder-pulse 0.8s infinite; }
.encoder-led-inactive { color:#f59e0b; }
.encoder-sensor-active { color:#22d3ee; }
.encoder-sensor-inactive { color:#0e7490; }
.encoder-beam {
width: 120px;
height: 4px;
background: linear-gradient(to right,#facc15,#fb923c);
opacity: 0.6;
}
.encoder-button {
margin-top: 30px;
padding: 12px 28px;
border-radius: 10px;
border:none;
font-weight:bold;
font-size:18px;
color:#ffffff;
cursor:pointer;
box-shadow:0 5px 15px rgba(0,0,0,0.4);
transition: transform 0.1s, background 0.15s;
}
.encoder-button:active { transform:scale(0.96); }
.encoder-button-start { background:#16a34a; }
.encoder-button-start:hover { background:#15803d; }
.encoder-button-stop { background:#dc2626; }
.encoder-button-stop:hover { background:#b91c1c; }
.encoder-panel {
margin-top:40px;
background:#1e293b;
border:1px solid #334155;
border-radius:12px;
padding:25px;
width:100%;
max-width:550px;
text-align:center;
}
.encoder-panel-title {
font-size:22px;
font-weight:bold;
margin-bottom:20px;
}
.encoder-flow-row {
display:flex;
justify-content:center;
align-items:center;
gap:20px;
flex-wrap:wrap;
}
.encoder-flow-label { font-size:13px; color:#94a3b8; }
.encoder-flow-value {
font-family:"Courier New", monospace;
font-weight:bold;
font-size:26px;
}
.encoder-flow-arrow { font-size:26px; color:#64748b; }
.encoder-value-pulse { color:#34d399; }
.encoder-value-theta { color:#60a5fa; }
.encoder-value-dt { color:#c084fc; }
.encoder-value-omega { color:#facc15; font-size:32px; }
.encoder-infobox {
margin-top:25px;
background:rgba(30,64,175,0.4);
border-radius:12px;
padding:20px;
max-width:550px;
color:#bfdbfe;
font-size:14px;
}
.encoder-infobox ul {
margin-top:10px;
padding-left:20px;
line-height:1.6;
}
@keyframes encoder-pulse {
0% { transform:scale(1); opacity:1; }
50% { transform:scale(1.15); opacity:0.7; }
100% { transform:scale(1); opacity:1; }
}
</style>
<div class="encoder-root" id="encoder-app">
<h1 class="encoder-title">Sistem Encoder Motor</h1>
<!--Poros Motor-->
<div style="margin-bottom:20px; text-align:center;">
<div class="encoder-label-blue">Poros Motor</div>
<div class="encoder-shaft"></div>
</div>
<!--Disk Encoder-->
<div class="encoder-disk-wrapper">
<div class="encoder-label-green">Disk Encoder (berlubang)</div>
<div class="encoder-disk" id="encoder-disk">
<div class="encoder-center"></div>
<!--Lubang akan diisi via JavaScript-->
</div>
</div>
<!--LED & Sensor-->
<div class="encoder-row-led-sensor">
<div class="encoder-icon-label">
<div class="encoder-icon encoder-led-inactive" id="encoder-led">⚡</div>
<div style="color:#facc15; font-weight:600;">LED</div>
</div>
<div class="encoder-beam"></div>
<div class="encoder-icon-label">
<div class="encoder-icon encoder-sensor-inactive" id="encoder-sensor">👁️</div>
<div style="color:#22d3ee; font-weight:600;">Sensor Photo/IR</div>
</div>
</div>
<!--Button-->
<button class="encoder-button encoder-button-start" id="encoder-button">
START Motor
</button>
<!--Panel Proses Perhitungan-->
<div class="encoder-panel">
<div class="encoder-panel-title">Proses Perhitungan</div>
<div class="encoder-flow-row">
<div>
<div class="encoder-flow-label">Pulsa ON/OFF</div>
<div class="encoder-flow-value encoder-value-pulse" id="encoder-pulse">0</div>
</div>
<div class="encoder-flow-arrow">→</div>
<div>
<div class="encoder-flow-label">Δθ (rad)</div>
<div class="encoder-flow-value encoder-value-theta" id="encoder-dtheta">0.000</div>
</div>
<div class="encoder-flow-arrow">÷</div>
<div>
<div class="encoder-flow-label">Δt (detik)</div>
<div class="encoder-flow-value encoder-value-dt" id="encoder-dt">0.000</div>
</div>
<div class="encoder-flow-arrow">=</div>
<div>
<div class="encoder-flow-label">ω (rad/s)</div>
<div class="encoder-flow-value encoder-value-omega" id="encoder-omega">0</div>
</div>
</div>
</div>
<!--Info Box-->
<div class="encoder-infobox">
<strong>Cara Kerja:</strong>
<ul>
<li>LED memancarkan cahaya ke sensor melalui disk.</li>
<li>Saat lubang lewat, cahaya terdeteksi → Pulsa ON.</li>
<li>Saat bagian padat lewat, cahaya terblokir → Pulsa OFF.</li>
<li>Setiap pulsa = perubahan sudut (Δθ).</li>
<li>Kecepatan sudut (ω) = Δθ / Δt.</li>
</ul>
</div>
</div>
<script>
(function() {
var holes = 12;
var rotation = 0;
var isRunning = false;
var pulseCount = 0;
var lastPulseTime = null;
var intervalId = null;
var diskEl = document.getElementById('encoder-disk');
var buttonEl = document.getElementById('encoder-button');
var pulseEl = document.getElementById('encoder-pulse');
var dthetaEl = document.getElementById('encoder-dtheta');
var dtEl = document.getElementById('encoder-dt');
var omegaEl = document.getElementById('encoder-omega');
var ledEl = document.getElementById('encoder-led');
var sensorEl = document.getElementById('encoder-sensor');
(function createHoles() {
var radius = 100;
for (var i=0; i<holes; i++) {
var angle = (i * 360) / holes;
var rad = (angle - 90) * Math.PI/180;
var x = 130 + radius * Math.cos(rad);
var y = 130 + radius * Math.sin(rad);
var hole = document.createElement('div');
hole.className = 'encoder-hole';
hole.style.left = x + 'px';
hole.style.top = y + 'px';
diskEl.appendChild(hole);
}
})();
var deltaTheta = (2 * Math.PI) / holes;
dthetaEl.textContent = deltaTheta.toFixed(3);
function setRunningState(running) {
isRunning = running;
if (running) {
buttonEl.textContent = 'STOP Motor';
buttonEl.classList.remove('encoder-button-start');
buttonEl.classList.add('encoder-button-stop');
ledEl.classList.remove('encoder-led-inactive');
ledEl.classList.add('encoder-led-active');
sensorEl.classList.remove('encoder-sensor-inactive');
sensorEl.classList.add('encoder-sensor-active');
pulseCount = 0;
pulseEl.textContent = '0';
dtEl.textContent = '0.000';
omegaEl.textContent = '0';
lastPulseTime = null;
rotation = 0;
diskEl.style.transform = 'rotate(0deg)';
startLoop();
} else {
buttonEl.textContent = 'START Motor';
buttonEl.classList.remove('encoder-button-stop');
buttonEl.classList.add('encoder-button-start');
ledEl.classList.remove('encoder-led-active');
ledEl.classList.add('encoder-led-inactive');
sensorEl.classList.remove('encoder-sensor-active');
sensorEl.classList.add('encoder-sensor-inactive');
stopLoop();
}
}
function startLoop() {
if (intervalId !== null) return;
intervalId = setInterval(function() {
var prevRotation = rotation;
rotation = (rotation + 3) % 360;
diskEl.style.transform = 'rotate(' + rotation + 'deg)';
var segSize = 360 / holes;
var prevIndex = Math.floor(prevRotation / segSize);
var newIndex = Math.floor(rotation / segSize);
if (prevIndex !== newIndex) {
var now = (window.performance && performance.now)
? performance.now()
: Date.now();
if (lastPulseTime !== null) {
var deltaT = (now - lastPulseTime) / 1000;
var omega = deltaTheta / deltaT;
dtEl.textContent = deltaT.toFixed(3);
omegaEl.textContent = omega.toFixed(2);
}
lastPulseTime = now;
pulseCount += 1;
pulseEl.textContent = String(pulseCount);
}
}, 16);
}
function stopLoop() {
if (intervalId !== null) {
clearInterval(intervalId);
intervalId = null;
}
}
buttonEl.addEventListener('click', function() {
setRunningState(!isRunning);
});
})();
</script>
Comments