347 lines
8.2 KiB
HTML
347 lines
8.2 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<!--
|
|
Author: Franklin (https://fvdm.com)
|
|
Source: https://github.com/fvdm/speedtest
|
|
-->
|
|
<meta charset="UTF-8">
|
|
<title>Speedtest</title>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<style>
|
|
body {
|
|
font-family: sans-serif;
|
|
font-size: 10px;
|
|
background-color: #e1fff2;
|
|
padding: 0;
|
|
margin: 0;
|
|
}
|
|
#wrap {
|
|
position: absolute;
|
|
width: 200px;
|
|
height: 200px;
|
|
margin-left: -100px;
|
|
margin-top: -100px;
|
|
left: 50%;
|
|
top: 50%;
|
|
background-color: #fff;
|
|
text-align: center;
|
|
}
|
|
|
|
button {
|
|
padding: 0.8rem;
|
|
border: 0;
|
|
outline: none;
|
|
background-color: #fff;
|
|
font-weight: bold;
|
|
}
|
|
button.choice {
|
|
background-color: #ffc800;
|
|
}
|
|
|
|
progress {
|
|
width: 100%;
|
|
height: 30px;
|
|
-webkit-appearance: none;
|
|
-moz-appearance: none;
|
|
appearence: none;
|
|
border: none;
|
|
visibility: hidden;
|
|
background-color: #b4ffe0;
|
|
transition-timing-function: linear;
|
|
}
|
|
progress[value] {
|
|
transition: width 100ms;
|
|
background-color: #00ff9a;
|
|
}
|
|
|
|
progress::-webkit-progress-bar {
|
|
background-color: #b4ffe0;
|
|
}
|
|
progress::-moz-progress-bar {
|
|
background-color: #b4ffe0;
|
|
}
|
|
progress::-webkit-progress-value {
|
|
transition: width 100ms;
|
|
background-color:#00ff9a;
|
|
}
|
|
progress::-moz-progress-value {
|
|
transition: width 100ms;
|
|
background-color:#00ff9a;
|
|
}
|
|
|
|
#result {
|
|
font-size: 1.5rem;
|
|
}
|
|
#resultDone {
|
|
font-size: .8rem;
|
|
font-weight: bold;
|
|
}
|
|
#eta {
|
|
font-size: 1rem;
|
|
}
|
|
|
|
@media (prefers-color-scheme: dark) {
|
|
body {
|
|
background-color: #0e1e17;
|
|
}
|
|
#wrap {
|
|
background-color: #162b22;
|
|
}
|
|
|
|
button {
|
|
background-color: #334940;
|
|
}
|
|
|
|
#result,
|
|
#resultDone,
|
|
#eta {
|
|
color: #1c704b;
|
|
}
|
|
|
|
progress {
|
|
background-color: #053a23;;
|
|
}
|
|
progress::-webkit-progress-bar {
|
|
background-color: #053a23;
|
|
}
|
|
proress::-moz-progress-bar {
|
|
background-color: #053a23;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="wrap">
|
|
<p>
|
|
<button data-file="5mb.bin">5 M</button>
|
|
<button data-file="10mb.bin">10 M</button>
|
|
<button data-file="100mb.bin">100 M</button>
|
|
<button data-file="200mb.bin">200 M</button>
|
|
<button data-file="500mb.bin">500 M</button>
|
|
<button data-file="1000mb.bin">1 G</button>
|
|
</p>
|
|
<p>
|
|
<progress value="0" max="100"></progress>
|
|
</p>
|
|
<p>
|
|
<span id="result">speedtest</span><br>
|
|
<span id="eta">choose a size</span>
|
|
</p>
|
|
</div>
|
|
|
|
<script>
|
|
// global vars
|
|
let req;
|
|
let avg = {};
|
|
let start = 0;
|
|
let decimals = 0;
|
|
let binaryData;
|
|
|
|
// control precision
|
|
function onKeyDown( ev ) {
|
|
switch ( ev.code ) {
|
|
case 'ArrowUp':
|
|
case 'ArrowRight':
|
|
decimals++;
|
|
decimals = decimals > 3 ? 3 : decimals;
|
|
break;
|
|
|
|
case 'ArrowDown':
|
|
case 'ArrowLeft':
|
|
decimals--;
|
|
decimals = decimals < 0 ? 0 : decimals;
|
|
break;
|
|
}
|
|
}
|
|
|
|
document.addEventListener( 'keydown', onKeyDown );
|
|
|
|
// store <button> tags in array
|
|
const btns = Array.from( document.querySelectorAll( 'button' ) );
|
|
|
|
// add click handler to buttons
|
|
btns.forEach( btn => {
|
|
btn.addEventListener( 'click', testDownload );
|
|
} );
|
|
|
|
|
|
/**
|
|
* Round number to defined decimals
|
|
*
|
|
* @param {number} num Number to round
|
|
* @param {number} deci Number of decimals to round to
|
|
* @return {string} Rounded number with trailing zeros
|
|
*/
|
|
|
|
function dec ( num, deci ) {
|
|
const int = parseInt( num, 10 );
|
|
const flo = parseFloat( ( num % 1 ).toFixed( deci ) );
|
|
const len = ( String( flo ).split( '.' )[1] || '' ).length;
|
|
const diff = deci - len;
|
|
let res = String( int + flo );
|
|
|
|
if ( ! flo && diff ) {
|
|
res += '.';
|
|
for ( let i = 0; i < diff; i++ ) {
|
|
res += '0';
|
|
}
|
|
}
|
|
|
|
if ( flo && diff > 0 ) {
|
|
for ( let i = 0; i < diff; i++ ) {
|
|
res += '0';
|
|
}
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
/**
|
|
* Test completed
|
|
*
|
|
* @return {void}
|
|
*/
|
|
|
|
function testDone( updown, evBtn, evReq ) {
|
|
const diff = ( Date.now() - start ) / 1000;
|
|
|
|
if ( updown === 'download' ) {
|
|
binaryData = req.response;
|
|
testUpload( evBtn );
|
|
}
|
|
else {
|
|
binaryData = null;
|
|
resultString =
|
|
dec( avg.download, 1 )
|
|
+ ' / ' + dec( avg.upload, 1 )
|
|
+ ' Mb'
|
|
;
|
|
|
|
document.querySelector( 'progress' ).style.visibility = 'hidden';
|
|
document.querySelector( '#result' ).className = 'resultDone';
|
|
document.querySelector( '#result' ).innerHTML = resultString;
|
|
document.querySelector( '#eta' ).innerHTML = dec( diff, 2 ) + ' sec';
|
|
|
|
req = null;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Test progress handler
|
|
*
|
|
* @param {Event} ev XMLHttpRequest.onprogress event
|
|
* @return {void}
|
|
*/
|
|
|
|
function testRunning ( updown, ev ) {
|
|
const now = Date.now();
|
|
|
|
let percent = 0.0;
|
|
let Bps = 0;
|
|
avg[updown] = 0;
|
|
let eta = 0;
|
|
let total = ev.total;
|
|
let diff = 0;
|
|
|
|
if ( updown === 'upload' ) {
|
|
total = binaryData.size;
|
|
}
|
|
|
|
if ( ev.lengthComputable && total ) {
|
|
diff = ( now - start ) / 1000;
|
|
Bps = ev.loaded / diff;
|
|
mbit = Bps / 1024 / 1024 * 8;
|
|
avg[updown] = mbit;
|
|
percent = ev.loaded / total * 100.0;
|
|
eta = ( total - ev.loaded ) / Bps;
|
|
}
|
|
|
|
if ( updown === 'upload' ) {
|
|
percent = 100 - percent;
|
|
}
|
|
|
|
document.querySelector( 'progress' ).value = percent;
|
|
document.querySelector( '#result' ).innerHTML = dec( avg[updown], decimals ) + ' Mbit/s';
|
|
document.querySelector( '#eta' ).innerHTML = dec( eta, decimals ) + ' sec';
|
|
}
|
|
|
|
|
|
/**
|
|
* Start new test
|
|
* and abort any running test
|
|
*
|
|
* @param {Event} ev Click event
|
|
* @return {void}
|
|
*/
|
|
|
|
function testDownload ( btnEv ) {
|
|
if ( req ) {
|
|
req.abort();
|
|
}
|
|
|
|
req = new XMLHttpRequest;
|
|
start = Date.now();
|
|
|
|
btns.forEach( btn => {
|
|
btn.className = '';
|
|
} );
|
|
|
|
btnEv.target.className = 'choice';
|
|
document.querySelector( 'progress' ).value = 0;
|
|
document.querySelector( 'progress' ).style.visibility = 'visible';
|
|
|
|
req.onprogress = progEv => {
|
|
testRunning( 'download', progEv );
|
|
};
|
|
|
|
req.onreadystatechange = reqEv => {
|
|
if ( req.readyState === 4 ) {
|
|
testDone( 'download', btnEv, reqEv );
|
|
}
|
|
};
|
|
|
|
// load file avoiding the cache
|
|
req.open( 'GET', btnEv.target.dataset.file, true );
|
|
req.responseType = 'blob';
|
|
req.send( null );
|
|
}
|
|
|
|
function testUpload ( btnEv ) {
|
|
if ( req ) {
|
|
req.abort();
|
|
}
|
|
|
|
req = new XMLHttpRequest;
|
|
start = Date.now();
|
|
|
|
if ( req.upload ) {
|
|
req.upload.onprogress = progEv => {
|
|
testRunning( 'upload', progEv );
|
|
};
|
|
|
|
req.upload.onloadend = reqEv => {
|
|
testDone( 'upload', btnEv, reqEv );
|
|
};
|
|
}
|
|
else {
|
|
req.onprogress = progEv => {
|
|
testRunning( 'upload', progEv );
|
|
};
|
|
|
|
req.onreadystatechange = onreadystatechange = reqEv => {
|
|
if ( req.readyState === 4 ) {
|
|
testDone( 'upload', btnEv, reqEv );
|
|
}
|
|
};
|
|
}
|
|
|
|
req.open( 'POST', '?nocache=' + start, true );
|
|
req.send( binaryData );
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|