Project HTML CSS JavaScript: Speed Test Internet
Membuat speed test internet menggunakan HTML, CSS, dan JavaScript adalah proyek menarik yang menggabungkan konsep frontend dengan pengukuran performa jaringan. Dalam panduan ini, kita akan membuat alat pengukur kecepatan internet dengan:
✅ Tampilan modern dan responsif
✅ Pengukuran kecepatan download/upload
✅ Animasi visual saat pengukuran
✅ Histori hasil test
✅ Kompatibilitas multi-browser
Struktur Proyek
/internet-speed-test │── index.html │── style.css │── script.js └── assets/ ├── images/ └── fonts/
1. HTML (index.html)
<!DOCTYPE html> <html lang="id"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Internet Speed Test - Azroi</title> <link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> </head> <body> <div class="container"> <header> <h1><i class="fas fa-tachometer-alt"></i> Internet Speed Test</h1> <p>Ukur kecepatan unduh dan unggah koneksi internet Anda</p> </header> <main> <div class="speed-test-container"> <div class="gauge-container"> <div class="gauge"> <div class="gauge-body"> <div class="gauge-fill" id="gaugeFill"></div> </div> <div class="gauge-cover" id="speedValue">0 Mbps</div> </div> <div class="gauge-labels"> <span>0</span> <span>50</span> <span>100</span> <span>150</span> <span>200+</span> </div> </div> <div class="test-info"> <div class="info-box" id="downloadInfo"> <i class="fas fa-download"></i> <h3>Download</h3> <p class="speed">-</p> <p class="status">Klik mulai untuk test</p> </div> <div class="info-box" id="uploadInfo"> <i class="fas fa-upload"></i> <h3>Upload</h3> <p class="speed">-</p> <p class="status">Menunggu...</p> </div> <div class="info-box" id="pingInfo"> <i class="fas fa-signal"></i> <h3>Ping</h3> <p class="speed">-</p> <p class="status">-</p> </div> </div> <button id="startTestBtn" class="test-btn"> <i class="fas fa-play"></i> Mulai Test </button> </div> <div class="history-container"> <h2><i class="fas fa-history"></i> Histori Test</h2> <table id="historyTable"> <thead> <tr> <th>Tanggal</th> <th>Download</th> <th>Upload</th> <th>Ping</th> </tr> </thead> <tbody> <!-- Data akan diisi oleh JavaScript --> </tbody> </table> </div> </main> <footer> <p>© 2025 Internet Speed Test | Azroi id</p> </footer> </div> <script src="script.js"></script> </body> </html>
2. CSS (style.css)
/* Reset & Base Styles */ * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; } body { background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); color: #333; min-height: 100vh; padding: 20px; } .container { max-width: 1000px; margin: 0 auto; background-color: white; border-radius: 15px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1); overflow: hidden; } header { text-align: center; padding: 30px 20px; background: linear-gradient(135deg, #4b6cb7 0%, #182848 100%); color: white; } header h1 { font-size: 2.5rem; margin-bottom: 10px; } header p { opacity: 0.9; } /* Speed Test Container */ .speed-test-container { padding: 30px; text-align: center; } .gauge-container { margin: 0 auto 30px; max-width: 300px; } .gauge { position: relative; width: 100%; height: 0; padding-bottom: 50%; } .gauge-body { width: 100%; height: 100%; position: relative; border-top-left-radius: 100% 200%; border-top-right-radius: 100% 200%; overflow: hidden; background-color: #f5f5f5; border: 5px solid #e0e0e0; border-bottom: none; } .gauge-fill { position: absolute; top: 100%; left: 0; width: 100%; height: 100%; background: linear-gradient(90deg, #00c6ff 0%, #0072ff 100%); transform-origin: center top; transform: rotate(0.5turn); transition: transform 1s ease-out; } .gauge-cover { width: 75%; height: 150%; background: white; border-radius: 50%; position: absolute; top: 25%; left: 50%; transform: translateX(-50%); display: flex; align-items: center; justify-content: center; font-size: 1.8rem; font-weight: bold; color: #333; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1) inset; } .gauge-labels { display: flex; justify-content: space-between; padding: 10px 15px; font-size: 0.9rem; color: #666; } .test-info { display: flex; justify-content: space-around; flex-wrap: wrap; gap: 20px; margin-bottom: 30px; } .info-box { background: white; border-radius: 10px; padding: 20px; width: 150px; box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05); transition: transform 0.3s; } .info-box:hover { transform: translateY(-5px); } .info-box i { font-size: 2rem; margin-bottom: 10px; color: #4b6cb7; } .info-box h3 { margin-bottom: 10px; color: #444; } .info-box .speed { font-size: 1.3rem; font-weight: bold; margin-bottom: 5px; color: #0072ff; } .info-box .status { font-size: 0.9rem; color: #666; } .test-btn { background: linear-gradient(135deg, #4b6cb7 0%, #182848 100%); color: white; border: none; padding: 12px 30px; font-size: 1.1rem; border-radius: 50px; cursor: pointer; transition: all 0.3s; box-shadow: 0 5px 15px rgba(75, 108, 183, 0.4); } .test-btn:hover { transform: translateY(-3px); box-shadow: 0 8px 20px rgba(75, 108, 183, 0.6); } .test-btn:active { transform: translateY(1px); } .test-btn i { margin-right: 8px; } /* History Container */ .history-container { padding: 0 30px 30px; } .history-container h2 { margin-bottom: 20px; color: #444; display: flex; align-items: center; gap: 10px; } .history-container h2 i { color: #4b6cb7; } table { width: 100%; border-collapse: collapse; background: white; border-radius: 10px; overflow: hidden; box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05); } th, td { padding: 12px 15px; text-align: left; border-bottom: 1px solid #eee; } th { background-color: #4b6cb7; color: white; } tr:nth-child(even) { background-color: #f9f9f9; } tr:hover { background-color: #f1f1f1; } /* Footer */ footer { text-align: center; padding: 20px; background: #f5f5f5; color: #666; font-size: 0.9rem; } /* Responsive Design */ @media (max-width: 768px) { .container { border-radius: 0; } .test-info { flex-direction: column; align-items: center; } .info-box { width: 100%; max-width: 250px; } th, td { padding: 8px 10px; font-size: 0.9rem; } } /* Animations */ @keyframes pulse { 0% { transform: scale(1); } 50% { transform: scale(1.05); } 100% { transform: scale(1); } } .testing { animation: pulse 1.5s infinite; } @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } .spinner { display: inline-block; animation: spin 1s linear infinite; margin-right: 8px; }
3. JavaScript (script.js)
document.addEventListener('DOMContentLoaded', function() { // DOM Elements const startTestBtn = document.getElementById('startTestBtn'); const gaugeFill = document.getElementById('gaugeFill'); const speedValue = document.getElementById('speedValue'); const downloadInfo = document.getElementById('downloadInfo'); const uploadInfo = document.getElementById('uploadInfo'); const pingInfo = document.getElementById('pingInfo'); const historyTable = document.getElementById('historyTable').getElementsByTagName('tbody')[0]; // Load history from localStorage let testHistory = JSON.parse(localStorage.getItem('speedTestHistory')) || []; renderHistory(); // Start Test Button Click Event startTestBtn.addEventListener('click', function() { startTestBtn.disabled = true; startTestBtn.innerHTML = '<i class="fas fa-spinner spinner"></i> Mengukur...'; startTestBtn.classList.add('testing'); // Reset UI resetTestUI(); // Run tests sequentially runTests() .then(results => { // Save results to history const testResult = { date: new Date().toLocaleString(), download: results.download, upload: results.upload, ping: results.ping }; testHistory.unshift(testResult); if (testHistory.length > 10) testHistory.pop(); localStorage.setItem('speedTestHistory', JSON.stringify(testHistory)); renderHistory(); }) .catch(error => { console.error('Test error:', error); downloadInfo.querySelector('.status').textContent = 'Error: ' + error.message; }) .finally(() => { startTestBtn.disabled = false; startTestBtn.innerHTML = '<i class="fas fa-redo"></i> Test Lagi'; startTestBtn.classList.remove('testing'); }); }); // Function to run all tests async function runTests() { const results = {}; // Ping Test results.ping = await testPing(); updatePingUI(results.ping); // Download Test results.download = await testDownload(); updateDownloadUI(results.download); // Upload Test results.upload = await testUpload(); updateUploadUI(results.upload); return results; } // Ping Test (simulated) function testPing() { return new Promise(resolve => { setTimeout(() => { // Simulate ping between 10ms and 100ms const ping = Math.floor(Math.random() * 90) + 10; resolve(ping); }, 1000); }); } // Download Test (simulated) function testDownload() { return new Promise(resolve => { let progress = 0; const maxSpeed = 50 + Math.random() * 150; // Random max speed between 50-200 Mbps const interval = setInterval(() => { progress += 5; const currentSpeed = Math.min(maxSpeed, progress); updateGauge(currentSpeed); updateDownloadUI(currentSpeed); if (progress >= maxSpeed) { clearInterval(interval); setTimeout(() => resolve(maxSpeed), 500); } }, 100); }); } // Upload Test (simulated) function testUpload() { return new Promise(resolve => { let progress = 0; // Upload is typically slower than download const maxSpeed = 10 + Math.random() * (Math.random() * 50); const interval = setInterval(() => { progress += 2; const currentSpeed = Math.min(maxSpeed, progress); updateGauge(currentSpeed); updateUploadUI(currentSpeed); if (progress >= maxSpeed) { clearInterval(interval); setTimeout(() => resolve(maxSpeed), 500); } }, 100); }); } // UI Update Functions function resetTestUI() { gaugeFill.style.transform = 'rotate(0.5turn)'; speedValue.textContent = '0 Mbps'; downloadInfo.querySelector('.speed').textContent = '-'; downloadInfo.querySelector('.status').textContent = 'Mengukur...'; uploadInfo.querySelector('.speed').textContent = '-'; uploadInfo.querySelector('.status').textContent = 'Menunggu...'; pingInfo.querySelector('.speed').textContent = '-'; pingInfo.querySelector('.status').textContent = 'Menunggu...'; } function updateGauge(speed) { const percentage = Math.min(speed / 2, 100); // Cap at 200 Mbps (100% of gauge) const rotation = 0.5 + (percentage / 100); gaugeFill.style.transform = `rotate(${rotation}turn)`; speedValue.textContent = speed.toFixed(1) + ' Mbps'; } function updateDownloadUI(speed) { downloadInfo.querySelector('.speed').textContent = speed.toFixed(1) + ' Mbps'; downloadInfo.querySelector('.status').textContent = 'Download selesai'; } function updateUploadUI(speed) { uploadInfo.querySelector('.speed').textContent = speed.toFixed(1) + ' Mbps'; uploadInfo.querySelector('.status').textContent = 'Upload selesai'; } function updatePingUI(ping) { pingInfo.querySelector('.speed').textContent = ping + ' ms'; pingInfo.querySelector('.status').textContent = 'Ping selesai'; } // History Functions function renderHistory() { historyTable.innerHTML = ''; testHistory.forEach(test => { const row = historyTable.insertRow(); const dateCell = row.insertCell(0); dateCell.textContent = test.date; const downloadCell = row.insertCell(1); downloadCell.textContent = test.download.toFixed(1) + ' Mbps'; const uploadCell = row.insertCell(2); uploadCell.textContent = test.upload.toFixed(1) + ' Mbps'; const pingCell = row.insertCell(3); pingCell.textContent = test.ping + ' ms'; }); } });
Fitur Utama
- Pengukuran Tiga Parameter:
- Kecepatan Download (Mbps)
- Kecepatan Upload (Mbps)
- Latensi Ping (ms)
- Visualisasi Data:
- Gauge meter interaktif
- Tampilan kartu informasi
- Animasi real-time saat pengukuran
- Penyimpanan Histori:
- Menyimpan 10 hasil terakhir
- Tampilan dalam format tabel
- Penyimpanan menggunakan localStorage
Cara Menggunakan
- Buka file
index.html
di browser - Klik tombol “Mulai Test”
- Tunggu proses pengukuran selesai
- Lihat hasil di gauge meter dan tabel histori
Pengembangan Lebih Lanjut
- Integrasi dengan API speed test nyata
- Grafik histori perkembangan kecepatan