index-classic.html 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <link rel="shortcut icon" href="favicon.ico">
  5. <link rel="manifest" href="manifest.webmanifest">
  6. <link rel="apple-touch-icon" href="images/icon-192.png">
  7. <meta name="theme-color" content="#000000">
  8. <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, user-scalable=no" />
  9. <meta charset="UTF-8" />
  10. <script type="text/javascript" src="speedtest.js"></script>
  11. <script type="text/javascript">
  12. function I(i) { return document.getElementById(i); }
  13. //LIST OF TEST SERVERS. Leave empty if you're doing a standalone installation. See documentation for details
  14. var SPEEDTEST_SERVERS = [
  15. /*{ //this server doesn't actually exist, remove it
  16. name:"Example Server 1", //user friendly name for the server
  17. server:"//test1.mydomain.com/", //URL to the server. // at the beginning will be replaced with http:// or https:// automatically
  18. dlURL:"backend/garbage.php", //path to download test on this server (garbage.php or replacement)
  19. ulURL:"backend/empty.php", //path to upload test on this server (empty.php or replacement)
  20. pingURL:"backend/empty.php", //path to ping/jitter test on this server (empty.php or replacement)
  21. getIpURL:"backend/getIP.php" //path to getIP on this server (getIP.php or replacement)
  22. },
  23. { //this server doesn't actually exist, remove it
  24. name:"Example Server 2", //user friendly name for the server
  25. server:"//test2.example.com/", //URL to the server. // at the beginning will be replaced with http:// or https:// automatically
  26. dlURL:"garbage.php", //path to download test on this server (garbage.php or replacement)
  27. ulURL:"empty.php", //path to upload test on this server (empty.php or replacement)
  28. pingURL:"empty.php", //path to ping/jitter test on this server (empty.php or replacement)
  29. getIpURL:"getIP.php" //path to getIP on this server (getIP.php or replacement)
  30. }*/
  31. //add other servers here, comma separated
  32. ];
  33. //INITIALIZE SPEEDTEST
  34. var s = new Speedtest(); //create speed test object
  35. s.setParameter("telemetry_level", "basic"); //enable basic telemetry (for results sharing)
  36. //SERVER AUTO SELECTION
  37. function initServers() {
  38. if (SPEEDTEST_SERVERS.length == 0) { //standalone installation
  39. //just make the UI visible
  40. I("loading").className = "hidden";
  41. I("serverArea").style.display = "none";
  42. I("testWrapper").className = "visible";
  43. initUI();
  44. } else { //multiple servers
  45. var noServersAvailable = function () {
  46. I("message").innerHTML = "No servers available";
  47. }
  48. var runServerSelect = function () {
  49. s.selectServer(function (server) {
  50. if (server != null) { //at least 1 server is available
  51. I("loading").className = "hidden"; //hide loading message
  52. //sort servers by country, then by city
  53. //name formats: "City, Country", "City, Country (qualifier)", "City, Country, Provider", "Country"
  54. function parseServerName(name) {
  55. var parts = (name || "").split(",");
  56. for (var p = 0; p < parts.length; p++) parts[p] = parts[p].trim();
  57. var country, city;
  58. if (parts.length >= 3) {
  59. country = parts[1];
  60. city = parts[0];
  61. } else if (parts.length === 2) {
  62. country = parts[1];
  63. city = parts[0];
  64. } else {
  65. country = parts[0];
  66. city = "";
  67. }
  68. country = country.replace(/\s*\([^)]*\)\s*/g, "").trim();
  69. return { country: country, city: city };
  70. }
  71. var indexed = [];
  72. for (var j = 0; j < SPEEDTEST_SERVERS.length; j++) {
  73. indexed.push({ idx: j, server: SPEEDTEST_SERVERS[j] });
  74. }
  75. indexed.sort(function (a, b) {
  76. var pa = parseServerName(a.server.name);
  77. var pb = parseServerName(b.server.name);
  78. return pa.country.localeCompare(pb.country) || pa.city.localeCompare(pb.city);
  79. });
  80. //populate server list for manual selection
  81. for (var i = 0; i < indexed.length; i++) {
  82. if (indexed[i].server.pingT == -1) continue;
  83. var option = document.createElement("option");
  84. option.value = indexed[i].idx;
  85. option.textContent = indexed[i].server.name;
  86. if (indexed[i].server === server) option.selected = true;
  87. I("server").appendChild(option);
  88. }
  89. //show test UI
  90. I("testWrapper").className = "visible";
  91. initUI();
  92. } else { //no servers are available, the test cannot proceed
  93. noServersAvailable();
  94. }
  95. });
  96. }
  97. if (typeof SPEEDTEST_SERVERS === "string") {
  98. //need to fetch list of servers from specified URL
  99. s.loadServerList(SPEEDTEST_SERVERS, function (servers) {
  100. if (servers == null) { //failed to load server list
  101. noServersAvailable();
  102. } else { //server list loaded
  103. SPEEDTEST_SERVERS = servers;
  104. runServerSelect();
  105. }
  106. });
  107. } else {
  108. //hardcoded server list
  109. s.addTestPoints(SPEEDTEST_SERVERS);
  110. runServerSelect();
  111. }
  112. }
  113. }
  114. var meterBk = /Trident.*rv:(\d+\.\d+)/i.test(navigator.userAgent) ? "#EAEAEA" : "#80808040";
  115. var dlColor = "#6060AA",
  116. ulColor = "#616161";
  117. var progColor = meterBk;
  118. //CODE FOR GAUGES
  119. function drawMeter(c, amount, bk, fg, progress, prog) {
  120. var ctx = c.getContext("2d");
  121. var dp = window.devicePixelRatio || 1;
  122. var cw = c.clientWidth * dp, ch = c.clientHeight * dp;
  123. var sizScale = ch * 0.0055;
  124. if (c.width == cw && c.height == ch) {
  125. ctx.clearRect(0, 0, cw, ch);
  126. } else {
  127. c.width = cw;
  128. c.height = ch;
  129. }
  130. ctx.beginPath();
  131. ctx.strokeStyle = bk;
  132. ctx.lineWidth = 12 * sizScale;
  133. ctx.arc(c.width / 2, c.height - 58 * sizScale, c.height / 1.8 - ctx.lineWidth, -Math.PI * 1.1, Math.PI * 0.1);
  134. ctx.stroke();
  135. ctx.beginPath();
  136. ctx.strokeStyle = fg;
  137. ctx.lineWidth = 12 * sizScale;
  138. ctx.arc(c.width / 2, c.height - 58 * sizScale, c.height / 1.8 - ctx.lineWidth, -Math.PI * 1.1, amount * Math.PI * 1.2 - Math.PI * 1.1);
  139. ctx.stroke();
  140. if (typeof progress !== "undefined") {
  141. ctx.fillStyle = prog;
  142. ctx.fillRect(c.width * 0.3, c.height - 16 * sizScale, c.width * 0.4 * progress, 4 * sizScale);
  143. }
  144. }
  145. function mbpsToAmount(s) {
  146. return 1 - (1 / (Math.pow(1.3, Math.sqrt(s))));
  147. }
  148. function format(d) {
  149. d = Number(d);
  150. if (d < 10) return d.toFixed(2);
  151. if (d < 100) return d.toFixed(1);
  152. return d.toFixed(0);
  153. }
  154. //UI CODE
  155. var uiData = null;
  156. function startStop() {
  157. if (s.getState() == 3) {
  158. //speed test is running, abort
  159. s.abort();
  160. data = null;
  161. I("startStopBtn").className = "";
  162. I("startStopBtn").setAttribute("aria-label", "Start");
  163. I("server").disabled = false;
  164. initUI();
  165. } else {
  166. //test is not running, begin
  167. I("startStopBtn").className = "running";
  168. I("startStopBtn").setAttribute("aria-label", "Abort");
  169. I("shareArea").style.display = "none";
  170. I("server").disabled = true;
  171. s.onupdate = function (data) {
  172. uiData = data;
  173. };
  174. s.onend = function (aborted) {
  175. I("startStopBtn").className = "";
  176. I("startStopBtn").setAttribute("aria-label", "Start");
  177. I("server").disabled = false;
  178. updateUI(true);
  179. if (!aborted) {
  180. //if testId is present, show sharing panel, otherwise do nothing
  181. try {
  182. var testId = uiData.testId;
  183. if (testId != null) {
  184. var shareURL = window.location.href.substring(0, window.location.href.lastIndexOf("/")) + "/results/?id=" + testId;
  185. I("resultsImg").src = shareURL;
  186. I("resultsURL").value = shareURL;
  187. I("testId").innerHTML = testId;
  188. I("shareArea").style.display = "";
  189. }
  190. } catch (e) { }
  191. }
  192. };
  193. s.start();
  194. }
  195. }
  196. //this function reads the data sent back by the test and updates the UI
  197. function updateUI(forced) {
  198. if (!forced && s.getState() != 3) return;
  199. if (uiData == null) return;
  200. var status = uiData.testState;
  201. I("ip").textContent = uiData.clientIp;
  202. I("dlText").textContent = (status == 1 && uiData.dlStatus == 0) ? "..." : format(uiData.dlStatus);
  203. drawMeter(I("dlMeter"), mbpsToAmount(Number(uiData.dlStatus * (status == 1 ? oscillate() : 1))), meterBk, dlColor, Number(uiData.dlProgress), progColor);
  204. I("ulText").textContent = (status == 3 && uiData.ulStatus == 0) ? "..." : format(uiData.ulStatus);
  205. drawMeter(I("ulMeter"), mbpsToAmount(Number(uiData.ulStatus * (status == 3 ? oscillate() : 1))), meterBk, ulColor, Number(uiData.ulProgress), progColor);
  206. I("pingText").textContent = format(uiData.pingStatus);
  207. I("jitText").textContent = format(uiData.jitterStatus);
  208. }
  209. function oscillate() {
  210. return 1 + 0.02 * Math.sin(Date.now() / 100);
  211. }
  212. //update the UI every frame
  213. window.requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.msRequestAnimationFrame || (function (callback, element) { setTimeout(callback, 1000 / 60); });
  214. function frame() {
  215. requestAnimationFrame(frame);
  216. updateUI();
  217. }
  218. frame(); //start frame loop
  219. //function to (re)initialize UI
  220. function initUI() {
  221. drawMeter(I("dlMeter"), 0, meterBk, dlColor, 0);
  222. drawMeter(I("ulMeter"), 0, meterBk, ulColor, 0);
  223. I("dlText").textContent = "";
  224. I("ulText").textContent = "";
  225. I("pingText").textContent = "";
  226. I("jitText").textContent = "";
  227. I("ip").textContent = "";
  228. }
  229. </script>
  230. <style type="text/css">
  231. html,
  232. body {
  233. border: none;
  234. padding: 0;
  235. margin: 0;
  236. background: #FFFFFF;
  237. color: #202020;
  238. }
  239. body {
  240. text-align: center;
  241. font-family: "Roboto", sans-serif;
  242. }
  243. h1 {
  244. color: #404040;
  245. }
  246. #loading {
  247. background-color: #FFFFFF;
  248. color: #404040;
  249. text-align: center;
  250. }
  251. span.loadCircle {
  252. display: inline-block;
  253. width: 2em;
  254. height: 2em;
  255. vertical-align: middle;
  256. background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAMAAAD04JH5AAAAP1BMVEUAAAB2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZyFzwnAAAAFHRSTlMAEvRFvX406baecwbf0casimhSHyiwmqgAAADpSURBVHja7dbJbQMxAENRahnN5lkc//5rDRAkDeRgHszXgACJoKiIiIiIiIiIiIiIiIiIiIj4HHspsrpAVhdVVguzrA4OWc10WcEqpwKbnBo0OU1Q5NSpsoJFTgOecrrdEag85DRgktNqfoEdTjnd7hrEHMEJvmRUYJbTYk5Agy6nau6Abp5Cm7mDBtRdPi9gyKdU7w4p1fsLvyqs8hl4z9/w3n/Hmr9WoQ65lAU4d7lMYOz//QboRR5jBZibLMZdAR6O/Vfa1PlxNr3XdS3HzK/HVPRu/KnLs8iAOh993VpRRERERMT/fAN60wwWaVyWwAAAAABJRU5ErkJggg==');
  257. background-size: 2em 2em;
  258. margin-right: 0.5em;
  259. animation: spin 0.6s linear infinite;
  260. }
  261. @keyframes spin {
  262. 0% {
  263. transform: rotate(0deg);
  264. }
  265. 100% {
  266. transform: rotate(359deg);
  267. }
  268. }
  269. #startStopBtn {
  270. display: inline-block;
  271. margin: 0 auto;
  272. color: #6060AA;
  273. background-color: rgba(0, 0, 0, 0);
  274. border: 0.15em solid #6060FF;
  275. padding: 0;
  276. font: inherit;
  277. border-radius: 0.3em;
  278. transition: all 0.3s;
  279. box-sizing: border-box;
  280. width: 8em;
  281. height: 3em;
  282. line-height: 2.7em;
  283. cursor: pointer;
  284. box-shadow: 0 0 0 rgba(0, 0, 0, 0.1), inset 0 0 0 rgba(0, 0, 0, 0.1);
  285. }
  286. #startStopBtn:hover {
  287. box-shadow: 0 0 2em rgba(0, 0, 0, 0.1), inset 0 0 1em rgba(0, 0, 0, 0.1);
  288. }
  289. #startStopBtn.running {
  290. background-color: #FF3030;
  291. border-color: #FF6060;
  292. color: #FFFFFF;
  293. }
  294. #startStopBtn:before {
  295. content: "Start";
  296. }
  297. #startStopBtn.running:before {
  298. content: "Abort";
  299. }
  300. #serverArea {
  301. margin-top: 1em;
  302. }
  303. #server {
  304. font-size: 1em;
  305. padding: 0.2em;
  306. }
  307. #test {
  308. margin-top: 2em;
  309. margin-bottom: 12em;
  310. }
  311. div.testArea {
  312. display: inline-block;
  313. width: 16em;
  314. height: 12.5em;
  315. position: relative;
  316. box-sizing: border-box;
  317. }
  318. div.testArea2 {
  319. display: inline-block;
  320. width: 14em;
  321. height: 7em;
  322. position: relative;
  323. box-sizing: border-box;
  324. text-align: center;
  325. }
  326. div.testArea div.testName {
  327. position: absolute;
  328. top: 0.1em;
  329. left: 0;
  330. width: 100%;
  331. font-size: 1.4em;
  332. z-index: 9;
  333. }
  334. div.testArea2 div.testName {
  335. display: block;
  336. text-align: center;
  337. font-size: 1.4em;
  338. }
  339. div.testArea div.meterText {
  340. position: absolute;
  341. bottom: 1.55em;
  342. left: 0;
  343. width: 100%;
  344. font-size: 2.5em;
  345. z-index: 9;
  346. }
  347. div.testArea2 div.meterText {
  348. display: inline-block;
  349. font-size: 2.5em;
  350. }
  351. div.meterText:empty:before {
  352. content: "0.00";
  353. }
  354. div.testArea div.unit {
  355. position: absolute;
  356. bottom: 2em;
  357. left: 0;
  358. width: 100%;
  359. z-index: 9;
  360. }
  361. div.testArea2 div.unit {
  362. display: inline-block;
  363. }
  364. div.testArea canvas {
  365. position: absolute;
  366. top: 0;
  367. left: 0;
  368. width: 100%;
  369. height: 100%;
  370. z-index: 1;
  371. }
  372. div.testGroup {
  373. display: block;
  374. margin: 0 auto;
  375. }
  376. #shareArea {
  377. width: 95%;
  378. max-width: 40em;
  379. margin: 0 auto;
  380. margin-top: 2em;
  381. }
  382. #shareArea>* {
  383. display: block;
  384. width: 100%;
  385. height: auto;
  386. margin: 0.25em 0;
  387. }
  388. #privacyPolicy {
  389. position: fixed;
  390. top: 2em;
  391. bottom: 2em;
  392. left: 2em;
  393. right: 2em;
  394. overflow-y: auto;
  395. width: auto;
  396. height: auto;
  397. box-shadow: 0 0 3em 1em #000000;
  398. z-index: 999999;
  399. text-align: left;
  400. background-color: #FFFFFF;
  401. padding: 1em;
  402. }
  403. a.privacy {
  404. text-align: center;
  405. font-size: 0.8em;
  406. color: #808080;
  407. padding: 0 3em;
  408. }
  409. div.closePrivacyPolicy {
  410. width: 100%;
  411. text-align: center;
  412. }
  413. div.closePrivacyPolicy a.privacy {
  414. padding: 1em 3em;
  415. }
  416. @media all and (max-width:40em) {
  417. body {
  418. font-size: 0.8em;
  419. }
  420. }
  421. div.visible {
  422. animation: fadeIn 0.4s;
  423. display: block;
  424. }
  425. div.hidden {
  426. animation: fadeOut 0.4s;
  427. display: none;
  428. }
  429. @keyframes fadeIn {
  430. 0% {
  431. opacity: 0;
  432. }
  433. 100% {
  434. opacity: 1;
  435. }
  436. }
  437. @keyframes fadeOut {
  438. 0% {
  439. display: block;
  440. opacity: 1;
  441. }
  442. 100% {
  443. display: block;
  444. opacity: 0;
  445. }
  446. }
  447. @media all and (prefers-color-scheme: dark) {
  448. html,
  449. body,
  450. #loading {
  451. background: #202020;
  452. color: #F4F4F4;
  453. color-scheme: dark;
  454. }
  455. h1 {
  456. color: #E0E0E0;
  457. }
  458. a {
  459. color: #9090FF;
  460. }
  461. #privacyPolicy {
  462. background: #000000;
  463. }
  464. #resultsImg {
  465. filter: invert(1);
  466. }
  467. }
  468. </style>
  469. <title>LibreSpeed</title>
  470. </head>
  471. <body onload="initServers()">
  472. <h1>LibreSpeed</h1>
  473. <div id="loading" class="visible">
  474. <p id="message"><span class="loadCircle"></span>Selecting a server...</p>
  475. </div>
  476. <div id="testWrapper" class="hidden">
  477. <button id="startStopBtn" onclick="startStop()" aria-label="Start"></button><br />
  478. <a class="privacy" href="#" onclick="I('privacyPolicy').style.display=''">Privacy</a>
  479. <div id="serverArea">
  480. Server: <select id="server" onchange="s.setSelectedServer(SPEEDTEST_SERVERS[this.value])"></select>
  481. </div>
  482. <div id="test">
  483. <div class="testGroup">
  484. <div class="testArea2">
  485. <div class="testName">Ping</div>
  486. <div id="pingText" class="meterText" style="color:#AA6060"></div>
  487. <div class="unit">ms</div>
  488. </div>
  489. <div class="testArea2">
  490. <div class="testName">Jitter</div>
  491. <div id="jitText" class="meterText" style="color:#AA6060"></div>
  492. <div class="unit">ms</div>
  493. </div>
  494. </div>
  495. <div class="testGroup">
  496. <div class="testArea">
  497. <div class="testName">Download</div>
  498. <canvas id="dlMeter" class="meter"></canvas>
  499. <div id="dlText" class="meterText"></div>
  500. <div class="unit">Mbit/s</div>
  501. </div>
  502. <div class="testArea">
  503. <div class="testName">Upload</div>
  504. <canvas id="ulMeter" class="meter"></canvas>
  505. <div id="ulText" class="meterText"></div>
  506. <div class="unit">Mbit/s</div>
  507. </div>
  508. </div>
  509. <div id="ipArea">
  510. <span id="ip"></span>
  511. </div>
  512. <div id="shareArea" style="display:none">
  513. <h3>Share results</h3>
  514. <p>Test ID: <span id="testId"></span></p>
  515. <input type="text" value="" id="resultsURL" readonly="readonly"
  516. onclick="this.select();this.focus();this.select();document.execCommand('copy');alert('Link copied')" />
  517. <img src="" id="resultsImg" />
  518. </div>
  519. </div>
  520. <a href="index.html?design=new">Try the modern design</a><br>
  521. <a href="stability.html">Stability Test</a> | <a href="https://github.com/librespeed/speedtest">Source code</a>
  522. </div>
  523. <div id="privacyPolicy" style="display:none">
  524. <h2>Privacy Policy</h2>
  525. <p>This HTML5 speed test server is configured with telemetry enabled.</p>
  526. <h4>What data we collect</h4>
  527. <p>
  528. At the end of the test, the following data is collected and stored:
  529. <ul>
  530. <li>Test ID</li>
  531. <li>Time of testing</li>
  532. <li>Test results (download and upload speed, ping and jitter)</li>
  533. <li>IP address</li>
  534. <li>ISP information</li>
  535. <li>Approximate location (inferred from IP address, not GPS)</li>
  536. <li>User agent and browser locale</li>
  537. <li>Test log (contains no personal information)</li>
  538. </ul>
  539. </p>
  540. <h4>How we use the data</h4>
  541. <p>
  542. Data collected through this service is used to:
  543. <ul>
  544. <li>Allow sharing of test results (sharable image for forums, etc.)</li>
  545. <li>To improve the service offered to you (for instance, to detect problems on our side)</li>
  546. </ul>
  547. No personal information is disclosed to third parties.
  548. </p>
  549. <h4>Your consent</h4>
  550. <p>
  551. By starting the test, you consent to the terms of this privacy policy.
  552. </p>
  553. <h4>Data removal</h4>
  554. <p>
  555. If you want to have your information deleted, you need to provide either the ID of the test or your IP
  556. address. This is the only way to identify your data, without this information we won't be able to comply
  557. with your request.<br /><br />
  558. Contact this email address for all deletion requests:
  559. <a href="mailto:PUT@YOUR_EMAIL.HERE">TO BE FILLED BY DEVELOPER</a>.
  560. </p>
  561. <br /><br />
  562. <div class="closePrivacyPolicy">
  563. <a class="privacy" href="#" onclick="I('privacyPolicy').style.display='none'">Close</a>
  564. </div>
  565. <br />
  566. </div>
  567. </body>
  568. </html>