网络信息 API 实现自适应能力

1,731 阅读8分钟

转载请保留这部分内容,注明出处。
关注公众号“头号前端”,每周新鲜前端好文推送。

另外,头条号前端团队非常 期待你的加入

在当下同时有2G、3G、4G、5G等网络条件的情况下,用户对网页的体验也出现了层出不穷的情况。例如在手机上浏览视频网站,能够根据用户当前使用的网络类型,在必要的时候给出提示,是一种很好的体验。

从1G到5G的演进

有兴趣的同学可以看一下, 一部波澜壮阔的移动通信史 简单的说就是: 1G采用的技术是模拟通信系统。只能用于打电话,短信什么的都不可以;信号不稳定,通话质量差;不同国家通信系统不兼容。 2G能发短信、浏览最简单网页、甚至玩游戏(记得以前玩过一款 猫扑网 的文字游戏) 3G主要提升了传输数据的速度,3G手机能快速处理图像、音乐、视频等媒体(3G版qq,偷菜) 4G具备速度快、通信质量高、费用便宜等特点,几乎满足了所有用户对于无线服务的需求 5G拥抱未来,超高速传输会给用户带来更好的体验,能提供更多的服务,一切皆有可能

简单列举JS在浏览器中判断当前网络连接状态的方法

navigator.onLine

demo: html5-demos.appspot.com/static/navi… 通过window.navigator.onLine是否为true来判断当前的网络状态 根据 MDN 的描述:机器未连接到局域网或路由器时返回false,其他情况下均返回true。 也就是说,机器连接上路由器后,即使这个路由器没联通网络,navigator.onLine仍然返回true。 这里做了一个简单的小实验,电脑连接手机热点,此时手机断开流量,电脑无法打开网页,但是navigator.onLine仍然返回true。 除了主动的触发navigator.onLine进行网络状态检测以外,还提供了相关的事件进行监听:

  function updateOnlineStatus(event) {
    console.log(event)
  }
  window.addEventListener('online',  updateOnlineStatus);
  window.addEventListener('offline', updateOnlineStatus);

可以看到伴随着网络的打开和关闭,会触发相应的事件,我们也可以在回调中去进行我们想要的操作。 同样的,路由器的断网无法侦测到。 兼容性方面是非常好的 caniuse.com/#search=onl…

获取网络资源

常用的方法是可以创建一张隐藏图片去发送get请求,通过onerror来判断网络是否异常。

Network Information API

Navigator.connection 是只读的,提供一个 NetworkInformation 对象来获取设备的网络连接信息。例如用户设备的当前带宽或连接是否被计量, 这可以用于基于用户的连接来选择高清晰度内容或低清晰度内容。

如何应对网络连接质量的变化呢?

googlechrome.github.io/samples/net… 我们可以通过 connection.onchange 事件监听器来监听网络变化:

function onConnectionChange() {
    const { rtt, downlink, effectiveType,  saveData } = navigator.connection;

    console.log(`Effective network connection type: ${effectiveType}`);
    console.log(`Downlink Speed/bandwidth estimate: ${downlink}Mb/s`);
    console.log(`Round-trip time estimate: ${rtt}ms`);
    console.log(`Data-saver mode on/requested: ${saveData}`);
}

navigator.connection.addEventListener('change', onConnectionChange)

type 返回设备正在与网络进行通信的连接类型

enum ConnectionType {
  "bluetooth",
  "cellular",
  "ethernet",
  "mixed",
  "none",
  "other",
  "unknown",
  "wifi",
  "wimax"
};
  • bluetooth 蓝牙连接

  • cellular 蜂窝连接

  • ethernet 以太网连接

  • none 无网络连接

  • wifi Wi-Fi连接

  • wimax WiMAX连接

·在概念上类似 WiFi ,WiMAX传送速率更快,传送范围距离更大,简单理解为一种“大WiFi”。

  • other 已知的连接类型,但不是枚举连接类型之一。

  • unknown 用户代理已建立网络连接,但无法或不愿确定基础连接技术。

downlink 有效带宽估计

该属性表示基于最近观察到的活动连接的有效带宽估计值(以Mb / s为单位)。

effectiveType

effectiveType 可取值有 'slow-2g'、'2g'、'3g' 或者 '4g'。在网速慢的时候,此功能可以让你通过提供较低质量的资源来提高页面的加载速度。

enum EffectiveConnectionType {
  "2g",
  "3g",
  "4g",
  "slow-2g"
};

rtt来回通信延迟

在通信(Communication)、电脑网络(Computer network)领域中,意指:在双方通信中,发讯方的信号(Signal)传播(Propagation)到收讯方的时间(意即:传播延迟(Propagation delay)),加上收讯方回传消息到发讯方的时间(如果没有造成双向传播速率差异的因素,此时间与发讯方将信号传播到收讯方的时间一样久)

saveData

developers.google.com/web/fundame… 正确解决用户对快速轻便的应用程序和页面的高需求,一种相当简单的技术是让浏览器使用Save-Data请求标头提供帮助 。通过标识此标头,网页可以自定义并向受成本和性能限制的用户提供最佳的用户体验。

兼容性

大体看来Chrome对它的兼容性会较好一些,其他的浏览器兼容性参差不齐,不过,它是渐进式增强的一个很好的候选者,并且对其他平台的支持 也在不断增加caniuse.com/#search=Net…

网络感知组件

codepen.io/mxbck/pen/J…

function withConnectionType(WrappedComponent, respondToChange = false) {
    return class extends React.Component {
        constructor(props) {
            super(props)
            this.state = {
                connectionType: undefined
            }
            // Basic API Support Check.
            this.hasNetworkInfoSupport = Boolean(
                navigator.connection && navigator.connection.effectiveType
            )
            this.setConnectionType = this.setConnectionType.bind(this)
        }

        componentWillMount() {
            // Check before the component first renders.
            this.setConnectionType()
        }

        componentDidMount() {
            // optional: respond to connectivity changes.
            if (respondToChange) {
                navigator.connection.addEventListener(
                    'change', 
                    this.setConnectionType
                )
            }
        }

        componentWillUnmount() {
            if (respondToChange) {
                navigator.connection.removeEventListener(
                    'change', 
                    this.setConnectionType
                )
            }
        }

        getConnectionType() {
            const connection = navigator.connection
            // check if we're offline first...
            if (!navigator.onLine) {
                return 'offline'
            }
            // ...or if reduced data is preferred.
            if (connection.saveData) {
                return 'saveData'
            }
            return connection.effectiveType
        }

        setConnectionType() {
            if (this.hasNetworkInfoSupport) {
                const connectionType = this.getConnectionType()
                this.setState({
                    connectionType
                })
            }
        }

        render() {
            // inject the prop into our component.
            // default to "undefined" if API is not supported.
            return (
                <WrappedComponent
                    connectionType={this.state.connectionType}
                    {...this.props}
                />
            )
        }
    }
}

// Now we can reuse the function to enhance all kinds of components.
const ConnectionAwareMedia = withConnectionType(Media)

结合 localForage 的增强体验

localForage 是一个 JavaScript 库,通过简单类似 localStorage API 的异步存储来改进你的 Web 应用程序的离线体验。它能存储多种类型的数据,而不仅仅是字符串。 localForage 有一个优雅降级策略,若浏览器不支持 IndexedDB 或 WebSQL,则使用 localStorage。在所有主流浏览器中都可用:Chrome,Firefox,IE 和 Safari(包括 Safari Mobile)。 默认情况下,localForage 按照以下顺序选择数据仓库的后端驱动:

  1. IndexedDB

  2. WebSQL

  3. localStorage

浏览器本地存储的大小测试

dev-test.nemikor.com/web-storage…

getItem(key, successCallback)

localforage.getItem('somekey').then(function(value) {
    // 当离线仓库中的值被载入时,此处代码运行
    console.log(value);
}).catch(function(err) {
    // 当出错时,此处代码运行
    console.log(err);
});

// 回调版本:
localforage.getItem('somekey', function(err, value) {
    // 当离线仓库中的值被载入时,此处代码运行
    console.log(value);
});

setItem(key, value, successCallback)

将数据保存到离线仓库。你可以存储如下类型的 JavaScript 对象: Array、ArrayBuffer、Blob、Float32Array、Float64Array、Int8Array、Int16Array、Int32Array、Number、Object、Uint8Array、Uint8ClampedArray、Uint16Array、Uint32Array、String

localforage.setItem('somekey', 'some value').then(function (value) {
    // 当值被存储后,可执行其他操作
    console.log(value);
}).catch(function(err) {
    // 当出错时,此处代码运行
    console.log(err);
});

// 不同于 localStorage,你可以存储非字符串类型
localforage.setItem('my array', [1, 2, 'three']).then(function(value) {
    // 如下输出 `1`
    console.log(value[0]);
}).catch(function(err) {
    // 当出错时,此处代码运行
    console.log(err);
});

// 你甚至可以存储 AJAX 响应返回的二进制数据
req = new XMLHttpRequest();
req.open('GET', '/photo.jpg', true);
req.responseType = 'arraybuffer';

req.addEventListener('readystatechange', function() {
    if (req.readyState === 4) { // readyState 完成
        localforage.setItem('photo', req.response).then(function(image) {
            // 如下为一个合法的 <img> 标签的 blob URI
            var blob = new Blob([image]);
            var imageURI = window.URL.createObjectURL(blob);
        }).catch(function(err) {
          // 当出错时,此处代码运行
          console.log(err);
        });
    }
});

removeItem(key, successCallback)

localforage.removeItem('somekey').then(function() {
    // 当值被移除后,此处代码运行
    console.log('Key is cleared!');
}).catch(function(err) {
    // 当出错时,此处代码运行
    console.log(err);
});

clear(successCallback)

localforage.clear().then(function() {
    // 当数据库被全部删除后,此处代码运行
    console.log('Database is now empty.');
}).catch(function(err) {
    // 当出错时,此处代码运行
    console.log(err);
});

IndexedDB 浏览器存储限制和清理标准

储存限制

浏览器的最大存储空间是动态的——它取决于硬盘大小。 全局限制 ——可用磁盘空间的50%。 如果您的硬盘驱动器是500GB,那么浏览器的总存储容量为250GB。如果超过此范围,则会发起称为 源回收 的过程,删除整个源的数据,直到存储量再次低于限制。删除源数据没有只删一部分的说法——因为这样可能会导致不一致的问题。 组限制 ——这被定义为全局限制的20%,但它至少有10 MB,最大为2GB。 每个源都是一组(源组)的一部分。 每个eTLD+1域都有一个组。 例如:

mozilla.org——组1,源1 www.mozilla.org——组1,源2 joe.blogs.mozilla.org——组1,源3 firefox.com——组2,源4 在这个组中,mozilla.org、www.mozilla.org和joe.blogs.mozilla.org可以聚合使用最多20%的全局限制。firefox.com单独最多使用20%。

达到限制后有两种不同的反应:

  • 组限制也称为“硬限制”:它不会触发源回收,如果超出,浏览器将抛出 QuotaExceededError 错误。

  • 全局限制是一个“软限制”,因为其有可能释放一些空间并且这个操作可能持续。

LRU策略

当可用磁盘空间已满时, 配额管理器 将根据LRU策略开始清除数据——最近最少使用的源将首先被删除,然后是下一个,直到浏览器不再超过限制。 使用临时存储跟踪每个源的“上次访问时间”。 一旦达到临时存储的全局限制(之后会有更多限制),系统将尝试查找所有当前未使用的源(即没有打开选项卡/应用程序的那些来保持打开的数据存储)。 然后根据“上次访问时间”对它们进行排序。 然后删除最近最少使用的源,直到有足够的空间来满足触发此源回收的请求。

总结

通过网络感知提供一个自适应的能力,结合离线存储方案以及渐进式体验等方式,在当下网络质量参差不齐的情况下,可以更好的增强用户体验。