16

在前端 Network 还能这样玩

 4 years ago
source link: https://semlinker.com/frontend-network-detect/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

明天就除夕了,现在全国 新型冠状病毒 2019-nCoV 的疫情还在不断扩大,84 岁的钟老和大批医务工作者还奋战在一线,相信很多国人新年的愿望就是疫情得到有效的控制,确诊的病人都能早日康复,一起迎接新的一年。武汉加油!中国加油!

这几年手机和网络已经是大多数人生活中的必需品,其中有很多人,比如我家”超哥“,她每次到一个新的环境中一般开口都会来一句,”请问你家有 WIFI 么,密码是多少?“,相信很多人都有这样的经历。接下来,本文将介绍在前端如何实现在线或离线检测、获取网络信息、获取网络延迟和网络测速等内容,有兴趣的小伙伴赶紧学起来。

一、在线或离线检测

在现代的浏览器中,可以通过 navigator.onLine 获取当前网络的在线状态,该属性会根据用户的网络在线状态返回 true 或 false。

navigator.onLine; // true(在线) 
navigator.onLine; // false(离线)

但在某些场景,除了需要获取当前的网络状态之外,我们更希望能监听网络状态的变化,针对这个需求我们可以监听 window 对象的 onlineoffline 事件,具体代码如下:

window.addEventListener('online', () => {
  // 网络恢复咯,:smile:~~
});

window.addEventListener('offline', () => {
  // 网络掉线咯,:cry:~~
});

下面我们来看一个完整的示例,该示例会在页面中动态显示当前的网络状态:

1、页面加载后监听网络变化

window.addEventListener('load', () => {
  // 在页面加载后,设置正确的网络状态
  navigator.onLine ? showStatus(true) : showStatus(false);

  // 开始监听网络状态的变化
  window.addEventListener('online', () => {
    showStatus(true);
  });

  window.addEventListener('offline', () => {
    showStatus(false);
  });
});

2、在页面中动态显示当前的网络状态

function showStatus(online) {
  const statusEl = document.querySelector('.network-status');

  if (online) {
    statusEl.classList.remove('warning');
    statusEl.classList.add('success');
    statusEl.innerText = `You're online! :smile:`;
  } else {
    statusEl.classList.remove('success');
    statusEl.classList.add('warning');
    statusEl.innerText = `You're offline! :cry:`;
  }
}

浏览器兼容情况:

navigator.onLine —— https://caniuse.com/#search=navigator.onLine

online event —— https://caniuse.com/#feat=mdn-api_window_online_event

二、获取网络信息

在某些视频网站中,当用户在非 WIFI 情况下点播视频时,会展示一个友好的提醒,让用户确认是否在非 WIFI 的情况下播放视频。

bYvQR3j.jpg!web

(图片来源 - https://www.bilibili.com/)

要满足这个需求,我们就需要获取用户当前的网络信息。在浏览器中,通过 navigator.connection 可以获取网络连接状态 NetworkInformation 对象。

NetworkInformation 对象提供有关设备正在使用的连接与网络进行通信的信息,并提供了在连接类型更改时通知事件。 NetworkInformation 接口不能被是实例化, 而是通过 Navigator connection 属性进行访问,且该属性是只读的。

NetworkInformation 对象中有多个只读的属性,比如 type 和 downlink 属性。

1、 NetworkInformation.type

返回设备正在与网络进行通信的连接类型。 它将是以下值之一:

bluetooth
cellular
ethernet
none
wifi
wimax
other
unknown

2、 NetworkInformation.downlink

返回下行网络速度,以 Mbps 为单位。

3、 NetworkInformation.downlinkMax

返回基础连接技术的最大下行网络速度,以 Mbps 为单位。

4、 NetworkInformation.effectiveType

返回连接的有效类型,比如 “slow-2g”,“2g”,“3g” 或 “4g”。使用最近观察到的往返时间和下行链路值的组合来确定此值。

5、 NetworkInformation.rtt

表示从发送端发送数据开始,到发送端收到来自接收端的确认(接收端收到数据后便立即发送确认,不包含数据传输时间)总共经历的时间。

6、 NetworkInformation.saveData

如果用户在用户代理上设置了减少数据使用量选项,则返回 true。

若需要监听网络信息的变化,可以通过 NetworkInformation.onchange 的方式来绑定监听函数,当网络信息发生改变时,会自动触发 change 事件,然后执行对应的监听函数。

介绍完上述的知识后,我们来看个检测是否 WIFI 环境的示例代码:

function isWifi() {
  try {
    let wifi = true;
    const ua = window.navigator.userAgent;
    const conn = window.navigator.connection;
    // 判断是否微信环境
    if (/MicroMessenger/.test(ua)) {
      if (ua.indexOf("WIFI") >= 0) {
        return true;
      } else {
        wifi = false;
      }
      // 判断是否支持navigator.connection
    } else if (conn) {
      wifi = conn.type === "wifi"
    }
    return wifi;
  } catch (e) {
    return false;
  }
}

虽然通过 navigator.connection 可以方便地获取当前的网络信息,不过很可惜目前该 API 的兼容性不是很好。

77fiimQ.jpg!web

(图片来源 - https://caniuse.com/ - 2020/01/23)

针对这种情况,我们可以根据当前的平台使用对应的 JS SDK 或安装对应的网络插件。下面我们介绍微信、企业微信、微信小程序、钉钉和 cordova 等平台获取网络信息的方式。

微信/微信小程序/企业微信

wx.getNetworkType({
  success: function (res) {
    var networkType = res.networkType; // 返回网络类型2g,3g,4g,wifi
  }
});

钉钉

dd.device.connection.getNetworkType({
    onSuccess : function(data) {
      {
         // result值: wifi 2g 3g 4g unknown none
         // none表示离线
         result: 'wifi' 
      }
    },
    onFail : function(err) {}
});

cordova

对于 cordova 环境,可以通过安装 cordova-plugin-network-information 这个插件来获取网络信息。

function checkConnection() {
    var networkState = navigator.connection.type;
 
    var states = {};
    states[Connection.UNKNOWN]  = 'Unknown connection';
    states[Connection.ETHERNET] = 'Ethernet connection';
    states[Connection.WIFI]     = 'WiFi connection';
    states[Connection.CELL_2G]  = 'Cell 2G connection';
    states[Connection.CELL_3G]  = 'Cell 3G connection';
    states[Connection.CELL_4G]  = 'Cell 4G connection';
    states[Connection.CELL]     = 'Cell generic connection';
    states[Connection.NONE]     = 'No network connection';
 
    alert('Connection type: ' + states[networkState]);
}
 
checkConnection();

浏览器兼容情况:

navigator.connection —— https://caniuse.com/#search=navigator.connection

三、获取网络延迟

在日常工作中,当遇到某个站点无法访问或网络连接超时的时候,我们经常会打开命令行,然后使用 ping 命令,ping 一下对应的站点。 比如,ping 一下全球最大的同性交友平台:

UjyA7vM.jpg!web

PING (Packet Internet Groper), 因特网 包探索器,用于测试网络连接量的程序。Ping是工作在 TCP/IP 网络体系结构中应用层的一个服务命令, 主要是向特定的目的主机发送 ICMP (Internet Control Message Protocol 因特网报文控制协议) Echo 请求报文,测试目的站是否可达及了解其有关状态。

在 Web 环境中,如果要实现 Ping 的功能,我们可以使用 Github 上 Ping.js 这个 JavaScript 库。Ping.js 是一个小型且简单的 JavaScript 库,用于使用纯 JavaScript 方式来获取指定主机的网络延迟时间。该库的使用示例如下:

const p = new Ping();
p.ping("https://github.com", function(err, data) {
  if (err) {
    console.log("error loading resource")
    data = data + " " + err;
  }
  document.getElementById("ping-github").innerHTML = data;
});

因为 JavaScript 本身并没有提供 ping 的实现,所以通过 ping.js 获取的结果并不能保证准确性。由于 AJAX 请求有跨域的限制,所以不能通过 AJAX 方式来实现。 Ping.js 的实现方式是使用从任意主机加载 favicon.ico 图片来确认响应时间。若 favicon.ico 图片不存在,则会返回 error 字符串和响应时间。

ping 方法的具体实现如下:

Ping.prototype.ping = function(source, callback) {
    var self = this;
    self.wasSuccess = false;
    self.img = new Image();
    self.img.onload = onload;
    self.img.onerror = onerror;

    var timer;
    var start = new Date();

    function onload(e) {
        self.wasSuccess = true;
        pingCheck.call(self, e);
    }

    function onerror(e) {
        self.wasSuccess = false;
        pingCheck.call(self, e);
    }

    if (self.timeout) {
       timer = setTimeout(function() {
         pingCheck.call(self, undefined);
    }, self.timeout); }

    /**
     * 计算响应时间并触发相应回调函数
     */
    function pingCheck() {
        if (timer) { clearTimeout(timer); }
        var pong = new Date() - start;

        if (typeof callback === "function") {
            if (!this.wasSuccess) {
                if (self.logError) { 
                  console.error("error loading resource"); 
                }
                return callback("error", pong);
            }
            return callback(null, pong);
        }
    }

    // 触发图片加载
    self.img.src = source + self.favicon + "?" + (+new Date()); 
};

对于上面的示例,执行 p.ping("https://github.com") 方法时,会发起一个 GET 请求,具体如下图所示:

meimIbY.jpg!web

四、网络测速

在前端要实现网络测速,比如计算下行带宽,一般有以下几种方法:

navigator.connection.downlink

下面我们来重点分析一下以上几种方案的优缺点和具体实现。

4.1 通过 AJAX 测算网速

该方案通过创建 XMLHttpRequest 对象并记录开始时间,然后发起 AJAX 请求,当请求成功后获取 'Content-Length' 响应头来取得资源的大小并记录结束时间,最后计算下行带宽。

该方案的具体实现如下:

function getSpeedWithAjax(url) {
    return new Promise((resolve, reject) => {
        let start = null;
        let end = null;
        start = new Date().getTime();
        const xhr = new XMLHttpRequest();
        xhr.onreadystatechange = function () {
            if (xhr.readyState === 4) {
                end = new Date().getTime();
                const size = xhr.getResponseHeader('Content-Length') / 1024;
                const speed = size * 1000 / (end - start);
                resolve(speed);
            }
        }
        xhr.open('GET', url);
        xhr.send();
    }).catch(err => { throw err });
}

使用示例

getSpeedWithAjax('./speed.jpg')
 .then(speed => {
    console.log(speed);
});

该方案的好处是测试的文件不一定是图片,且返回的数据量能灵活控制。不好的地方是存在跨域问题。

4.2 通过创建 Image 对象加载指定图片来测算网速

该方案是通过创建 Image 对象并记录开始时间,然后绑定 onload 回调函数,接着指定一个有效的图片地址,一旦图片加载完成就会触发 onload 回调函数,最后在回调函数中记录结束时间并计算下行带宽。

该方案的具体实现如下:

function getSpeedWithImg(imgUrl, fileSize) {
    return new Promise((resolve, reject) => {
        let start = null;
        let end = null;
        let img = document.createElement('img');
        start = new Date().getTime();
        img.onload = function (e) {
            end = new Date().getTime();
            const speed = fileSize * 1000 / (end - start);
            resolve(speed);
        }
        img.src = imgUrl;
    }).catch(err => { throw err });
}

使用示例

getSpeedWithImg(
  "https://s2.ax1x.com/2019/08/13/mPJ2iq.jpg", 8.97
).then(speed => {
    console.log(speed);
});

该方案的优点是不会存在跨域问题,不好的地方是要求文件必须是图片且已知文件大小,文件大小不能灵活控制。使用该方案,若需要保证结果准确性,可以考虑进行多次测试取平均值。

4.3 通过 navigator.connection.downlink 直接获取网速

function getSpeedWithDnlink() {
    // downlink测算网速
    const connection = window.navigator.connection;
    if (connection && connection.downlink) {
        return connection.downlink * 1024 / 8;
    }
}

使用示例

getSpeedWithDnlink();

该方案的优点是直接调用浏览器提供的 API 接口,不需要提供任何参数。它的缺点是存在较大的兼容性问题,带宽查询不是实时的,具有分钟级别的时间间隔。

4.4 综合测速

最后我们再来介绍一种综合测速方案,即先尝试采用 navigator.connection.downlink 测速,若当前浏览器不支持的话,再采用多次 AJAX 测速并求平均值。

function getNetSpeed(url, times) {
    // downlink测算网速
    const connection = window.navigator.connection;
    if (connection && connection.downlink) {
        return connection.downlink * 1024 / 8;
    }
    // 多次测速求平均值
    const arr = [];
    for (let i = 0; i < times; i++) {
        arr.push(getSpeedWithAjax(url));
    }
    return Promise.all(arr).then(speeds => {
        let sum = 0;
        speeds.forEach(speed => {
            sum += speed;
        });
        return sum / times;
    })
}

备注:本章节的示例代码来源于 Github 上 network-speed-test 这个开源项目。

五、参考资源


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK