如何在JavaScript中检测互联网速度?

285

我该如何创建一个JavaScript页面来检测用户的网络速度,并在页面上显示呢?类似于“您的网络速度为??/?? Kb/s”。


1
@Jakub,@Ankit:人们可能会使用Flash,但你不需要。完全没有理由不能用JavaScript来做到这一点。 - T.J. Crowder
这是你需要的:http://speedof.me/api.html - advncd
12个回答

364

在某种程度上是可能的,但不会非常准确。这个想法是加载一个已知文件大小的图像,然后在其onload事件中测量触发该事件所经过的时间,并将此时间除以图像文件大小。

示例可以在这里找到:使用JavaScript计算速度

应用建议修复的测试案例:

//JUST AN EXAMPLE, PLEASE USE YOUR OWN PICTURE!
var imageAddr = "https://upload.wikimedia.org/wikipedia/commons/3/3a/Bloemen_van_adderwortel_%28Persicaria_bistorta%2C_synoniem%2C_Polygonum_bistorta%29_06-06-2021._%28d.j.b%29.jpg"; 
var downloadSize = 7300000; //bytes

function ShowProgressMessage(msg) {
    if (console) {
        if (typeof msg == "string") {
            console.log(msg);
        } else {
            for (var i = 0; i < msg.length; i++) {
                console.log(msg[i]);
            }
        }
    }
    
    var oProgress = document.getElementById("progress");
    if (oProgress) {
        var actualHTML = (typeof msg == "string") ? msg : msg.join("<br />");
        oProgress.innerHTML = actualHTML;
    }
}

function InitiateSpeedDetection() {
    ShowProgressMessage("Loading the image, please wait...");
    window.setTimeout(MeasureConnectionSpeed, 1);
};    

if (window.addEventListener) {
    window.addEventListener('load', InitiateSpeedDetection, false);
} else if (window.attachEvent) {
    window.attachEvent('onload', InitiateSpeedDetection);
}

function MeasureConnectionSpeed() {
    var startTime, endTime;
    var download = new Image();
    download.onload = function () {
        endTime = (new Date()).getTime();
        showResults();
    }
    
    download.onerror = function (err, msg) {
        ShowProgressMessage("Invalid image, or error downloading");
    }
    
    startTime = (new Date()).getTime();
    var cacheBuster = "?nnn=" + startTime;
    download.src = imageAddr + cacheBuster;
    
    function showResults() {
        var duration = (endTime - startTime) / 1000;
        var bitsLoaded = downloadSize * 8;
        var speedBps = (bitsLoaded / duration).toFixed(2);
        var speedKbps = (speedBps / 1024).toFixed(2);
        var speedMbps = (speedKbps / 1024).toFixed(2);
        ShowProgressMessage([
            "Your connection speed is:", 
            speedBps + " bps", 
            speedKbps + " kbps", 
            speedMbps + " Mbps"
        ]);
    }
}
<h1 id="progress" style="font-family:sans-serif">JavaScript is turned off, or your browser is realllllly slow</h1>

"真实"速度测试服务进行快速比较,使用大图时仅有0.12 Mbps的小差异。

为确保测试的完整性,您可以在启用Chrome开发工具限制带宽的情况下运行代码,然后查看结果是否符合限制。(感谢user284130 :))

需要牢记的重要事项:

  1. 使用的图像应该经过适当的优化和压缩。如果没有进行优化,那么网络服务器上的默认压缩可能会显示比实际速度更快。另一个选择是使用不可压缩的文件格式,例如jpg。(感谢Rauli Rajande指出这一点,以及Fluxine提醒我)

  2. 上述缓存破坏机制可能无法与某些CDN服务器配合使用,这些服务器可以配置为忽略查询字符串参数,因此最好在图像本身上设置缓存控制头。(感谢orcaman指出这一点)

  3. 图像大小越大越好。较大的图像将使测试更准确,5 MB是不错的选择,但如果您能使用更大的图像将更好。

  4. 考虑首先了解设备屏幕大小,并相应地选择图像大小。小屏幕通常意味着较慢的连接,因此较小的图像应该足以获得良好的读数。

  5. 最后,请记住其他内容可能会并行下载。因此,如果您需要准确的读数,请在所有下载完成后运行。


10
请注意测试图片要进行适当的优化和压缩。如果没有这样做,那么网页服务器上默认的压缩连接可能会显示比实际速度更快的速度。 - Rauli Rajande
4
我发现一个小技巧可以确保你的图片适用于测试:启用Chrome开发者工具限制网络速度功能,运行代码并看结果是否符合要求。希望这能帮到某些人。 - user284130
3
加入 Rauli Rajande:最好使用一个不能(或者几乎不能)被压缩的文件,否则 Web 服务器的压缩模块会显著降低文件大小,从而使测量无效。JPEG 图像是一个不错的选择。 - Fluxine
3
较小的图像意味着测试不够准确,这个图像故意做得很大。 :) - Shadow The Spring Wizard
2
@AndrewSchultz 是的,很可能是这种情况。速度测试网站通常足够大,拥有世界各地的多个服务器,并使用最接近用户的服务器。此外,您使用我的代码上传文件的服务器可能存在上传限制。 - Shadow The Spring Wizard
显示剩余38条评论

130

好的,既然现在是2017年,你现在可以使用Network Information API(尽管目前在各种浏览器上的支持都有限)来获取某种程度的下行速度估计信息:

navigator.connection.downlink

这是以兆比特每秒(Mbits per sec)为单位的有效带宽估计值。浏览器根据最近活动连接中应用层吞吐量来进行估计。毋庸置疑,这种方法的最大优点是你无需下载任何内容来计算带宽或速度。

你可以查看这个和其他相关属性这里

由于不同浏览器的实现方式不同(截至2017年11月),因此强烈建议详细阅读此处内容


42
在 Can I Use 网站上,这里有很多红色。 - Francisco Presencia
5
使用此方法时,我无法得到大于10MBit的数字。这里有限制吗? - Tobi
1
@Tobi,我似乎也无法获得高于10MBit的速度,应该更接近100MBit。 - camjocotem
1
@Tobi 我也是,如果速度超过10Mb,我就会一直读取10。 - Aramil
7
Chrome将最大值设为10兆比特,以防止指纹识别攻击。 - zachaysan
显示剩余4条评论

33

我需要一种快速的方法来确定用户连接速度是否足够快,以便在我正在开发的网站中启用/禁用某些功能。我编写了这个小脚本,它会对下载一个单一(小)图像的时间进行多次平均,通过我的测试,它工作得非常准确,能够清楚地区分3G或Wi-Fi等不同的网络类型。

var arrTimes = [];
var i = 0; // start
var timesToTest = 5;
var tThreshold = 150; //ms
var testImage = "http://www.google.com/images/phd/px.gif"; // small image in your server
var dummyImage = new Image();
var isConnectedFast = false;

testLatency(function(avg){
  isConnectedFast = (avg <= tThreshold);
  /** output */
  document.body.appendChild(
    document.createTextNode("Time: " + (avg.toFixed(2)) + "ms - isConnectedFast? " + isConnectedFast)
  );
});

/** test and average time took to download image from server, called recursively timesToTest times */
function testLatency(cb) {
  var tStart = new Date().getTime();
  if (i<timesToTest-1) {
    dummyImage.src = testImage + '?t=' + tStart;
    dummyImage.onload = function() {
      var tEnd = new Date().getTime();
      var tTimeTook = tEnd-tStart;
      arrTimes[i] = tTimeTook;
      testLatency(cb);
      i++;
    };
  } else {
    /** calculate average of array items then callback */
    var sum = arrTimes.reduce(function(a, b) { return a + b; });
    var avg = sum / arrTimes.length;
    cb(avg);
  }
}


2
上传测试怎么样? - gumuruh

26

正如我在StackOverflow的另一个答案中概述的那样,您可以通过计时下载不同大小的文件(先从小的开始,如果连接允许,则逐渐增加),通过缓存标头等确保文件确实是从远程服务器读取而不是从缓存中检索。这不一定需要您拥有自己的服务器(文件可以来自S3或类似服务),但您需要提供某个位置以获取文件以测试连接速度。

尽管如此,即使受到其他窗口中正在下载的项目、服务器速度、路由链路等因素的影响,点对点带宽测试也是出了名的不可靠的。但您可以使用这种技术获得一个大致的想法。


1
@Jakub:你需要有一个上传的地方,但是你完全可以使用同样的技术。你可以使用实时生成的数据,或者当然也可以重复使用下载测试中下载的一些数据。 - T.J. Crowder
那么你如何知道上传完成的时间呢? - Jakub Hampl
2
@Jakub:有几种方法。例如,如果您对隐藏的“iframe”进行表单提交,则可以轮询“iframe”或cookie以获取完成状态。如果使用XMLHttpRequest对象进行发布,则有一个回调函数用于完成操作。 - T.J. Crowder

16
尽管这是一个旧问题并已经有答案,但我想分享一下我在2020年根据Shadow Wizard Says No More War的解决方案制作的解决方案。 我把它合并到了一个对象中,并且该对象具有在任何时间运行并在指定的Mbps高于或低于测量结果时运行回调的灵活性。 您可以通过运行testConnectionSpeed对象来自任何位置开始测试(注意在包含testConnectionSpeed对象后)。
/**
* @param float    mbps               - Specify a limit of mbps.
* @param function more(float result) - Called if more mbps than specified limit.
* @param function less(float result) - Called if less mbps than specified limit.
*/
testConnectionSpeed.run(mbps, more, less)

例如:

var testConnectionSpeed = {
  imageAddr : "https://upload.wikimedia.org/wikipedia/commons/a/a6/Brandenburger_Tor_abends.jpg", // this is just an example, you rather want an image hosted on your server
  downloadSize : 2707459, // Must match the file above (from your server ideally)
  run:function(mbps_max,cb_gt,cb_lt){
    testConnectionSpeed.mbps_max = parseFloat(mbps_max) ? parseFloat(mbps_max) : 0;
    testConnectionSpeed.cb_gt = cb_gt;
    testConnectionSpeed.cb_lt = cb_lt;
    testConnectionSpeed.InitiateSpeedDetection();
  },
  InitiateSpeedDetection: function() {
    window.setTimeout(testConnectionSpeed.MeasureConnectionSpeed, 1);
  },
  result:function(){
    var duration = (endTime - startTime) / 1000;
    var bitsLoaded = testConnectionSpeed.downloadSize * 8;
    var speedBps = (bitsLoaded / duration).toFixed(2);
    var speedKbps = (speedBps / 1024).toFixed(2);
    var speedMbps = (speedKbps / 1024).toFixed(2);
    if(speedMbps >= (testConnectionSpeed.max_mbps ? testConnectionSpeed.max_mbps : 1) ){
      testConnectionSpeed.cb_gt ? testConnectionSpeed.cb_gt(speedMbps) : false;
    }else {
      testConnectionSpeed.cb_lt ? testConnectionSpeed.cb_lt(speedMbps) : false;
    }
  },
  MeasureConnectionSpeed:function() {
    var download = new Image();
    download.onload = function () {
        endTime = (new Date()).getTime();
        testConnectionSpeed.result();
    }
    startTime = (new Date()).getTime();
    var cacheBuster = "?nnn=" + startTime;
    download.src = testConnectionSpeed.imageAddr + cacheBuster;
  }
}




// start test immediatly, you could also call this on any event or whenever you want
testConnectionSpeed.run(1.5, function(mbps){console.log(">= 1.5Mbps ("+mbps+"Mbps)")}, function(mbps){console.log("< 1.5Mbps("+mbps+"Mbps)")} )

我成功地使用它来为慢速网络连接加载低分辨率的媒体。您需要进行一些尝试,因为一方面,图像越大,则测试更合理,另一方面,在慢速连接下,测试将需要更长时间,而在我的情况下,我特别不希望慢速连接的用户加载大量 MB。


不起作用,而且这不是普通的JavaScript,出现了错误! - niki
1
你好,这是纯JavaScript代码,它可以正常运行。请查看我添加的可运行代码片段。 - john Smith
2
有没有办法也针对上传速度做到这一点? - Dani
@Dani 是的。不要下载文件,直接上传它。 - DylanYoung
@johnSmith 它适用于托管在 CDN 上的图像吗? - J4GD33P 51NGH
显示剩余4条评论

13

这个图片技巧很酷,但在我的测试中,它会在我希望完成的某些ajax调用之前加载。

2017年的正确解决方案是使用一个worker (http://caniuse.com/#feat=webworkers)。

worker将如下所示:

/**
 * This function performs a synchronous request
 * and returns an object contain informations about the download
 * time and size
 */
function measure(filename) {
  var xhr = new XMLHttpRequest();
  var measure = {};
  xhr.open("GET", filename + '?' + (new Date()).getTime(), false);
  measure.start = (new Date()).getTime();
  xhr.send(null);
  measure.end = (new Date()).getTime();
  measure.len = parseInt(xhr.getResponseHeader('Content-Length') || 0);
  measure.delta = measure.end - measure.start;
  return measure;
}

/**
 * Requires that we pass a base url to the worker
 * The worker will measure the download time needed to get
 * a ~0KB and a 100KB.
 * It will return a string that serializes this informations as
 * pipe separated values
 */
onmessage = function(e) {
  measure0 = measure(e.data.base_url + '/test/0.bz2');
  measure100 = measure(e.data.base_url + '/test/100K.bz2');
  postMessage(
    measure0.delta + '|' +
    measure0.len + '|' +
    measure100.delta + '|' +
    measure100.len
  );
};

调用 Worker 的 js 文件:
var base_url = PORTAL_URL + '/++plone++experimental.bwtools';
if (typeof(Worker) === 'undefined') {
  return; // unsupported
}
w = new Worker(base_url + "/scripts/worker.js");
w.postMessage({
  base_url: base_url
});
w.onmessage = function(event) {
  if (event.data) {
    set_cookie(event.data);
  }
};

这段代码来自我编写的Plone软件包:


使用Worker相对于普通的JavaScript线程的原因是什么? - Ski

9

测试速度最好使用图片。但是如果你必须处理zip文件,下面的代码可以工作。

var fileURL = "your/url/here/testfile.zip";

var request = new XMLHttpRequest();
var avoidCache = "?avoidcache=" + (new Date()).getTime();;
request.open('GET', fileURL + avoidCache, true);
request.responseType = "application/zip";
var startTime = (new Date()).getTime();
var endTime = startTime;
request.onreadystatechange = function () {
    if (request.readyState == 2)
    {
        //ready state 2 is when the request is sent
        startTime = (new Date().getTime());
    }
    if (request.readyState == 4)
    {
        endTime = (new Date()).getTime();
        var downloadSize = request.responseText.length;
        var time = (endTime - startTime) / 1000;
        var sizeInBits = downloadSize * 8;
        var speed = ((sizeInBits / time) / (1024 * 1024)).toFixed(2);
        console.log(downloadSize, time, speed);
    }
}

request.send();

这对小于10MB的文件效果不是很好。您需要在多次下载尝试中运行聚合结果。

3
我非常喜欢这个答案的简洁性,并为了我的目的进行了适应:我使用 window.performance.now 来作为时间戳,将 request.responseType 设置为“blob”(MIME类型无效),使用 request.response.size 来表示下载大小,以及使用 1000000 来计算速度(因为 Mbps 应该使用国际单位制)。 - Rupert Rawnsley

6
感谢Punit S的答案,要检测动态连接速度变化,您可以使用以下代码:
navigator.connection.onchange = function () {
 //do what you need to do ,on speed change event
 console.log('Connection Speed Changed');
}

10
很遗憾,它不支持所有的浏览器。https://caniuse.com/#search=netinfo - axelio

5

在John Smith的答案上进行改进,这是一个漂亮而干净的解决方案,它返回一个Promise,因此可以与async/await一起使用。以Mbps为单位返回一个值。

const imageAddr = 'https://upload.wikimedia.org/wikipedia/commons/a/a6/Brandenburger_Tor_abends.jpg';
const downloadSize = 2707459; // this must match with the image above

let startTime, endTime;
async function measureConnectionSpeed() {
  startTime = (new Date()).getTime();
  const cacheBuster = '?nnn=' + startTime;

  const download = new Image();
  download.src = imageAddr + cacheBuster;
  // this returns when the image is finished downloading
  await download.decode();
  endTime = (new Date()).getTime();
  const duration = (endTime - startTime) / 1000;
  const bitsLoaded = downloadSize * 8;
  const speedBps = (bitsLoaded / duration).toFixed(2);
  const speedKbps = (speedBps / 1024).toFixed(2);
  const speedMbps = (speedKbps / 1024).toFixed(2);
  return Math.round(Number(speedMbps));
}

4

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接