pin_drop当前位置:知识文库 ❯ 图文

HTML5 API:Network Information API - 完整教程与代码示例

一、教程简介

Network Information API 允许 Web 应用获取设备的网络连接信息,包括连接类型(WiFi、蜂窝网络等)、有效带宽、往返时间等。通过这些信息,开发者可以根据网络状况动态调整应用行为,例如在慢速网络下加载低分辨率图片、减少数据请求、预缓存关键资源等,从而优化用户体验。

Network Information API 对于移动端应用尤为重要,因为移动设备的网络环境变化频繁,从高速 WiFi 到慢速 2G/3G 网络的切换随时可能发生。通过感知网络状态变化,应用可以做出智能的适配决策。


二、核心概念

1. NetworkInformation 对象

NetworkInformation 是 Network Information API 的核心接口,通过 navigator.connection 属性获取。

代码示例

// 获取 NetworkInformation 对象
const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;

if (connection) {
    console.log(connection.effectiveType);  // 有效连接类型
    console.log(connection.downlink);       // 下行带宽估算(Mbps)
    console.log(connection.rtt);            // 往返时间估算(ms)
    console.log(connection.saveData);       // 是否启用省流模式
    console.log(connection.type);           // 连接类型(已废弃)
}

2. effectiveType(有效连接类型)

代码示例

// effectiveType 返回以下值之一:
// 'slow-2g' - 极慢网络(RTT > 2000ms,下行 < 50kbps)
// '2g'      - 慢速网络(RTT > 1000ms,下行 < 70kbps)
// '3g'      - 中速网络(RTT > 250ms,下行 < 700kbps)
// '4g'      - 快速网络(RTT < 250ms,下行 > 700kbps)

const connection = navigator.connection;
if (connection) {
    switch (connection.effectiveType) {
        case 'slow-2g':
            console.log('极慢网络,仅加载文本');
            break;
        case '2g':
            console.log('慢速网络,加载低分辨率资源');
            break;
        case '3g':
            console.log('中速网络,加载中等质量资源');
            break;
        case '4g':
            console.log('快速网络,加载高质量资源');
            break;
    }
}

3. downlink(下行带宽)

代码示例

// downlink 返回估算的下行带宽,单位为 Mbps
// 值基于最近活跃连接的加权平均值
// 注意:这是估算值,不是精确测量

const connection = navigator.connection;
if (connection) {
    console.log('估算下行带宽:', connection.downlink, 'Mbps');

    // 根据带宽选择资源质量
    if (connection.downlink >= 5) {
        loadHighQualityVideo();
    } else if (connection.downlink >= 1) {
        loadMediumQualityVideo();
    } else {
        loadLowQualityVideo();
    }
}

4. rtt(往返时间)

代码示例

// rtt 返回估算的往返时间,单位为毫秒
// 基于最近活跃连接的加权平均值

const connection = navigator.connection;
if (connection) {
    console.log('估算 RTT:', connection.rtt, 'ms');

    // 根据 RTT 调整超时设置
    const timeout = Math.max(connection.rtt * 3, 3000);
    console.log('建议超时:', timeout, 'ms');
}

5. saveData(省流模式)

代码示例

// saveData 返回布尔值,表示用户是否启用了省流模式
// 当用户在浏览器设置中启用"省流"或"数据节省"模式时为 true

const connection = navigator.connection;
if (connection) {
    if (connection.saveData) {
        console.log('用户启用了省流模式,减少数据传输');
        // 减少请求、加载低分辨率资源、禁用自动播放等
    }
}

6. change 事件

代码示例

// 监听网络状态变化
const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;

if (connection) {
    connection.addEventListener('change', () => {
        console.log('网络状态变化:');
        console.log('有效类型:', connection.effectiveType);
        console.log('下行带宽:', connection.downlink, 'Mbps');
        console.log('RTT:', connection.rtt, 'ms');
        console.log('省流模式:', connection.saveData);
    });
}

三、语法与用法

获取连接信息

代码示例

// 标准获取方式
const connection = navigator.connection;

// 兼容性获取方式
const connection = navigator.connection ||
                   navigator.mozConnection ||
                   navigator.webkitConnection;

// 检测支持
if (connection) {
    // Network Information API 可用
} else {
    // 不可用,使用默认策略
}

读取属性

代码示例

function getConnectionInfo() {
    const connection = navigator.connection;
    if (!connection) return null;

    return {
        effectiveType: connection.effectiveType,  // 'slow-2g' | '2g' | '3g' | '4g'
        downlink: connection.downlink,            // Mbps
        rtt: connection.rtt,                      // ms
        saveData: connection.saveData,            // boolean
        type: connection.type,                    // 已废弃,部分浏览器仍可用
        downlinkMax: connection.downlinkMax       // 已废弃
    };
}

监听变化

代码示例

function onNetworkChange(callback) {
    const connection = navigator.connection ||
                       navigator.mozConnection ||
                       navigator.webkitConnection;

    if (!connection) return;

    const handler = () => {
        callback({
            effectiveType: connection.effectiveType,
            downlink: connection.downlink,
            rtt: connection.rtt,
            saveData: connection.saveData
        });
    };

    connection.addEventListener('change', handler);

    // 返回取消监听函数
    return () => {
        connection.removeEventListener('change', handler);
    };
}

四、代码示例

示例1:网络状态监控面板

代码示例

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Network Information API - 网络状态监控面板</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            font-family: 'Segoe UI', sans-serif;
            background: linear-gradient(135deg, #0c0c1d, #1a1a3e);
            color: #e0e0e0;
            min-height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
            padding: 20px;
        }
        .panel {
            background: rgba(255,255,255,0.05);
            border-radius: 20px;
            padding: 30px;
            width: 500px;
            max-width: 100%;
            backdrop-filter: blur(10px);
        }
        h1 { text-align: center; color: #00cec9; margin-bottom: 25px; }
        .network-type { text-align: center; margin-bottom: 25px; }
        .type-badge {
            display: inline-block;
            padding: 10px 30px;
            border-radius: 25px;
            font-size: 24px;
            font-weight: bold;
            transition: all 0.5s;
        }
        .type-badge.slow-2g { background: rgba(214,48,49,0.2); color: #d63031; border: 2px solid #d63031; }
        .type-badge.2g { background: rgba(225,112,85,0.2); color: #e17055; border: 2px solid #e17055; }
        .type-badge.3g { background: rgba(253,203,110,0.2); color: #fdcb6e; border: 2px solid #fdcb6e; }
        .type-badge.4g { background: rgba(0,206,201,0.2); color: #00cec9; border: 2px solid #00cec9; }
        .type-badge.unknown { background: rgba(99,110,114,0.2); color: #636e72; border: 2px solid #636e72; }
        .metrics { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin-bottom: 25px; }
        .metric-card { background: rgba(255,255,255,0.05); border-radius: 12px; padding: 18px; text-align: center; }
        .metric-card .label { font-size: 12px; color: #888; margin-bottom: 8px; }
        .metric-card .value { font-size: 28px; font-weight: bold; color: #00cec9; }
        .metric-card .unit { font-size: 12px; color: #636e72; }
    </style>
</head>
<body>
    <div class="panel">
        <h1>Network Monitor</h1>
        <div class="network-type">
            <div class="type-badge unknown" id="typeBadge">检测中</div>
        </div>
        <div class="metrics">
            <div class="metric-card">
                <div class="label">下行带宽</div>
                <div class="value" id="downlinkValue">--</div>
                <div class="unit">Mbps</div>
            </div>
            <div class="metric-card">
                <div class="label">往返时间</div>
                <div class="value" id="rttValue">--</div>
                <div class="unit">ms</div>
            </div>
        </div>
    </div>
    <script>
        const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
        if (connection) {
            function updateUI() {
                const type = connection.effectiveType || 'unknown';
                document.getElementById('typeBadge').textContent = type.toUpperCase();
                document.getElementById('typeBadge').className = 'type-badge ' + type;
                document.getElementById('downlinkValue').textContent = connection.downlink.toFixed(1);
                document.getElementById('rttValue').textContent = connection.rtt;
            }
            updateUI();
            connection.addEventListener('change', updateUI);
        }
    </script>
</body>
</html>

示例2:自适应图片加载

代码示例

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Network Information API - 自适应图片加载</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body { font-family: 'Segoe UI', sans-serif; background: #1e272e; color: #d2dae2; min-height: 100vh; padding: 20px; }
        .container { max-width: 700px; margin: 0 auto; }
        h1 { color: #0fbcf9; text-align: center; margin-bottom: 20px; }
        .network-info { display: flex; align-items: center; gap: 15px; padding: 15px 20px; background: rgba(255,255,255,0.05); border-radius: 12px; margin-bottom: 25px; }
        .net-type { padding: 6px 16px; border-radius: 15px; font-size: 14px; font-weight: bold; }
        .net-type.4g { background: rgba(0,206,201,0.2); color: #00cec9; }
        .net-type.3g { background: rgba(253,203,110,0.2); color: #fdcb6e; }
        .net-type.2g { background: rgba(225,112,85,0.2); color: #e17055; }
        .net-type.slow-2g { background: rgba(214,48,49,0.2); color: #d63031; }
        .image-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
        .image-card { border-radius: 12px; overflow: hidden; position: relative; aspect-ratio: 1; background: rgba(255,255,255,0.05); }
        .image-card img { width: 100%; height: 100%; object-fit: cover; }
    </style>
</head>
<body>
    <div class="container">
        <h1>Adaptive Image Loading</h1>
        <div class="network-info">
            <span class="net-type" id="netType">检测中</span>
            <span id="netDetail">正在检测网络状态...</span>
        </div>
        <div class="image-grid" id="imageGrid"></div>
    </div>
    <script>
        const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
        const images = [{seed:'mountain'},{seed:'ocean'},{seed:'forest'},{seed:'city'},{seed:'sunset'},{seed:'flower'}];
        
        function getQuality() {
            if (!connection) return 'sd';
            if (connection.saveData) return 'ld';
            switch (connection.effectiveType) {
                case '4g': return 'hd';
                case '3g': return 'sd';
                case '2g': return 'ld';
                case 'slow-2g': return 'placeholder';
                default: return 'sd';
            }
        }
        
        function loadImages() {
            const quality = getQuality();
            const grid = document.getElementById('imageGrid');
            grid.innerHTML = '';
            images.forEach(img => {
                const card = document.createElement('div');
                card.className = 'image-card';
                const sizes = {hd: 800, sd: 400, ld: 200, placeholder: 50};
                const size = sizes[quality] || sizes.sd;
                card.innerHTML = `<img src="https://picsum.photos/seed/${img.seed}/${size}/${size}.jpg" loading="lazy">`;
                grid.appendChild(card);
            });
        }
        
        loadImages();
        if (connection) connection.addEventListener('change', loadImages);
    </script>
</body>
</html>

示例3:网络感知的数据请求策略

代码示例

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Network Information API - 网络感知数据请求</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body { font-family: 'Segoe UI', sans-serif; background: #0a0a1a; color: #e0e0e0; min-height: 100vh; padding: 20px; }
        .container { max-width: 650px; margin: 0 auto; }
        h1 { color: #a29bfe; text-align: center; margin-bottom: 20px; }
        .status-bar { display: flex; align-items: center; gap: 12px; padding: 12px 20px; background: rgba(255,255,255,0.05); border-radius: 12px; margin-bottom: 20px; }
        .status-dot { width: 10px; height: 10px; border-radius: 50%; background: #00b894; }
        .status-dot.slow { background: #fdcb6e; }
        .btn { padding: 8px 18px; border-radius: 20px; border: 1px solid rgba(255,255,255,0.2); background: rgba(255,255,255,0.05); color: #d2dae2; cursor: pointer; font-size: 13px; }
        .btn.primary { background: rgba(162,155,254,0.2); border-color: #a29bfe; color: #a29bfe; }
        .log-area { background: rgba(0,0,0,0.3); border-radius: 10px; padding: 12px; max-height: 250px; overflow-y: auto; font-family: monospace; font-size: 12px; }
    </style>
</head>
<body>
    <div class="container">
        <h1>Network-Aware Requests</h1>
        <div class="status-bar">
            <div class="status-dot" id="statusDot"></div>
            <span id="statusText">检测网络状态...</span>
        </div>
        <button class="btn primary" id="fetchBtn">发送请求</button>
        <div class="log-area" id="logArea"></div>
    </div>
    <script>
        const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
        const strategies = {
            '4g': {timeout: 5000, retries: 2},
            '3g': {timeout: 10000, retries: 3},
            '2g': {timeout: 20000, retries: 4},
            'slow-2g': {timeout: 30000, retries: 5}
        };
        
        function getStrategy() {
            if (!connection) return {timeout: 10000, retries: 3};
            return strategies[connection.effectiveType] || strategies['3g'];
        }
        
        document.getElementById('fetchBtn').addEventListener('click', async () => {
            const strategy = getStrategy();
            const controller = new AbortController();
            setTimeout(() => controller.abort(), strategy.timeout);
            try {
                const response = await fetch('https://httpbin.org/get', {signal: controller.signal});
                document.getElementById('logArea').innerHTML += '<div style="color:#00b894">请求成功</div>';
            } catch (e) {
                document.getElementById('logArea').innerHTML += '<div style="color:#d63031">请求失败: ' + e.message + '</div>';
            }
        });
    </script>
</body>
</html>

示例4:在线/离线状态与网络质量综合监控

代码示例

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Network Information API - 综合网络监控</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body { font-family: 'Segoe UI', sans-serif; background: #0f0f23; color: #e0e0e0; min-height: 100vh; padding: 20px; }
        .container { max-width: 700px; margin: 0 auto; }
        h1 { color: #e056fd; text-align: center; margin-bottom: 25px; }
        .online-status { text-align: center; padding: 15px; border-radius: 12px; margin-bottom: 20px; font-size: 16px; font-weight: bold; }
        .online-status.online { background: rgba(0,184,148,0.15); border: 1px solid rgba(0,184,148,0.3); color: #00b894; }
        .online-status.offline { background: rgba(214,48,49,0.15); border: 1px solid rgba(214,48,49,0.3); color: #d63031; }
        .dashboard { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; }
        .dash-card { background: rgba(255,255,255,0.05); border-radius: 12px; padding: 18px; text-align: center; }
        .dash-card .label { font-size: 12px; color: #808e9b; margin-bottom: 5px; }
        .dash-card .value { font-size: 22px; font-weight: bold; }
    </style>
</head>
<body>
    <div class="container">
        <h1>Network Dashboard</h1>
        <div class="online-status online" id="onlineStatus">在线</div>
        <div class="dashboard">
            <div class="dash-card">
                <div class="label">连接类型</div>
                <div class="value" id="dType">--</div>
            </div>
            <div class="dash-card">
                <div class="label">下行带宽</div>
                <div class="value" id="dDownlink">-- Mbps</div>
            </div>
            <div class="dash-card">
                <div class="label">往返延迟</div>
                <div class="value" id="dRtt">-- ms</div>
            </div>
            <div class="dash-card">
                <div class="label">省流模式</div>
                <div class="value" id="dSaveData">--</div>
            </div>
        </div>
    </div>
    <script>
        const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
        
        function updateDashboard() {
            const isOnline = navigator.onLine;
            const statusEl = document.getElementById('onlineStatus');
            statusEl.textContent = isOnline ? '在线' : '离线';
            statusEl.className = 'online-status ' + (isOnline ? 'online' : 'offline');
            
            if (connection) {
                document.getElementById('dType').textContent = (connection.effectiveType || 'unknown').toUpperCase();
                document.getElementById('dDownlink').textContent = connection.downlink.toFixed(1) + ' Mbps';
                document.getElementById('dRtt').textContent = connection.rtt + ' ms';
                document.getElementById('dSaveData').textContent = connection.saveData ? '已启用' : '未启用';
            }
        }
        
        updateDashboard();
        if (connection) connection.addEventListener('change', updateDashboard);
        window.addEventListener('online', updateDashboard);
        window.addEventListener('offline', updateDashboard);
    </script>
</body>
</html>

五、浏览器兼容性

特性 Chrome Firefox Safari Edge 移动端支持
navigator.connection 61+ 不支持 不支持 79+ Android Chrome 38+
effectiveType 61+ 不支持 不支持 79+ Android Chrome 38+
downlink 61+ 不支持 不支持 79+ Android Chrome 38+
rtt 61+ 不支持 不支持 79+ Android Chrome 38+
saveData 65+ 不支持 不支持 79+ Android Chrome 65+
change 事件 61+ 不支持 不支持 79+ Android Chrome 38+
type (已废弃) 不支持 不支持 不支持 不支持 Android Browser

兼容性注意事项

代码示例

// 1. Firefox 和 Safari 不支持 Network Information API
// 需要提供降级方案

// 2. 完整的兼容性检测
function isNetworkInfoSupported() {
    return !!(navigator.connection ||
              navigator.mozConnection ||
              navigator.webkitConnection);
}

// 3. 安全获取连接信息
function getConnection() {
    return navigator.connection ||
           navigator.mozConnection ||
           navigator.webkitConnection ||
           null;
}

// 4. iOS 和 Firefox 的降级方案
// 可以使用 Performance API 估算网络质量
async function estimateNetworkQuality() {
    if (getConnection()) {
        return {
            type: getConnection().effectiveType,
            downlink: getConnection().downlink,
            rtt: getConnection().rtt
        };
    }

    // 降级:使用 Resource Timing API 估算
    const entries = performance.getEntriesByType('resource');
    if (entries.length > 0) {
        const recent = entries.slice(-5);
        const avgRtt = recent.reduce((sum, e) => sum + (e.responseEnd - e.requestStart), 0) / recent.length;
        return {
            type: avgRtt < 250 ? '4g' : avgRtt < 1000 ? '3g' : '2g',
            downlink: null,
            rtt: Math.round(avgRtt)
        };
    }

    return { type: 'unknown', downlink: null, rtt: null };
}

六、注意事项与最佳实践

1. 渐进增强策略

代码示例

// 最佳实践:Network Information API 作为增强,而非依赖

// 基础功能:所有用户都能使用
function loadImage(baseUrl) {
    return fetch(baseUrl);
}

// 增强功能:根据网络状态优化
function loadAdaptiveImage(baseUrl) {
    const connection = getConnection();

    if (!connection) {
        // API 不可用,使用默认质量
        return loadImage(baseUrl);
    }

    // 根据网络状态选择质量
    let quality = 'medium';
    if (connection.saveData) {
        quality = 'low';
    } else {
        switch (connection.effectiveType) {
            case '4g': quality = 'high'; break;
            case '3g': quality = 'medium'; break;
            case '2g':
            case 'slow-2g': quality = 'low'; break;
        }
    }

    const url = `${baseUrl}?quality=${quality}`;
    return loadImage(url);
}

2. 避免频繁切换

代码示例

// 问题:网络状态频繁变化导致频繁切换策略
// 解决方案:添加防抖和最小切换间隔

class StableNetworkStrategy {
    constructor(minSwitchInterval = 30000) {
        this.minSwitchInterval = minSwitchInterval;
        this.lastSwitchTime = 0;
        this.currentStrategy = 'default';
    }

    getStrategy() {
        const connection = getConnection();
        if (!connection) return 'default';

        const now = Date.now();
        if (now - this.lastSwitchTime < this.minSwitchInterval) {
            return this.currentStrategy;
        }

        let newStrategy;
        switch (connection.effectiveType) {
            case '4g': newStrategy = 'high'; break;
            case '3g': newStrategy = 'medium'; break;
            case '2g':
            case 'slow-2g': newStrategy = 'low'; break;
            default: newStrategy = 'default';
        }

        if (newStrategy !== this.currentStrategy) {
            this.currentStrategy = newStrategy;
            this.lastSwitchTime = now;
        }

        return this.currentStrategy;
    }
}

3. 结合 Service Worker

代码示例

// Network Information API 与 Service Worker 结合使用
// 在 Service Worker 中根据网络状态选择缓存策略

// service-worker.js
self.addEventListener('fetch', (event) => {
    const connection = self.navigator.connection;

    if (connection) {
        switch (connection.effectiveType) {
            case '4g':
                // 高速网络:网络优先
                event.respondWith(
                    fetch(event.request)
                        .catch(() => caches.match(event.request))
                );
                break;

            case '3g':
                // 中速网络:缓存优先,后台更新
                event.respondWith(
                    caches.match(event.request).then(cached => {
                        const fetchPromise = fetch(event.request).then(response => {
                            const cache = await caches.open('dynamic');
                            cache.put(event.request, response.clone());
                            return response;
                        });
                        return cached || fetchPromise;
                    })
                );
                break;

            case '2g':
            case 'slow-2g':
                // 慢速网络:仅缓存
                event.respondWith(
                    caches.match(event.request)
                );
                break;

            default:
                event.respondWith(
                    fetch(event.request)
                        .catch(() => caches.match(event.request))
                );
        }
    } else {
        // API 不可用,使用默认策略
        event.respondWith(
            fetch(event.request)
                .catch(() => caches.match(event.request))
        );
    }
});

4. 隐私考虑

代码示例

// Network Information API 可能被用于指纹追踪
// 最佳实践:降低精度,不发送到服务器

// 不好的做法:将精确的网络信息发送到服务器
// fetch('/analytics', {
//     method: 'POST',
//     body: JSON.stringify({
//         type: connection.effectiveType,
//         downlink: connection.downlink,  // 精确值
//         rtt: connection.rtt             // 精确值
//     })
// });

// 好的做法:仅发送粗略信息
function getNetworkCategory() {
    const connection = getConnection();
    if (!connection) return 'unknown';

    // 只返回大类,不返回精确值
    if (connection.saveData) return 'saving';
    if (connection.effectiveType === '4g') return 'fast';
    if (connection.effectiveType === '3g') return 'medium';
    return 'slow';
}

七、代码规范示例

完整的网络感知管理封装

代码示例

/**
 * NetworkAwareManager - 网络感知管理器
 * 封装 Network Information API,提供策略管理、事件监听、优雅降级
 */
class NetworkAwareManager {
    // 网络质量等级
    static QUALITY = {
        FAST: 'fast',       // 4G
        MEDIUM: 'medium',   // 3G
        SLOW: 'slow',       // 2G
        VERY_SLOW: 'very-slow', // slow-2g
        OFFLINE: 'offline',
        UNKNOWN: 'unknown'
    };

    // 预定义策略
    static STRATEGIES = {
        [NetworkAwareManager.QUALITY.FAST]: {
            imageQuality: 'high',
            requestTimeout: 5000,
            retryCount: 2,
            concurrency: 6,
            cacheMode: 'network-first',
            prefetch: true,
            autoPlay: true
        },
        [NetworkAwareManager.QUALITY.MEDIUM]: {
            imageQuality: 'medium',
            requestTimeout: 10000,
            retryCount: 3,
            concurrency: 3,
            cacheMode: 'stale-while-revalidate',
            prefetch: false,
            autoPlay: false
        },
        [NetworkAwareManager.QUALITY.SLOW]: {
            imageQuality: 'low',
            requestTimeout: 20000,
            retryCount: 4,
            concurrency: 2,
            cacheMode: 'cache-first',
            prefetch: false,
            autoPlay: false
        },
        [NetworkAwareManager.QUALITY.VERY_SLOW]: {
            imageQuality: 'placeholder',
            requestTimeout: 30000,
            retryCount: 5,
            concurrency: 1,
            cacheMode: 'cache-only',
            prefetch: false,
            autoPlay: false
        },
        [NetworkAwareManager.QUALITY.OFFLINE]: {
            imageQuality: 'placeholder',
            requestTimeout: 0,
            retryCount: 0,
            concurrency: 0,
            cacheMode: 'cache-only',
            prefetch: false,
            autoPlay: false
        },
        [NetworkAwareManager.QUALITY.UNKNOWN]: {
            imageQuality: 'medium',
            requestTimeout: 10000,
            retryCount: 3,
            concurrency: 4,
            cacheMode: 'network-first',
            prefetch: false,
            autoPlay: false
        }
    };

    constructor(options = {}) {
        this.connection = navigator.connection ||
                         navigator.mozConnection ||
                         navigator.webkitConnection;

        this.minSwitchInterval = options.minSwitchInterval || 5000;
        this.lastSwitchTime = 0;
        this.currentQuality = NetworkAwareManager.QUALITY.UNKNOWN;
        this.listeners = new Map();
        this._initialized = false;
    }

    /** 初始化 */
    async init() {
        if (this._initialized) return this;

        // 初始评估
        this._evaluateNetwork();

        // 监听网络变化
        if (this.connection) {
            this.connection.addEventListener('change', () => this._onNetworkChange());
        }

        // 监听在线/离线
        window.addEventListener('online', () => this._onNetworkChange());
        window.addEventListener('offline', () => this._onNetworkChange());

        this._initialized = true;
        this._emit('ready', this.getStatus());

        return this;
    }

    /** 评估网络质量 */
    _evaluateNetwork() {
        if (!navigator.onLine) {
            this.currentQuality = NetworkAwareManager.QUALITY.OFFLINE;
            return;
        }

        if (!this.connection) {
            this.currentQuality = NetworkAwareManager.QUALITY.UNKNOWN;
            return;
        }

        // 省流模式优先
        if (this.connection.saveData) {
            this.currentQuality = NetworkAwareManager.QUALITY.SLOW;
            return;
        }

        switch (this.connection.effectiveType) {
            case '4g':
                this.currentQuality = NetworkAwareManager.QUALITY.FAST;
                break;
            case '3g':
                this.currentQuality = NetworkAwareManager.QUALITY.MEDIUM;
                break;
            case '2g':
                this.currentQuality = NetworkAwareManager.QUALITY.SLOW;
                break;
            case 'slow-2g':
                this.currentQuality = NetworkAwareManager.QUALITY.VERY_SLOW;
                break;
            default:
                this.currentQuality = NetworkAwareManager.QUALITY.UNKNOWN;
        }
    }

    /** 网络变化处理 */
    _onNetworkChange() {
        const now = Date.now();
        if (now - this.lastSwitchTime < this.minSwitchInterval) return;

        const oldQuality = this.currentQuality;
        this._evaluateNetwork();

        if (oldQuality !== this.currentQuality) {
            this.lastSwitchTime = now;
            this._emit('qualitychange', {
                oldQuality,
                newQuality: this.currentQuality,
                strategy: this.getStrategy()
            });
        }

        this._emit('networkchange', this.getStatus());
    }

    /** 获取当前策略 */
    getStrategy() {
        return NetworkAwareManager.STRATEGIES[this.currentQuality] ||
               NetworkAwareManager.STRATEGIES[NetworkAwareManager.QUALITY.UNKNOWN];
    }

    /** 获取当前状态 */
    getStatus() {
        return {
            quality: this.currentQuality,
            isOnline: navigator.onLine,
            effectiveType: this.connection?.effectiveType || null,
            downlink: this.connection?.downlink || null,
            rtt: this.connection?.rtt || null,
            saveData: this.connection?.saveData || false,
            strategy: this.getStrategy()
        };
    }

    /** 判断是否为慢速网络 */
    isSlowNetwork() {
        return [NetworkAwareManager.QUALITY.SLOW,
                NetworkAwareManager.QUALITY.VERY_SLOW,
                NetworkAwareManager.QUALITY.OFFLINE].includes(this.currentQuality);
    }

    /** 判断是否应加载数据 */
    shouldLoadData() {
        return this.currentQuality !== NetworkAwareManager.QUALITY.OFFLINE;
    }

    /** 注册事件 */
    on(event, callback) {
        if (!this.listeners.has(event)) {
            this.listeners.set(event, []);
        }
        this.listeners.get(event).push(callback);
        return this;
    }

    /** 触发事件 */
    _emit(event, data) {
        if (this.listeners.has(event)) {
            this.listeners.get(event).forEach(cb => cb(data));
        }
    }

    /** 销毁 */
    destroy() {
        this.listeners.clear();
        this._initialized = false;
    }
}

// 使用示例
const networkManager = new NetworkAwareManager({
    minSwitchInterval: 5000
});

networkManager
    .on('ready', (status) => {
        console.log('网络管理器已启动:', status.quality);
    })
    .on('qualitychange', ({ oldQuality, newQuality, strategy }) => {
        console.log(`网络质量变化: ${oldQuality} -> ${newQuality}`);
        console.log('新策略:', strategy);
    })
    .on('networkchange', (status) => {
        console.log('网络状态变化:', status);
    });

networkManager.init();

八、常见问题与解决方案

常见问题

Firefox 和 Safari 不支持 Network Information API 怎么办?

Firefox 和 Safari 不支持 Network Information API。解决方案是使用 Performance API 的 Resource Timing API 估算网络质量。通过分析最近几个资源的请求和响应时间差,可以估算出 RTT 值,从而判断网络类型(4G/3G/2G)。这种方法虽然不如原生 API 精确,但能在不支持的浏览器上提供基本的网络质量感知能力。

网络状态频繁变化如何处理?

移动设备在 WiFi 和蜂窝网络间频繁切换会导致策略频繁变化。解决方案是使用防抖和滞后策略:设置最小切换间隔(如 5-30 秒),在此间隔内不响应网络变化事件;或者使用延迟处理,在检测到网络变化后等待一段时间再执行策略切换,避免短暂的网络波动导致不必要的调整。

downlink 和 rtt 值不准确怎么办?

downlink 和 rtt 是基于最近活跃连接的估算值,可能与实际网络状况有偏差。解决方案是自行测量网络质量:通过向测试服务器发送小文件请求,使用 Performance API 精确测量请求时间,计算实际的 RTT 和下载速度。多次测量取平均值可以获得更准确的结果。

Network Information API 存在隐私问题吗?

是的,Network Information API 可能被用于指纹追踪。网络类型、带宽、RTT 等组合信息可以作为用户指纹的一部分。最佳实践是降低精度,只使用粗略分类(fast/medium/slow)而非精确值;不将网络信息发送到服务器;仅在客户端本地使用这些信息来优化用户体验。

如何正确实现网络感知的资源加载?

应使用渐进增强策略:首先确保基础功能在所有网络条件下都能工作,然后根据 Network Information API 进行优化。根据 effectiveType 选择不同质量的资源(图片分辨率、视频码率等);根据 saveData 属性尊重用户的省流意愿;使用防抖避免频繁切换;在 API 不可用时提供合理的默认策略。


九、总结

Network Information API 为 Web 应用提供了获取设备网络连接信息的能力,主要特点包括:

  • 网络类型检测:通过 effectiveType 获取当前网络质量等级(4G/3G/2G/slow-2g)。

  • 带宽和延迟估算:通过 downlinkrtt 获取网络带宽和往返时间的估算值。

  • 省流模式检测:通过 saveData 检测用户是否启用了数据节省模式。

  • 实时变化监听:通过 change 事件实时响应网络状态变化。

  • 在线/离线检测:结合 navigator.onLineonline/offline 事件检测网络连接状态。

在使用 Network Information API 时,需要注意:

  • 兼容性有限:仅 Chrome 和 Edge 支持,Firefox 和 Safari 不支持

  • 估算值非精确值:downlink 和 rtt 是基于历史连接的估算

  • 隐私风险:网络信息可能被用于指纹追踪

  • 需要降级方案:API 不可用时

本文涉及AI创作

内容由AI创作,请仔细甄别

list快速访问

上一篇: HTML5 API:Battery API - 完整教程与代码示例 下一篇: 布局结构:HTML布局概述 - 完整教程与代码示例

poll相关推荐